1
2
3
4
5
6
7
8
9
title: 移动互联网技术
description: 安卓复习
date: 2023-12-4 15:28:00
update: 2023-12-4 15:58:00
tags:
- 计算机
categories:
- 安卓
swiper_index: 16

安卓复习

实验一

  1. 请结合android的体系结构说明,为什么Android选择Java作为上层应用的开发语言。

    四层结构

    • 应用层:自带应用,拨号应用,短信应用,浏览器等
    • 应用框架层:为开发者提供了大量的API
    • 系统运行库层
      • Libraries类库:提供SQLite、Media等功能
      • Android Runtime
        • Core Libraries 核心库:提供Java编程的核心库
        • Davlik 虚拟机:用来执行Android程序的。每一个Android应用程序都是在一个独立的Davlik虚拟机实例中执行的。
    • Linux内核层:内存管理,进程管理,网络协议,以及驱动模型等核心服务

    原因

    • 广泛的生态系统,Java是一种广泛使用的语言,有庞大而成熟的开发者社区和丰富的第三方库支持。开发人员可以更快速地构建和扩展Android应用程序,解决问题时可以利用大量开源资源。

    • 跨平台,通过JVM,安卓应用程序可以再不同硬件和操作系统上运行,便于开发者更高效的开发并将应用程序发布到不同设备上

    • 性能优化,安卓平台通过引入JIT编译器,AOT编译器等技术来提升java应用程序的性能,这些优化措施使java在安卓平台上可以提供高效的运行和响应能力

    • 良好的安全性,Java具有严格的安全性措施,例如内存管理,异常处理和访问控制等机制,为移动应用程序提供可靠的安全保护,防止恶意代码和漏洞的利用

    • 对新手友好,Java语言相对于其他语言来说,相对容易学习和理解,采用简洁明了的语法,提供强大的面向对象编程功能,且拥有丰富的文档和教k程资源,使得初学者和新手开发者可以更容易地进入Android应用开发领域。

  2. 请说明Android Studio和 Android SDK的关系。

    • Android Studio是安卓应用开发工具
    • Android SDK是一个开发包,提供开发工具给开发者开发安卓
    • Android Studio内置了Android SDK
  3. AVD Manager,SDK Mannager,DDMS分别是干什么的?

    • AVD Manager:创建和管理Android虚拟设备的工具
    • SDK Mannager:开发工具包中的一个核心组件,用于管理和更新Android SDK的各种组件
    • DDMS:监控和调试Android设备或模拟器上运行的应用程序的调试工具
  4. 请结合HelloWorld程序说明Android中是如何基于MVC模式进行开发的?

实验二

  1. 除了课堂上介绍的设置的监听器的方法,还有哪些办法实现按钮的响应?

  2. 增加两个按钮(prev,next)实现问题的遍历。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public class MainActivity extends AppCompatActivity {
    private TextView mQuestion;
    private int mCurrentIndex = 0;
    public int[] mQuestionBank = new int[]{
    R.string.question_australia,
    R.string.question_oceans,
    R.string.question_mideast,
    R.string.question_africa,
    R.string.question_americas,
    R.string.question_asia,
    };
    private Button mBtnPrev;
    private Button mMBtnNext;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mQuestion = findViewById(R.id.question_text);
    mQuestion.setText(mQuestionBank[mCurrentIndex]);
    mBtnPrev = findViewById(R.id.btnPrev);
    mBtnPrev.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    mCurrentIndex = (mCurrentIndex - 1 + mQuestionBank.length) % mQuestionBank.length;
    mQuestion.setText(mQuestionBank[mCurrentIndex]);
    }
    });
    mMBtnNext = findViewById(R.id.btnNext);
    mMBtnNext.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
    mQuestion.setText(mQuestionBank[mCurrentIndex]);
    }
    });
    }
    }
  3. 问题描述已经分离在资源文件中,如何将answer也分离在资源文件中?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class Question {
    private int mTextId;
    private int mAnswerId;
    public Question(int textId, int answerId) {
    mTextId = textId;
    mAnswerId = answerId;
    }
    public int getTextId() {
    return mTextId;
    }
    public void setTextId(int textId) {
    mTextId = textId;
    }
    public int getAnswerId() {
    return mAnswerId;
    }
    public void setAnswerId(int answerId) {
    mAnswerId = answerId;
    }
    @Override
    public String toString() {
    return "Question{" +
    "mTextId=" + mTextId +
    ", mAnswerId=" + mAnswerId +
    '}';
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public Question[] mQuestionBank = new Question[]{
    new Question(R.string.question_australia,R.string.false_answer),
    new Question(R.string.question_oceans,R.string.true_answer),
    new Question(R.string.question_mideast,R.string.true_answer),
    new Question(R.string.question_africa,R.string.true_answer),
    new Question(R.string.question_americas,R.string.true_answer),
    new Question(R.string.question_asia,R.string.true_answer),
    };
    //选择True
    mMBtnTrue = findViewById(R.id.btnTrue);
    mMBtnTrue.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    if (mQuestionBank[mCurrentIndex].getAnswerId() == R.string.true_answer) {
    Toast.makeText(MainActivity.this, R.string.correct_toast, Toast.LENGTH_SHORT).show();
    } else {
    Toast.makeText(MainActivity.this, R.string.incorrect_toast, Toast.LENGTH_SHORT).show();
    }
    }
    });
    //选择False
    mMMBtnFalse = findViewById(R.id.btnFalse);
    mMMBtnFalse.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    if (mQuestionBank[mCurrentIndex].getAnswerId() == R.string.false_answer) {
    Toast.makeText(MainActivity.this, R.string.correct_toast, Toast.LENGTH_SHORT).show();
    } else {
    Toast.makeText(MainActivity.this, R.string.incorrect_toast, Toast.LENGTH_SHORT).show();
    }
    }
    });
  4. 如果要增加新的问题及其答案,是否需要修改Java代码?能否不修改Java代码,仅修改资源文件就实现增加新的问题。提示:Array类型的资源.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <string-array name="questions">
    <item>Canberra is the capital of Australia.</item>
    <item>The Pacific Ocean is larger than the Atlantic Ocean.</item>
    <item>The Suez Canal connects the Red Sea and the Indian Ocean.</item>
    <item>The source of the Nile River is in Egypt.</item>
    <item>The Amazon River is the longest river in the Americas.</item>
    <item>Lake Baikal is the world\'s oldest and deepest freshwater lake.</item>
    </string-array>
    <string-array name="answers">
    <item>false</item>
    <item>true</item>
    <item>true</item>
    <item>true</item>
    <item>true</item>
    <item>true</item>
    </string-array>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Question {
    private String mQuestionText;
    private String mAnswerText;
    public Question(String questionText, String answerText) {
    mQuestionText = questionText;
    mAnswerText = answerText;
    }
    public String getQuestionText() {
    return mQuestionText;
    }
    public void setQuestionText(String questionText) {
    mQuestionText = questionText;
    }
    public String getAnswerText() {
    return mAnswerText;
    }
    public void setAnswerText(String answerText) {
    mAnswerText = answerText;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mQuestion = findViewById(R.id.question_text);
    String[] questions = getResources().getStringArray(R.array.questions);
    String[] answers = getResources().getStringArray(R.array.answers);
    Question[] mQuestionBank = new Question[questions.length];
    for (int i = 0; i < questions.length; i++) {
    mQuestionBank[i] = new Question(questions[i], answers[i]);
    }
    mQuestion.setText(mQuestionBank[mCurrentIndex].getQuestionText());
    //上一题
    mBtnPrev = findViewById(R.id.btnPrev);
    mBtnPrev.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    mCurrentIndex = (mCurrentIndex - 1 + mQuestionBank.length) % mQuestionBank.length;
    mQuestion.setText(mQuestionBank[mCurrentIndex].getQuestionText());
    }
    });
  5. 用户答完某题后,禁掉对应题目的True和False作答按钮,即,一个题目只允许作答一次;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    public int[] mAnswerFlag = new int[mQuestionBank.length];//0表示未回答,1表示回答正确,2表示回答错误
    //上一题
    mBtnPrev = findViewById(R.id.btnPrev);
    mBtnPrev.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    mCurrentIndex = (mCurrentIndex - 1 + mQuestionBank.length) % mQuestionBank.length;
    mQuestion.setText(mQuestionBank[mCurrentIndex].getTextId());
    if (mAnswerFlag[mCurrentIndex] == 0) {
    mMBtnTrue.setEnabled(true);
    mMMBtnFalse.setEnabled(true);
    } else {
    mMBtnTrue.setEnabled(false);
    mMMBtnFalse.setEnabled(false);
    }
    }
    });
    //下一题
    mMBtnNext = findViewById(R.id.btnNext);
    mMBtnNext.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
    mQuestion.setText(mQuestionBank[mCurrentIndex].getTextId());
    if (mAnswerFlag[mCurrentIndex] == 0) {
    mMBtnTrue.setEnabled(true);
    mMMBtnFalse.setEnabled(true);
    } else {
    mMBtnTrue.setEnabled(false);
    mMMBtnFalse.setEnabled(false);
    }
    }
    });
    //选择True
    mMBtnTrue = findViewById(R.id.btnTrue);
    mMBtnTrue.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    if (mQuestionBank[mCurrentIndex].getAnswerId() == R.string.true_answer) {
    mAnswerFlag[mCurrentIndex] = 1;
    mMBtnTrue.setEnabled(false);
    mMMBtnFalse.setEnabled(false);
    Toast.makeText(MainActivity.this, R.string.correct_toast, Toast.LENGTH_SHORT).show();
    } else {
    mAnswerFlag[mCurrentIndex] = 2;
    mMBtnTrue.setEnabled(false);
    mMMBtnFalse.setEnabled(false);
    Toast.makeText(MainActivity.this, R.string.incorrect_toast, Toast.LENGTH_SHORT).show();
    }
    }
    });
    //选择False
    mMMBtnFalse = findViewById(R.id.btnFalse);
    mMMBtnFalse.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    if (mQuestionBank[mCurrentIndex].getAnswerId() == R.string.false_answer) {
    mAnswerFlag[mCurrentIndex] = 1;
    mMMBtnFalse.setEnabled(false);
    mMBtnTrue.setEnabled(false);
    Toast.makeText(MainActivity.this, R.string.correct_toast, Toast.LENGTH_SHORT).show();
    } else {
    mAnswerFlag[mCurrentIndex] = 2;
    mMMBtnFalse.setEnabled(false);
    mMBtnTrue.setEnabled(false);
    Toast.makeText(MainActivity.this, R.string.incorrect_toast, Toast.LENGTH_SHORT).show();
    }
    }
    });
  6. 用户答完全部的题目后,toast消息提示答对题目的百分比评分;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    int count = 0;//记录回答正确的题目数
    //选择True
    mMBtnTrue = findViewById(R.id.btnTrue);
    mMBtnTrue.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    if (mQuestionBank[mCurrentIndex].getAnswerId() == R.string.true_answer) {
    count++;
    mAnswerFlag[mCurrentIndex] = 1;
    mMBtnTrue.setEnabled(false);
    mMMBtnFalse.setEnabled(false);
    Toast.makeText(MainActivity.this, R.string.correct_toast, Toast.LENGTH_SHORT).show();
    } else {
    mAnswerFlag[mCurrentIndex] = 2;
    mMBtnTrue.setEnabled(false);
    mMMBtnFalse.setEnabled(false);
    Toast.makeText(MainActivity.this, R.string.incorrect_toast, Toast.LENGTH_SHORT).show();
    }
    if(mCurrentIndex==mQuestionBank.length-1){
    Double res=(count/(mQuestionBank.length*1.0))*100;
    Toast.makeText(MainActivity.this, "正确率为"+res+"%", Toast.LENGTH_SHORT).show();
    }
    }
    });
    //选择False
    mMMBtnFalse = findViewById(R.id.btnFalse);
    mMMBtnFalse.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    if (mQuestionBank[mCurrentIndex].getAnswerId() == R.string.false_answer) {
    count++;
    mAnswerFlag[mCurrentIndex] = 1;
    mMMBtnFalse.setEnabled(false);
    mMBtnTrue.setEnabled(false);
    Toast.makeText(MainActivity.this, R.string.correct_toast, Toast.LENGTH_SHORT).show();
    } else {
    mAnswerFlag[mCurrentIndex] = 2;
    mMMBtnFalse.setEnabled(false);
    mMBtnTrue.setEnabled(false);
    Toast.makeText(MainActivity.this, R.string.incorrect_toast, Toast.LENGTH_SHORT).show();
    }
    if(mCurrentIndex==mQuestionBank.length-1){
    Double res=(count/(mQuestionBank.length*1.0))*100;
    Toast.makeText(MainActivity.this, "正确率为"+res+"%", Toast.LENGTH_SHORT).show();
    }
    }
    });

实验三

  1. 请给MiniQuiz增加一个Activity,在这个新增的Activity提示答案;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".CheatActivity">
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="题目"/>
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="你确定要提示答案吗?"/>
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="答案"/>
    <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="提示答案"/>
    </LinearLayout>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
            mMBtnCheat = findViewById(R.id.btnCheat);
    mMBtnCheat.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    Intent intent = new Intent(MainActivity.this, CheatActivity.class);
    Bundle bundle = new Bundle();
    bundle.putString("question", getResources().getString(mQuestionBank[mCurrentIndex].getTextId()));
    bundle.putString("answer", getResources().getString(mQuestionBank[mCurrentIndex].getAnswerId()));
    intent.putExtras(bundle);
    startActivity(intent);
    }
    });
    }


    public class CheatActivity extends AppCompatActivity {
    private TextView mQuestionCheat;
    private Button mBtnShowAnswer;
    private TextView mMAnswerCheat;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_cheat);
    Bundle bundle = getIntent().getExtras();
    String question = bundle.getString("question");
    String answer = bundle.getString("answer");
    mQuestionCheat = findViewById(R.id.question_cheat);
    mQuestionCheat.setText(question);
    mMAnswerCheat = findViewById(R.id.answer_cheat);
    mBtnShowAnswer = findViewById(R.id.show_answer_button);
    mBtnShowAnswer.setOnClickListener(view -> {
    mMAnswerCheat.setText(answer);
    Toast.makeText(this, "Cheat is wrong", Toast.LENGTH_SHORT).show();
    Intent intent = new Intent(CheatActivity.this, MainActivity.class);
    startActivity(intent);
    });
    }
    }
  2. 再新增一个Activty,答完最后一个题目的时候,提示用户一共回答正确和错误的汇总情况,以及使用了提示答案的情况;在这个新增的Acity中共用户选择“退出程序”或者“重新开始答题”。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    //点击提示答案返回原界面回传作弊次数
    //CheatActivity
    public class CheatActivity extends AppCompatActivity {
    private TextView mQuestionCheat;
    private Button mBtnShowAnswer;
    private TextView mMAnswerCheat;
    private Button mMBtnBack;
    private int isCheated = 0;//0表示未作弊,1表示作弊
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_cheat);
    Bundle bundle = getIntent().getExtras();
    int mCurrentIndex = bundle.getInt("mCurrentIndex");
    mQuestionCheat = findViewById(R.id.question_cheat);
    mQuestionCheat.setText(MainActivity.mQuestionBank[mCurrentIndex].getTextId());
    mMAnswerCheat = findViewById(R.id.answer_cheat);
    mBtnShowAnswer = findViewById(R.id.show_answer_button);
    mBtnShowAnswer.setOnClickListener(view -> {
    isCheated = 1;
    mMAnswerCheat.setText(MainActivity.mQuestionBank[mCurrentIndex].getAnswerId());
    Toast.makeText(this, "Cheat is wrong", Toast.LENGTH_SHORT).show();
    });
    //回传是否作弊
    mMBtnBack = findViewById(R.id.btn_cheat_back);
    mMBtnBack.setOnClickListener(view -> {
    Intent intent = new Intent();
    Bundle bundle1 = new Bundle();
    bundle1.putInt("isCheated", isCheated);
    intent.putExtras(bundle1);
    setResult(Activity.RESULT_OK, intent);
    finish();
    });
    }
    }
    //MainActivity
    private ActivityResultLauncher<Intent> mRegister;
    private int[] isCheated = new int[mQuestionBank.length];//0表示未作弊,1表示作弊
    private int mCheatCount=0;
    mRegister = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
    if(result!=null){
    Intent intent=result.getData();
    if(intent!=null && result.getResultCode()== Activity.RESULT_OK){
    Bundle bundle=intent.getExtras();
    //是否作弊
    isCheated[mCurrentIndex] = bundle.getInt("isCheated");
    }
    }
    });
    mMBtnCheat = findViewById(R.id.btnCheat);
    mMBtnCheat.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    Intent intent = new Intent(MainActivity.this, CheatActivity.class);
    Bundle bundle = new Bundle();
    bundle.putInt("mCurrentIndex", mCurrentIndex);
    intent.putExtras(bundle);
    mRegister.launch(intent);
    }
    });
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".SummaryActivity">

    <TextView
    android:id="@+id/questionRes_summary"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="24dp" />

    <TextView
    android:id="@+id/cheatCount_summary"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="24dp" />

    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <Button
    android:id="@+id/btnQuit"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Quit" />

    <Button
    android:id="@+id/btnRestart"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Restart" />
    </LinearLayout>
    </LinearLayout>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    if(mCurrentIndex==mQuestionBank.length-1){
    Double res=(count/(mQuestionBank.length*1.0))*100;
    Intent intent=new Intent(MainActivity.this,SummaryActivity.class);
    Bundle bundle=new Bundle();
    bundle.putInt("count",count);
    bundle.putDouble("res",res);
    for (int i = 0; i < isCheated.length; i++) {
    if(isCheated[i]==1){
    mCheatCount++;
    }
    }
    bundle.putInt("cheatCount",mCheatCount);
    intent.putExtras(bundle);
    startActivity(intent);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class SummaryActivity extends AppCompatActivity {

    private TextView mQuestionRes;
    private TextView mCheatSummary;
    private Button mMBtnQuit;
    private Button mMMBtnRestart;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_summary);
    Bundle bundle = getIntent().getExtras();
    int count = bundle.getInt("count");
    Double res=bundle.getDouble("res");
    int cheatCount = bundle.getInt("cheatCount");
    mQuestionRes = findViewById(R.id.questionRes_summary);
    mQuestionRes.setText("回答正确的题目数为:"+count+","+"回答错误的题目数为"+(MainActivity.mQuestionBank.length-count)+"\n"+"正确率为:"+res+"%,"+"错误率为:"+(100-res)+"%");
    mCheatSummary = findViewById(R.id.cheatCount_summary);
    mCheatSummary.setText("作弊次数为:"+cheatCount);
    mMBtnQuit = findViewById(R.id.btnQuit);
    mMBtnQuit.setOnClickListener(view -> {
    Intent home=new Intent(Intent.ACTION_MAIN);
    home.addCategory(Intent.CATEGORY_HOME);
    startActivity(home);
    });
    mMMBtnRestart = findViewById(R.id.btnRestart);
    mMMBtnRestart.setOnClickListener(view -> {
    Intent intent = new Intent(SummaryActivity.this, MainActivity.class);
    startActivity(intent);
    });
    }
    }
  3. 在整个运行过程中,请使用Log显示Activity生命周期状态变化,并在实验报告中分析其状态变化的条件和原因。

    • 一个Activity正常启动时,会按顺序执行onCreate->onStart->onResume,然后这个Activity is running,就可以和用户交互了

    • 当Another activity comes in front of the activity时,即跳转到另外一个界面,执行onPause,原界面不可见了,onStop

    • 当在新的界面中按返回按钮,则会按顺序执行onStop->onRestart->onStart->onResume,返回原界面

    • 如果在跳转新界面过程中原界面还未来得及完全消失按返回键,则会按顺序执行onPause->onResume,此时原界面Activity is running

    • 如果其他应用需要内存,退在后台运行的应用将被系统杀掉,在点到原界面重新进来时,会按顺序执行onStop->onCreate

    • onCreate:创建活动。把页面布局加载进内存,进入了初始状态。

    • onStart:开始活动。把活动页面显示在屏幕上,进入了就绪状态。

    • onResume:恢复活动。活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的点击动作、允许用户输入文字等等。

    • onPause:暂停活动。页面进入暂停状态,无法与用户正常交互。

    • onStop:停止活动。页面将不在屏幕上显示。

    • onDestroy:销毁活动。回收活动占用的系统资源,把页面从内存中清除。

    • onRestart:重启活动。重新加载内存中的页面数据。

    • onNewlntent:重用已有的活动实例。

​ 4.在以上程序运行的过程中,进行横屏和竖屏的切换,要求,无论程序在什么时候切换,都可以正确运行。

为了避免横竖屏切换时重新加载界面的情况,Android设计了一种配置变更机制,在指定的环境配置发生变更之时,无需重启活动页面,只需执行特定的变更行为。该机制的实现过程分为两步:

  • 修改AndroidManifest,xml,给activity节点增加android:configChanges属性

    configChanges属性的取值 说明
    orientation 屏幕方向发生改变
    screenLayout 屏幕的显示发生改变,例如在全屏和分屏之间切换
    screenSize 屏幕大小发生改变,例如在竖屏与横屏之间切换
    keyboard 键盘发生改变,例如使用了外部键盘
    keyboardHidden 软键盘弹出或隐藏
    navigation 导航方式发生改变,例如采用了轨迹球导航
    fontScale 字体比例发生改变,例如在系统设置中调整默认字体
    locale 设备的本地位置发生改变,例如切换了系统语言
    uiMode 用户界面的模式发生改变,例如开启了夜间模式
  • 修改活动页面的Java代码,重写活动的onConfigurationChanged方法,补充对应的代码处理逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
        <activity
android:name=".MainActivity"
android:exported="true"
android:configChanges="orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
//MainActivity
//在配置项变更时触发,比如屏幕方向发生变更等
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
switch (newConfig.orientation){
case Configuration.ORIENTATION_PORTRAIT:
Toast.makeText(this,"现在是竖屏",Toast.LENGTH_SHORT).show();
break;
case Configuration.ORIENTATION_LANDSCAPE:
Toast.makeText(this,"现在是横屏",Toast.LENGTH_SHORT).show();
break;
}

}

实验四

  1. 实现CiriminalIntent的Fragment的竖屏界面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/fragment_container"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    </FrameLayout>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    //fragment_crime.xml
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="16dp"
    android:orientation="vertical"
    tools:context=".CrimeFragment">
    <TextView
    style="?android:listSeparatorTextViewStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="标题" />
    <EditText
    android:id="@+id/crime_title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="请输入标题"/>
    <TextView
    style="?android:listSeparatorTextViewStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="细节" />
    <Button
    android:id="@+id/crime_date"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="选择日期" />
    <CheckBox
    android:id="@+id/crime_solved"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="解决了" />
    </LinearLayout>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    //CrimeFragment
    public class CrimeFragment extends Fragment {
    private Crime mCrime;
    private TextView mTvTitle;
    private CheckBox mChkSolved;
    private Button mBtnDate;
    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";
    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;
    public CrimeFragment() {
    // Required empty public constructor
    }
    /**
    * Use this factory method to create a new instance of
    * this fragment using the provided parameters.
    * @param param1 Parameter 1.
    * @param param2 Parameter 2.
    * @return A new instance of fragment CrimeFragment.
    */
    // TODO: Rename and change types and number of parameters
    public static CrimeFragment newInstance(String param1, String param2) {
    CrimeFragment fragment = new CrimeFragment();
    Bundle args = new Bundle();
    args.putString(ARG_PARAM1, param1);
    args.putString(ARG_PARAM2, param2);
    fragment.setArguments(args);
    return fragment;
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
    mParam1 = getArguments().getString(ARG_PARAM1);
    mParam2 = getArguments().getString(ARG_PARAM2);
    }
    mCrime = new Crime();
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.fragment_crime, container, false);
    //todo 对view做一些设置
    mTvTitle = v.findViewById(R.id.crime_title);
    mChkSolved = v.findViewById(R.id.crime_solved);
    mBtnDate = v.findViewById(R.id.crime_date);
    mTvTitle.setText(mCrime.getId().toString());
    mBtnDate.setText(mCrime.getDate().toString());
    mChkSolved.setChecked(mCrime.isSolved());
    return v;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //MainActivity
    public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    FragmentManager fm = getSupportFragmentManager();
    Fragment fragment = fm.findFragmentById(R.id.fragment_container);
    if (fragment == null) {
    fragment = new CrimeFragment();
    fm.beginTransaction().add(R.id.fragment_container, fragment).commit();
    }
    }
    }
  2. 实现CiriminalIntent的Fragment的横屏界面

    在res中新建目录layout-land目录,创建与竖屏布局文件同名的文件fragment_crime.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="16dp"
    android:orientation="horizontal"
    tools:context=".CrimeFragment">
    <!-- TODO: Update blank fragment layout -->
    <TextView
    style="?android:listSeparatorTextViewStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="标题" />
    <EditText
    android:id="@+id/crime_title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="请输入标题"/>
    <TextView
    style="?android:listSeparatorTextViewStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="细节" />
    <Button
    android:id="@+id/crime_date"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="选择日期" />
    <CheckBox
    android:id="@+id/crime_solved"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="解决了" />
    </LinearLayout>
  3. 在Activity中加载一个Fragment的基本流程

    1
       
  4. 使用Fragment设计UI有哪些好处?

实验五

  1. 实现一个班级名单的列表视图,每个列表项包含:个人照片,学号和姓名,电话;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //MainActivity
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <FrameLayout
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
    </LinearLayout>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    //StudentFragment
    public class StuFragment extends Fragment {
    private List<Student> mStudentList=new ArrayList<>();
    String[] ids = new String[]{"210107101", "210107102", "210107103", "210107104", "210107105", "210107106"};
    String[] names = new String[]{"张三", "李四", "王二", "赵六", "小红", "小明"};
    String[] phones = new String[]{"1976090901", "1976090902", "1976090903", "1976090904", "1976090905", "1976090906"};
    int[] images = new int[]{R.drawable.aa, R.drawable.bb, R.drawable.cc, R.drawable.dd, R.drawable.ee, R.drawable.ff,R.drawable.gg,R.drawable.hh,R.drawable.ii,R.drawable.jj};
    private TextView mStuId;
    private TextView mMStuName;
    private TextView mMStuPhone;
    private RecyclerView mRecyclerView;
    private StuAdapter mStuAdapter;
    private Button mBtnAdd;
    private ImageView mMStuImg;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View view=inflater.inflate(R.layout.fragment_stu, container, false);
    mMStuImg = view.findViewById(R.id.img_stu);
    //选择随机头像
    final int[] random={0};
    mMStuImg.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    random[0]=new Random().nextInt(10);
    mMStuImg.setImageResource(images[random[0]]);
    }
    });
    mStuId = view.findViewById(R.id.stu_id);
    mMStuName = view.findViewById(R.id.stu_name);
    mMStuPhone = view.findViewById(R.id.stu_phone);
    mBtnAdd = view.findViewById(R.id.btn_add);
    mBtnAdd.setOnClickListener(v->{
    String editId=new String(mStuId.getText().toString());
    String editName=new String(mMStuName.getText().toString());
    String editPhone=new String(mMStuPhone.getText().toString());
    Student editStudent=new Student(random[0],editId,editName,editPhone);
    mStudentList.add(editStudent);
    mStuId.setText("");
    mMStuName.setText("");
    mMStuPhone.setText("");
    mStuAdapter.notifyDataSetChanged();
    });
    mRecyclerView = view.findViewById(R.id.recyclerview);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    mStuAdapter = new StuAdapter();
    mRecyclerView.setAdapter(mStuAdapter);
    for (int i = 0; i < 6; i++) {
    Student student = new Student(i, ids[i], names[i], phones[i]);
    mStudentList.add(student);
    }
    return view;
    }
    private class StuAdapter extends RecyclerView.Adapter<StuHolder>{
    @NonNull
    @Override
    public StuHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view=View.inflate(getContext(),R.layout.item,null);
    return new StuHolder(view);
    }
    @Override
    public void onBindViewHolder(@NonNull StuHolder holder, int position) {
    Student student = mStudentList.get(position);
    holder.mImgView.setImageResource(images[student.getImgId()]);
    holder.mItemId.setText(student.getId());
    holder.mMItemName.setText(student.getName());
    holder.mMItemPhone.setText(student.getPhone());
    holder.mBtnDel.setOnClickListener(v-> {
    mStudentList.remove(position);
    mStuAdapter.notifyDataSetChanged();
    });
    }
    @Override
    public int getItemCount() {
    return mStudentList.size();
    }
    }
    public class StuHolder extends RecyclerView.ViewHolder{
    private final TextView mItemId;
    private final TextView mMItemName;
    private final TextView mMItemPhone;
    private final Button mBtnDel;
    private final ImageView mImgView;
    public StuHolder(@NonNull View itemView) {
    super(itemView);
    mImgView = itemView.findViewById(R.id.item_img);
    mItemId = (TextView)itemView.findViewById(R.id.item_id);
    mMItemName = (TextView)itemView.findViewById(R.id.item_name);
    mMItemPhone = (TextView)itemView.findViewById(R.id.item_phone);
    mBtnDel = (Button)itemView.findViewById(R.id.btn_del);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    //fragment_stu.xml
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical"
    >
    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    >
    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="105dp"
    android:orientation="vertical">
    <EditText
    android:id="@+id/stu_id"
    android:layout_width="wrap_content"
    android:layout_height="35dp"
    android:ems="10"
    android:hint="请输入学号"
    android:inputType="text"
    android:textSize="13dp"/>
    <EditText
    android:id="@+id/stu_name"
    android:layout_width="wrap_content"
    android:layout_height="35dp"
    android:ems="10"
    android:hint="请输入姓名"
    android:inputType="text"
    android:textSize="13dp"/>
    <EditText
    android:id="@+id/stu_phone"
    android:layout_width="wrap_content"
    android:layout_height="35dp"
    android:ems="10"
    android:hint="请输入电话"
    android:inputType="text"
    android:textSize="13dp"/>
    </LinearLayout>
    <!-- TODO: Update blank fragment layout -->
    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="105dp"
    android:orientation="vertical">
    <ImageView
    android:id="@+id/img_stu"
    android:layout_width="155dp"
    android:layout_height="105dp"
    android:src="@drawable/aa" />
    </LinearLayout>
    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="80dp"
    android:orientation="vertical"
    android:gravity="center">
    <Button
    android:id="@+id/btn_add"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="12dp"
    android:text="添加"/>
    </LinearLayout>
    </LinearLayout>
    <androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
    </LinearLayout>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    //item.xml
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <ImageView
    android:id="@+id/item_img"
    android:layout_width="75dp"
    android:layout_height="75dp"
    android:src="@drawable/aa" />
    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="75dp"
    android:orientation="vertical">
    <TextView
    android:id="@+id/item_id"
    android:layout_width="250dp"
    android:layout_height="25dp"
    android:text="TextView"
    android:textSize="15dp" />
    <TextView
    android:id="@+id/item_name"
    android:layout_width="250dp"
    android:layout_height="25dp"
    android:text="TextView"
    android:textSize="15dp" />
    <TextView
    android:id="@+id/item_phone"
    android:layout_width="250dp"
    android:layout_height="25dp"
    android:text="TextView"
    android:textSize="15dp" />
    </LinearLayout>
    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="75dp"
    android:orientation="vertical"
    android:gravity="center"
    >
    <Button
    android:id="@+id/btn_del"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="删除" />
    </LinearLayout>
    </LinearLayout>
  2. 可以删除列表项,添加列表项;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //删除
    holder.mBtnDel.setOnClickListener(v-> {
    mStudentList.remove(position);
    mStuAdapter.notifyDataSetChanged();
    });
    //添加
    mBtnAdd.setOnClickListener(v->{
    String editId=new String(mStuId.getText().toString());
    String editName=new String(mMStuName.getText().toString());
    String editPhone=new String(mMStuPhone.getText().toString());
    Student editStudent=new Student(random[0],editId,editName,editPhone);
    mStudentList.add(editStudent);
    mStuId.setText("");
    mMStuName.setText("");
    mMStuPhone.setText("");
    mStuAdapter.notifyDataSetChanged();
    });
  3. 可以从列表项拨打对应的电话号码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //方法onBindViewHolder
    //拨打电话
    holder.mMItemPhone.setOnClickListener(view -> {
    String phone=mStudentList.get(position).getPhone();
    Intent intent = new Intent();
    intent.setAction("android.intent.action.DIAL");
    Uri uri = Uri.parse("tel:" + phone);
    intent.setData(uri);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
    });

实验六

  1. 将班级名单以SQLite的方式保存在本地,每个列表项包含:学号和姓名,电话;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    //activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/fragment_container"
    tools:context=".MainActivity">
    </androidx.constraintlayout.widget.ConstraintLayout>
    //MainActivity
    public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    String s = getFilesDir() + "/student.db";
    SQLiteDatabase database = openOrCreateDatabase(s, MODE_PRIVATE, null);
    if (database != null) {
    Toast.makeText(this, "数据库创建成功", Toast.LENGTH_SHORT).show();
    } else {
    Toast.makeText(this, "数据库创建失败", Toast.LENGTH_SHORT).show();
    }
    FragmentManager fm=getSupportFragmentManager();
    StuFragment fragment=(StuFragment)fm.findFragmentById(R.id.fragment_container);
    if(fragment==null){
    fragment=new StuFragment();
    fm.beginTransaction().add(R.id.fragment_container,fragment).commit();
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    //fragment_stu.xml
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/frameLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".StuFragment">
    <EditText
    android:id="@+id/editName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="72dp"
    android:layout_marginTop="60dp"
    android:ems="10"
    android:inputType="text"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    <EditText
    android:id="@+id/editPhone"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="72dp"
    android:layout_marginTop="128dp"
    android:ems="10"
    android:inputType="text"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    <TextView
    android:id="@+id/textView4"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="28dp"
    android:layout_marginTop="144dp"
    android:text="电话"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    <TextView
    android:id="@+id/textView3"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="28dp"
    android:layout_marginTop="88dp"
    android:text="姓名"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    <TextView
    android:id="@+id/textView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="28dp"
    android:layout_marginTop="28dp"
    android:text="学号"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    <EditText
    android:id="@+id/editId"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="72dp"
    android:layout_marginTop="4dp"
    android:ems="10"
    android:inputType="text"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    <Button
    android:id="@+id/btnFind"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="292dp"
    android:layout_marginTop="40dp"
    android:text="查询"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    <Button
    android:id="@+id/btnAdd"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="292dp"
    android:layout_marginTop="108dp"
    android:text="添加"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    <androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerview"
    android:layout_width="373dp"
    android:layout_height="513dp"
    android:layout_marginStart="16dp"
    android:layout_marginTop="180dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
    //StuFragment
    public class StuFragment extends Fragment {
    private List<Student> mStudentList=new ArrayList<>();
    private TextView mMEditId;
    private TextView mMEditName;
    private TextView mMMEditPhone;
    private Button mBtnAdd;
    private Button mMBtnFind;
    private RecyclerView mRecyclerView;
    private StuAdapter mStuAdapter;
    private MyOpenHelper mMyOpenHelper;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    View view=inflater.inflate(R.layout.fragment_stu, container, false);
    mMyOpenHelper = MyOpenHelper.getInstance(getActivity());
    mMyOpenHelper.openWriteLink();
    mMyOpenHelper.openReadLink();
    mMEditId = view.findViewById(R.id.editId);
    mMEditName = view.findViewById(R.id.editName);
    mMMEditPhone = view.findViewById(R.id.editPhone);
    mBtnAdd = view.findViewById(R.id.btnAdd);
    mBtnAdd.setOnClickListener(v->{
    String id=mMEditId.getText().toString();
    String name=mMEditName.getText().toString();
    String phone=mMMEditPhone.getText().toString();
    mMyOpenHelper.insert(new Student(id,name,phone));
    mStudentList=mMyOpenHelper.queryAll();
    mStuAdapter.notifyDataSetChanged();
    mMEditId.setText("");
    mMEditName.setText("");
    mMMEditPhone.setText("");

    });
    mMBtnFind = view.findViewById(R.id.btnFind);
    mRecyclerView = view.findViewById(R.id.recyclerview);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    mStudentList=mMyOpenHelper.queryAll();
    mStuAdapter = new StuAdapter();
    mRecyclerView.setAdapter(mStuAdapter);
    return view;
    }
    private class StuAdapter extends RecyclerView.Adapter<StuHolder>{
    @NonNull
    @Override
    public StuHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view=LayoutInflater.from(getActivity()).inflate(R.layout.item,parent,false);
    return new StuHolder(view);
    }
    @Override
    public void onBindViewHolder(@NonNull StuHolder holder, int position) {
    Student student=mStudentList.get(position);
    holder.mItemId.setText(student.getId());
    holder.mMItemName.setText(student.getName());
    holder.mMItemPhone.setText(student.getPhone());
    holder.mMBtnDel.setOnClickListener(v->{
    mMyOpenHelper.delete(student.getId());
    mStudentList=mMyOpenHelper.queryAll();
    mStuAdapter.notifyDataSetChanged();
    });
    }
    @Override
    public int getItemCount() {
    return mStudentList.size();
    }
    }
    public class StuHolder extends RecyclerView.ViewHolder{
    private final TextView mItemId;
    private final TextView mMItemName;
    private final TextView mMItemPhone;
    private final Button mMBtnUpdate;
    private final Button mMBtnDel;
    public StuHolder(@NonNull View itemView) {
    super(itemView);
    mItemId = itemView.findViewById(R.id.itemId);
    mMItemName = itemView.findViewById(R.id.itemName);
    mMItemPhone = itemView.findViewById(R.id.itemPhone);
    mMBtnUpdate = itemView.findViewById(R.id.btnUpdate);
    mMBtnDel = itemView.findViewById(R.id.btnDel);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    //MyOpenHelper
    public class MyOpenHelper extends SQLiteOpenHelper {
    public static final String DB_NAME = "student.db";
    public static final String TABLE_NAME = "stu_info";
    public static final int DB_VERSION = 1;
    private static MyOpenHelper instance;
    private SQLiteDatabase mRDB = null;
    private SQLiteDatabase mWDB = null;
    public static MyOpenHelper getInstance(Context context) {
    if (instance == null) {
    instance = new MyOpenHelper(context);
    }
    return instance;
    }
    public MyOpenHelper(@Nullable Context context) {
    super(context, DB_NAME, null, DB_VERSION);
    }
    public MyOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
    super(context, name, factory, version);
    }
    public SQLiteDatabase openReadLink() {
    if (mRDB == null || !mRDB.isOpen()) {
    mRDB = instance.getReadableDatabase();
    }
    return mRDB;
    }
    public SQLiteDatabase openWriteLink() {
    if (mWDB == null || !mWDB.isOpen()) {
    mWDB = instance.getWritableDatabase();
    }
    return mWDB;
    }
    public void closeLink() {
    if (mRDB != null && mRDB.isOpen()) {
    mRDB.close();
    mRDB = null;
    }
    if (mWDB != null && mWDB.isOpen()) {
    mWDB.close();
    mWDB = null;
    }
    }
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
    String sql = "create table stu_info(id primary key ,name ,phone )";
    sqLiteDatabase.execSQL(sql);
    }
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
    public int insert(Student student){
    ContentValues values = new ContentValues();
    values.put("id",student.getId());
    values.put("name",student.getName());
    values.put("phone",student.getPhone());
    return (int) mWDB.insert(TABLE_NAME,null,values);
    }
    public List<Student> queryAll(){
    List<Student> studentList=new ArrayList<>();
    Cursor cursor = mRDB.query(TABLE_NAME, null, null, null, null, null, null);
    while (cursor.moveToNext()){
    String id=cursor.getString(0);
    String name=cursor.getString(1);
    String phone=cursor.getString(2);
    Student student=new Student(id,name,phone);
    studentList.add(student);
    }
    cursor.close();
    return studentList;
    }
    public int delete(String id){
    return mWDB.delete(TABLE_NAME,"id=?",new String[]{id});
    }
    }
  2. 可以删除列表项,添加列表项(数据更新到SQLite数据库);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //添加
    mBtnAdd.setOnClickListener(v->{
    String id=mMEditId.getText().toString();
    String name=mMEditName.getText().toString();
    String phone=mMMEditPhone.getText().toString();
    mMyOpenHelper.insert(new Student(id,name,phone));
    mStudentList=mMyOpenHelper.queryAll();
    mStuAdapter.notifyDataSetChanged();
    mMEditId.setText("");
    mMEditName.setText("");
    mMMEditPhone.setText("");
    });
    //删除
    holder.mMBtnDel.setOnClickListener(v->{
    mMyOpenHelper.delete(student.getId());
    mStudentList=mMyOpenHelper.queryAll();
    mStuAdapter.notifyDataSetChanged();
    });
  3. 可以读取联系人(使用内容提供者),将联系人的名字和电话号码读入到名单列表并插入数据库;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //StuFragment  
    //检查获取通讯录权限
    private void checkPermission() {
    //判断是否有权限
    if (ContextCompat.checkSelfPermission(getContext(), android.Manifest.permission.READ_CONTACTS)
    != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.READ_CONTACTS}, 201);
    }
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    checkPermission();
    }
    mMBtnFind.setOnClickListener(v -> {
    List<Student> contacts = ContactUtils.getContacts(getActivity());
    for (int i = 0; i < contacts.size(); i++) {
    Student student = contacts.get(i);
    mMyOpenHelper.insert(student);
    }
    mStudentList = mMyOpenHelper.queryAll();
    mStuAdapter.notifyDataSetChanged();
    });
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class ContactUtils {
    public static final String ID = ContactsContract.CommonDataKinds.Phone.CONTACT_ID;//联系人ID
    public static final String NUM = ContactsContract.CommonDataKinds.Phone.NUMBER;//电话号码
    public static final String NAME = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME;//姓名
    public static final Uri phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
    public static List<Student> getContacts(Context context) {
    List<Student> students = new ArrayList<>();
    ContentResolver cr = context.getContentResolver();
    Cursor cursor = cr.query(phoneUri, new String[]{ID, NUM, NAME}, null, null, null);
    while (cursor.moveToNext()) {
    //随机生成id
    String id = String.valueOf((int) (Math.random() * 100000));
    @SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex(NAME));
    @SuppressLint("Range") String num = cursor.getString(cursor.getColumnIndex(NUM));
    Student student = new Student(id, name, num);
    students.add(student);
    }
    cursor.close();
    return students;
    }
    }

  4. 支持在新的页面中学号和姓名,电话。

    1
    2
    3
    4
    5
    6
    7
    holder.mMBtnUpdate.setOnClickListener(v->{
    Intent intent=new Intent(getActivity(),NewActivity.class);
    intent.putExtra("id",student.getId());
    intent.putExtra("name",student.getName());
    intent.putExtra("phone",student.getPhone());
    startActivity(intent);
    });
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    //activity_new.xml
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".NewActivity">
    <TextView
    android:id="@+id/newName"
    android:layout_width="74dp"
    android:layout_height="40dp"
    android:layout_marginStart="60dp"
    android:layout_marginTop="144dp"
    android:text="TextView"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    <TextView
    android:id="@+id/newId"
    android:layout_width="74dp"
    android:layout_height="40dp"
    android:layout_marginStart="60dp"
    android:layout_marginTop="72dp"
    android:text="TextView"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    <TextView
    android:id="@+id/newPhone"
    android:layout_width="74dp"
    android:layout_height="40dp"
    android:layout_marginStart="64dp"
    android:layout_marginTop="220dp"
    android:text="TextView"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    <Button
    android:id="@+id/btnBack"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="返回"
    tools:layout_editor_absoluteX="204dp"
    tools:layout_editor_absoluteY="132dp" />
    </androidx.constraintlayout.widget.ConstraintLayout>
    //NewActivity
    public class NewActivity extends AppCompatActivity {
    private TextView mNewId;
    private TextView mNewName;
    private TextView mNewPhone;
    private Button mBtnBack;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_new);
    mNewId = findViewById(R.id.newId);
    mNewName = findViewById(R.id.newName);
    mNewPhone = findViewById(R.id.newPhone);
    mBtnBack = findViewById(R.id.btnBack);
    Intent intent = getIntent();
    String id=intent.getStringExtra("id");
    String name=intent.getStringExtra("name");
    String phone=intent.getStringExtra("phone");
    mNewId.setText(id);
    mNewName.setText(name);
    mNewPhone.setText(phone);
    mBtnBack.setOnClickListener(v-> {
    finish();
    });
    }
    }

实验七

  1. 在SQLite版本的班级名单基础上实现以下功能,设计修改列表项的Fragment页面,支持调用相机和读取图库中照片;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //AndroidManifest.xml
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="@string/file_provider"
    android:grantUriPermissions="true"
    android:exported="false">
    <meta-data
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/file_paths"/>
    </provider>
    //xml文件夹下file_paths.xml
    <paths>
    <external-path
    name="external_files"
    path="." />
    </paths>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    //StuFragment
    //为头像设置点击事件,选择头像是从拍照中获取还是从相册中获取
    mImageView.setOnClickListener(v -> {
    final String[] items=new String[]{"拍照","相册"};
    AlertDialog.Builder builder=new AlertDialog.Builder(getContext());
    builder.setTitle("请选择头像获取方式");
    builder.setItems(items,new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
    switch (which) {
    case 0:
    takePhoto();
    break;
    case 1:
    choosePhoto();
    break;
    }
    }
    private void choosePhoto() {
    Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    register.launch(intent);
    }
    private void takePhoto() {
    File outputImage = new File(getContext().getExternalCacheDir(), "output_image.jpg");
    if (outputImage.exists()) outputImage.delete();
    String string = getString(R.string.file_provider);
    imageUri = FileProvider.getUriForFile(getContext(), string, outputImage);
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
    register.launch(intent);
    }
    });
    builder.create().show();
    });
  2. 将拍摄的或选中的照片设置为该名单对应的头像,并在列表中更新显示;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    //StuFragment
    //获取回传的图片设置成头像
    register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
    if (result.getResultCode() == RESULT_OK) {
    Intent intent = result.getData();
    //判断是拍照还是选择照片
    try {
    if (intent != null && intent.getData() != null) { //选择照片
    Uri uri = intent.getData();
    Bitmap bitmap = BitmapFactory.decodeStream(getContext().getContentResolver()
    .openInputStream(uri));
    mImageView.setImageBitmap(bitmap);
    image = bitmap2Bytes(bitmap);
    } else {//选择拍照
    Bitmap bitmap = BitmapFactory.decodeStream(
    getContext().getContentResolver().openInputStream(imageUri));
    mImageView.setImageBitmap(bitmap);
    image = bitmap2Bytes(bitmap);
    }
    } catch (FileNotFoundException e) {
    throw new RuntimeException(e);
    }
    }
    });
    //为添加按钮设置点击事件,将输入的学生信息保存到数据库后展示到列表中
    mBtnAdd.setOnClickListener(v -> {
    String id = mEditId.getText().toString();
    String name = mEditName.getText().toString();
    String phone = mEditPhone.getText().toString();
    Student student1 = new Student(id, name, phone,image);
    mMyOpenHelper.insert(student1);
    mStudentList = mMyOpenHelper.queryAll();
    mStuAdapter.notifyDataSetChanged();
    mEditId.setText("");
    mEditName.setText("");
    mEditPhone.setText("");
    });
    //StuAdapter
    //将bytes转换为Bitmap,将数组中的图片二进制转换成图片
    private Bitmap bytes2Bitmap(byte[] bytes) {
    return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    }
    @Override
    public void onBindViewHolder(@NonNull StuHolder holder, int position) {
    Student student = mStudentList.get(position);

    holder.mItemId.setText(student.getId());
    holder.mItemName.setText(student.getName());
    holder.mItemPhone.setText(student.getPhone());
    byte[] byteImage = student.getImage();
    Bitmap bitmap = bytes2Bitmap(byteImage);
    holder.mImageView.setImageBitmap(bitmap);

    holder.mBtnDel.setOnClickListener(v -> {
    mMyOpenHelper.delete(student.getId());
    mStudentList = mMyOpenHelper.queryAll();
    mStuAdapter.notifyDataSetChanged();
    });
  3. 将名单和图片文件的对应关系保存在SQLite数据库中,下次重启应用的时候可以看到名单的头像;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public long insert(Student student) {
    ContentValues values = new ContentValues();
    values.put("id", student.getId());
    values.put("name", student.getName());
    values.put("phone", student.getPhone());
    values.put("image", student.getImage());
    return mWDB.insert(TABLE_NAME, null, values);
    }
    public int delete(String id) {
    return mWDB.delete(TABLE_NAME, "id=?", new String[]{id});
    }

    public int update(Student student) {
    ContentValues values = new ContentValues();
    values.put("name", student.getName());
    values.put("phone", student.getPhone());
    return mWDB.update(TABLE_NAME, values, "id=?", new String[]{student.getId()});
    }
    public List<Student> queryAll() {
    List<Student> list = new ArrayList<>();
    Cursor cursor = mRDB.query(TABLE_NAME, null, null, null, null, null, null);
    while (cursor.moveToNext()) {
    String id = cursor.getString(0);
    String name = cursor.getString(1);
    String phone = cursor.getString(2);
    byte[] image = cursor.getBlob(3);
    Student student = new Student(id, name, phone,image);
    list.add(student);
    }
    cursor.close();
    return list;
    }

###实验八