第 2 章 Android与MVC设计模式

第 2 章 Android与MVC设计模式

新葡京娱乐场:林育卉在文末还写着“赶你羚羊啦”、“老娘忍很久了”、“趁清明不会早点去死”。

本章中,我们将升级GeoQuiz应用,提供更多的地理知识测试题目,如图2-1所示。

图2-1 更多测试题目

为实现目标,需要为GeoQuiz项目新增一个Question类。该类的一个实例代表一道题目。

然后再创建一个Question数组对象交由QuizActivity管理。

2.1 创建新类

在项目工具窗口中,右键单击com.bignerdranch.android.geoquiz类包,选择New → Java Class菜单项。如图2-2所示,类名处填入Question,然后单击OK按钮。

{%}

图2-2 创建Question

在Question.java中,新增两个成员变量和一个构造方法,如代码清单2-1所示。

代码清单2-1 Question类中的新增代码(Question.java)

public class Question {

    private int mTextResId;
    private boolean mAnswerTrue;

    public Question(int textResId, boolean answerTrue) {
        mTextResId = textResId;
        mAnswerTrue = answerTrue;
    }
}

Question类中封装的数据有两部分:问题文本和问题答案(true或false)。

为什么mTextResIdint类型,而不是String类型呢?变量mTextResId用来保存地理知识问题字符串的资源ID。资源ID总是int类型,所以这里设置它为int类型。稍后会处理需要用到的问题字符串资源。

新增的两个变量需要getter方法与setter方法。为避免手工输入,可设置由Android Studio自动生成。

生成getter方法与setter方法

首先,配置Android Studio识别成员变量的m前缀。

打开Android Studio首选项对话框(Mac用户选择Android Studio菜单,Windows用户选择File → Settings菜单)。依次展开Editor和Code Style选项,在Java选项下选择Code Generation选项页。

在Naming表单的Field一行中,添加m作为前缀,如图2-3所示。然后添加s作为Static field的前缀。(GeoQuiz项目不会用到s前缀,但之后的项目会用到。)

{%}

图2-3 设置Java代码风格首选项

单击OK按钮完成。

刚才设置的前缀有何作用?那就是,需要Android Studio为mTextResId生成获取方法时,它生成的是getTextResId()而不是getMTextResId();而在为mAnswerTrue生成获取方法时,生成的是isAnswerTrue()而不是isMAnswerTrue()

回到Question.java中,右击构造方法后方区域,选择Generate... → Getter and Setter菜单项。选择mTextResIdmAnswerTrue,为每个变量都生成getter方法与setter方法。单击OK按钮,Android Studio随即生成了getter与setter共4个方法的代码,如代码清单2-2所示。

代码清单2-2 生成getter方法与setter方法(Question.java)

public class Question {

    private int mTextResId;
    private boolean mAnswerTrue;
    ...
    public int getTextResId() {
        return mTextResId;
    }

    public void setTextResId(int textResId) {
        mTextResId = textResId;
    }

    public boolean isAnswerTrue() {
        return mAnswerTrue;
    }

    public void setAnswerTrue(boolean answerTrue) {
        mAnswerTrue = answerTrue;
    }

}

这样,Question类就完成了。稍后,我们会修改QuizActivity类来配合Question类使用。现在,先整体了解一下GeoQuiz应用,看看各个类是如何协同工作的。

我们使用QuizActivity创建Question数组对象,继而通过与TextView以及三个Button的交互,在屏幕上显示地理知识问题,并根据用户的回答作出反馈,如图2-4所示。

{%}

图2-4 GeoQuiz应用对象图解

2.2 Android与MVC设计模式

如图2-4所示,应用对象分为模型、视图和控制器三类。Android应用基于模型-视图-控制器(Model-View-Controller,MVC)的架构模式进行设计。MVC设计模式表明,应用的任何对象,归根结底都属于模型对象视图对象以及控制器对象中的一种。

  • 模型对象存储着应用的数据和业务逻辑。模型类通常用来映射与应用相关的一些事物,如用户、商店里的商品、服务器上的图片或者一段电视节目,抑或GeoQuiz应用里的地理知识问题。模型对象不关心用户界面,它为存储和管理应用数据而生。

    Android应用里,模型类通常就是我们创建的定制类。应用的全部模型对象组成了模型层

    GeoQuiz应用的模型层由Question类组成。

  • 视图对象知道如何在屏幕上绘制自己,以及如何响应用户的输入,如触摸动作等。一个简单的经验法则是,凡是能够在屏幕上看见的对象,就是视图对象。

    Android自带很多可配置的视图类。当然,也可以定制开发其他视图类。应用的全部视图对象组成了视图层

    GeoQuiz应用的视图层是由activity_quiz.xml文件中定义的各类组件构成的。

  • 控制器对象含有应用的逻辑单元,是视图对象与模型对象的联系纽带。控制器对象响应视图对象触发的各类事件,此外还管理着模型对象与视图层间的数据流动。

    在Android的世界里,控制器通常是ActivityFragmentService的子类(第7章和第28章将分别介绍fragment和service的概念)。

    GeoQuiz应用的控制器层目前仅由QuizActivity类组成。

图2-5展示了在响应诸如单击按钮等用户事件时,对象间的交互控制数据流。注意,模型对象与视图对象不直接交互。控制器作为它们之间的联系纽带,接收对象发送的消息,然后向其他对象发送操作指令。

{%}

图2-5 MVC数据控制流与用户交互

使用MVC设计模式的好处

随着应用功能的持续扩展,应用往往会变得过于复杂而让人难以理解。以Java类组织代码有助于从整体视角设计和理解应用。这样,我们就可以按类而不是按变量和方法思考设计开发问题。

同样,把Java类以模型层、视图层和控制器层进行分类组织,也有助于我们设计和理解Android应用。这样,我们就可以按层而非一个个类来考虑设计开发了。

GeoQuiz应用虽不复杂,但以MVC分层模式设计它的好处还是显而易见的。接下来,我们会升级GeoQuiz应用的视图层,为它添加一个NEXT按钮。你会发现,添加NEXT按钮时,可以不用考虑刚才创建的Question类。

MVC设计模式还便于复用类。相比功能多而全的类,功能单一的专用类更有利于代码复用。

举例来说,模型类Question与用作显示问题的组件毫无代码逻辑关联。这样,就很容易在应用里按需使用Question类。假设现在想显示包含所有地理知识问题的列表,很简单,直接利用Question对象逐条显示就可以了。

2.3 更新视图层

了解了MVC设计模式后,现在来更新GeoQuiz应用的视图层,为其添加一个NEXT按钮。

在Android的世界里,视图对象通常由XML布局文件生成。GeoQuiz应用唯一的布局定义在activity_quiz.xml文件中。布局定义文件需要更新的地方如图2-6所示。(注意,为节约版面,不变的组件属性就不再列出了。)

{%}

图2-6 新增按钮

应用视图层所需的改动如下。

  • 删除TextViewandroid:text属性定义。再也不用硬编码地理知识问题了。

  • TextView新增android:id属性。TextView组件需要资源ID,以便在QuizActivity代码中为它设置要显示的文字。

  • 以根LinearLayout为父组件,新增一个Button组件。

回到activity_quiz.xml文件中,完成XML文件的相应修改,如代码清单2-3所示。

代码清单2-3 新增按钮以及对文本视图的调整(activity_quiz.xml)

<LinearLayout... >

    <TextView
        android:id="@+id/question_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="24dp"
        android:text="@string/question_text" />

    <LinearLayout ... >
        ...
    </LinearLayout>

    <Button
        android:id="@+id/next_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/next_button" />

</LinearLayout>

保存activity_quiz.xml文件。这时,会看到一个错误提示,提醒缺少字符串资源。

回到res/values/strings.xml文件。重命名question_text,添加新按钮所需的字符串资源定义,如代码清单2-4所示。

代码清单2-4 更新字符串资源定义(strings.xml)

<string name="app_name">GeoQuiz</string>
<string name="question_text"> Canberra is the capital of Australia.</string>
<string name="question_australia">Canberra is the capital of Australia.</string>
<string name="true_button">True</string>
<string name="false_button">False</string>
<string name="next_button">Next</string>
<string name="correct_toast">Correct!</string>

既然已打开strings.xml文件,那就继续添加其他地理知识问题的字符串,结果如代码清单2-5所示。

代码清单2-5 新增问题字符串(strings.xml)

<string name="question_australia">Canberra is the capital of Australia.</string>
<string name="question_oceans">The Pacific Ocean is larger than
  the Atlantic Ocean.</string>
<string name="question_mideast">The Suez Canal connects the Red Sea
  and the Indian Ocean.</string>
<string name="question_africa">The source of the Nile River is in Egypt.</string>
<string name="question_americas">The Amazon River is the longest river
  in the Americas.</string>
<string name="question_asia">Lake Baikal is the world\'s oldest and deepest
  freshwater lake.</string>
...

注意最后一个字符串定义中的\'。为得到符号',这里使用了转义字符。在字符串资源定义中,也可使用其他常见的转义字符,比如\n是指新行符。

保存修改过的文件,然后回到activity_quiz.xml文件中,在图形布局工具里预览修改后的布局文件。

至此,GeoQuiz应用视图层的更新就全部完成了。为让GeoQuiz应用跑起来,接下来要更新控制层的QuizActivity类。

2.4 更新控制器层

在上一章,应用控制器层的QuizActivity类的处理逻辑很简单:显示定义在activity_quiz.xml文件中的布局对象,为两个按钮设置监听器,实现响应用户点击事件并创建toast消息。

既然现在有更多的地理知识问题可以检索与展示,QuizActivity类就需要更多的处理逻辑来让GeoQuiz应用的模型层与视图层协作。

打开QuizActivity.java文件,添加TextView和新Button变量。另外,再创建一个Question对象数组以及一个该数组的索引变量,如代码清单2-6所示。

代码清单2-6 增加按钮变量及Question对象数组(QuizActivity.java)

public class QuizActivity extends AppCompatActivity {

    private Button mTrueButton;
    private Button mFalseButton;
    private Button mNextButton;
    private TextView mQuestionTextView;

    private Question[] mQuestionBank = new Question[] {
        new Question(R.string.question_australia, true),
        new Question(R.string.question_oceans, true),
        new Question(R.string.question_mideast, false),
        new Question(R.string.question_africa, false),
        new Question(R.string.question_americas, true),
        new Question(R.string.question_asia, true)
    };

    private int mCurrentIndex = 0;
    ...

这里,我们通过多次调用Question类的构造方法,创建了Question对象数组。

(在更为复杂的项目里,这类数组的创建和存储会单独处理。在后续应用开发中,会介绍更好的模型数据存储管理方式。现在,简单起见,我们选择在控制器层代码中创建数组。)

要在屏幕上显示一系列地理知识问题,可以使用mQuestionBank数组、mCurrentIndex变量以及Question对象的存取方法。

首先,引用TextView,并将其文本内容设置为当前数组索引所指向的地理知识问题,如代码清单2-7所示。

代码清单2-7 使用TextView(QuizActivity.java)

public class QuizActivity extends AppCompatActivity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_quiz);

        mQuestionTextView = (TextView) findViewById(R.id.question_text_view);
        int question = mQuestionBank[mCurrentIndex].getTextResId();
        mQuestionTextView.setText(question);

        mTrueButton = (Button) findViewById(R.id.true_button);
        ...
    }
}

保存所有文件,确保没有错误发生。然后运行GeoQuiz应用。可看到数组存储的第一个问题显示在TextView上了。

现在我们来处理NEXT按钮。首先引用NEXT按钮,然后为其设置监听器View.OnClickListener。该监听器的作用是:递增数组索引并相应地更新TextView的文本内容,如代码清单2-8所示。

代码清单2-8 使用新增的按钮(QuizActivity.java)

public class QuizActivity extends AppCompatActivity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mFalseButton.setOnClickListener(new View.OnClickListener() {
            ...
            }
        });

        mNextButton = (Button) findViewById(R.id.next_button);
        mNextButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
                int question = mQuestionBank[mCurrentIndex].getTextResId();
                mQuestionTextView.setText(question);
            }
        });
    }
}

可以看到,更新mQuestionTextView变量的相同代码分布在两个不同的地方。参照代码清单2-9,花点时间把公共代码放在单独的私有方法里,然后在mNextButton监听器里以及onCreate(Bundle)方法的末尾分别调用它,以初步设置activity视图中的文本。

代码清单2-9 使用updateQuestion()封装公共代码(QuizActivity.java)

public class QuizActivity extends AppCompatActivity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mQuestionTextView = (TextView) findViewById(R.id.question_text_view);
        int question = mQuestionBank[mCurrentIndex].getTextResId();
        mQuestionTextView.setText(question);
        ...
        mNextButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
                int question = mQuestionBank[mCurrentIndex].getTextResId();
                mQuestionTextView.setText(question);
                updateQuestion();
            }
        });

        updateQuestion();
    }

    private void updateQuestion() {
        int question = mQuestionBank[mCurrentIndex].getTextResId();
        mQuestionTextView.setText(question);
    }
}

运行GeoQuiz应用,验证新增的NEXT按钮。

一切正常的话,问题应该已经完美显示出来了。当前,GeoQuiz应用认为所有问题的答案都是true,下面着手修正这个逻辑错误。同样,为避免代码重复,我们将解决方案封装在一个私有方法里。

要添加到QuizActivity类的方法如下:

private void checkAnswer(boolean userPressedTrue)

该方法接受布尔类型的变量参数,判别用户单击了TRUE还是FALSE按钮。然后,将用户的答案同当前Question对象中的答案作比较。最后,判断答案正确与否,生成一个toast消息反馈给用户。

在QuizActivity.java文件中,添加checkAnswer(boolean)方法的实现代码,如代码清单2-10所示。

代码清单2-10 增加checkAnswer(boolean)方法(QuizActivity.java)

public class QuizActivity extends AppCompatActivity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
    }

    private void updateQuestion() {
        int question = mQuestionBank[mCurrentIndex].getTextResId();
        mQuestionTextView.setText(question);
    }

    private void checkAnswer(boolean userPressedTrue) {
        boolean answerIsTrue = mQuestionBank[mCurrentIndex].isAnswerTrue();

        int messageResId = 0;

        if (userPressedTrue == answerIsTrue) {
            messageResId = R.string.correct_toast;
        } else {
            messageResId = R.string.incorrect_toast;
        }

        Toast.makeText(this, messageResId, Toast.LENGTH_SHORT)
            .show();
    }
}

在按钮的监听器里,调用checkAnswer(boolean)方法,如代码清单2-11所示。

代码清单2-11 调用checkAnswer(boolean)方法(QuizActivity.java)

public class QuizActivity extends AppCompatActivity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mTrueButton = (Button) findViewById(R.id.true_button);
        mTrueButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(QuizActivity.this,
                               R.string.correct_toast,
                               Toast.LENGTH_SHORT).show();
                checkAnswer(true);
            }
        });

        mFalseButton = (Button) findViewById(R.id.false_button);
        mFalseButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(QuizActivity.this,
                               R.string.incorrect_toast,
                               Toast.LENGTH_SHORT).show();
                checkAnswer(false);
            }
        });
        ...
    }
    ...
}

GeoQuiz应用可以运行了,这次,我们要在物理设备上运行它。

2.5 在物理设备上运行应用

本节,我们学习如何设置系统、设备和应用,实现在硬件设备上运行GeoQuiz应用。

2.5.1 连接设备

首先,将设备连接到系统上。Mac系统应该会立即识别出所用设备。如果是Windows系统,则可能需要安装adb(Android Debug Bridge)驱动。如果Windows系统自身无法找到adb驱动,请到设备生产商的网站下载。

2.5.2 配置设备用于应用开发

要在设备上测试应用,首先应打开设备的USB调试模式。

开发者选项默认不可见。先选择“设定 → 关于平板/手机”项,再点击版本号(Build Number)7次启用它,然后回到“设定”项,选择“开发”项,找到并勾选“USB调试”选项。

不同版本设备的设置方法迥异。如果在设置过程中遇到问题,请访问以下网页求助:http://www.wking-china.com/xpjylc/tools/device.html

可打开Devices视图确认设备是否已识别。选择Android Studio底部的Android Monitor工具窗口,可快速打开Devices视图。设备连接成功的话,你会看到已连接设备的下拉列表。AVD以及硬件设备应该就列在其中,如图2-7所示。

{%}

图2-7 查看已连接设备

如果设备无法识别,请首先确认是否已打开设备和开发者选项。如果仍然无法解决,请访问Android开发网站http://www.wking-china.com/xpjylc/tools/device.html,或访问本书论坛求助。

再次运行GeoQuiz应用,Android Studio会询问是在虚拟设备还是物理设备上运行应用。选择物理设备并继续。稍等片刻,GeoQuiz应用应该已经在设备上运行了。

如果Android Studio没有给出选项,应用依然在虚拟设备上运行,请按以上步骤重新检查设备设置,并确保设备与系统已正确连接。然后,再检查运行配置是否有问题。要修改运行配置,请选择Android Studio窗口靠近顶部的app下拉列表,如图2-8所示。

{%}

图2-8 打开运行配置

选择Edit Configurations…打开运行配置编辑窗口,如图2-9所示。

{%}

图2-9 运行配置界面

选择窗口左边区域的app,确认已选中Deployment Target Options区域的Open Select Deployment Target Dialog选项。点击OK按钮并重新运行应用,现在应能看到可以运行应用的设备选项了。

2.6 添加图标资源

GeoQuiz应用现在已经可用了。如果NEXT按钮上能够显示向右的图标,用户界面看起来应该会更美。

本书随书文件中提供了这样的箭头图标(http://www.wking-china.com/xpjylc/solutions/AndroidProgramming3e.zip)。随书文件集合了Android Studio项目文件,每章对应一个项目文件。

下载随书文件,找到并打开02_MVC/GeoQuiz/app/src/main/res目录。在该目录下,可以看到drawable-mdpi、drawable-hdpi、drawable-xhdpi和drawable-xxhdpi四个目录。

四个目录各自的后缀名代表设备的像素密度。

  • mdpi:中等像素密度屏幕(约160dpi)。

  • hdpi:高像素密度屏幕(约240dpi)。

  • xhdpi:超高像素密度屏幕(约320dpi)。

  • xxhdpi:超超高像素密度屏幕(约480dpi)。

(另外还有ldpi和xxxhdpi这两个本书用不到的类别,因此,未包括在内。)

每个目录下,有两个图片文件:arrow_right.png和arrow_left.png。这些图片文件都是按照目录名对应的dpi定制的。

GeoQuiz项目中的所有图片资源都会随应用安装在设备里,Android操作系统知道如何为不同设备提供最佳匹配。注意,在为不同设备准备适配图片的同时,应用安装包容量也随之增大。当然,对于GeoQuiz这样的小项目,问题并不明显。

如果应用不包含设备对应的屏幕像素密度文件,在运行时,Android系统会自动找到可用的图片资源,针对该设备进行缩放适配。有了这种特性,就不一定要准备各种屏幕像素密度文件了。因此,为控制应用包的大小,可以只为主流设备准备分辨率较高的定制图片资源。至于那些不常见的低分辨率设备,让Android系统自动适配就好。

(第23章会介绍为屏幕像素密度定制图片的替代方案,另外,还会解释mipmap目录的用途。)

2.6.1 向项目中添加资源

接下来,需将图片文件添加到GeoQuiz项目资源中去。

首先,确认打开了Android Studio的Project视图(打开方式请参见图1-13)。展开GeoQuiz/ app/src/main/res目录,会看到名为mipmap-hdpi和mipmap-xhdpi这样的目录,如图2-10所示。

图2-10 还缺各类drawable目录

在随书文件中,选择并复制drawable-mdpi、drawable-hdpi、drawable-xhdpi和drawable-xxhdpi这四个目录。然后粘贴到Android Studio的app/src/main/res目录中。完成后,在Android Studio的项目工具窗口,就可以看到这四个目录,每个目录当中含有对应的arrow_left.png和arrow_right.png文件,如图2-11所示。

图2-11 drawable目录中的箭头图标文件

如果将项目工具窗口切换回Android视图,新增加的drawable图片资源会以图2-12所示的形式展示。

{%}

图2-12 drawable目录中的箭头图标文件汇总

向应用添加图片就这么简单。任何添加到res/drawable目录中、后缀名为.png、.jpg或者.gif的文件都会自动获得资源ID。(注意,文件名必须是小写字母且不能有任何空格符号。)

这些资源ID并不按照屏幕像素密度匹配,因此不需要在运行的时候确定设备的屏幕像素密度,只需在代码中引用这些资源ID就可以了。应用运行时,操作系统知道如何在特定的设备上显示匹配的图片。

Android资源系统是如何工作的?从第3章起,我们会深入学习这方面的相关知识。现在,能显示右箭头图标就可以了。

2.6.2 在XML文件中引用资源

在代码中,可以使用资源ID引用资源。如果想在布局定义中配置NEXT按钮显示箭头图标,又该如何在布局XML文件中引用资源呢?

很简单,只是语法稍有不同而已。打开activity_quiz.xml文件,为Button组件新增两个属性,如代码清单2-12所示。

代码清单2-12 为NEXT按钮增加图标(activity_quiz.xml)

<LinearLayout  ... >
    ...
    <LinearLayout  ... >
        ...
    </LinearLayout>

    <Button
        android:id="@+id/next_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/next_button"
        android:drawableRight="@drawable/arrow_right"
        android:drawablePadding="4dp" />

</LinearLayout>

在XML资源文件中,通过资源类型和资源名称,可引用其他资源。以@string/开头的定义是引用字符串资源。以@drawable/开头的定义是引用drawable资源。

自第3章起,我们还会学习更多资源命名以及res目录结构中其他资源的使用等相关知识。

运行GeoQuiz应用。新按钮很漂亮吧?测试一下,确认它仍然工作正常。

别高兴得太早,GeoQuiz应用有个bug。应用运行时,单击NEXT按钮显示下一道题,然后旋转设备。如果是模拟器,请点击浮动工具栏上的左旋或右旋按钮,如图2-13所示。

{%}

图2-13 控制旋转

发现没有,设备旋转后,应用又显示了第一道测试题。怎么回事?如何修正?

要解决此类问题,需了解activity生命周期的概念。第3章会专门介绍。

2.7 挑战练习:为TextView添加监听器

NEXT按钮不错,但如果用户单击应用的TextView文字区域(地理知识问题),也可以跳转到下一道题,用户体验会更好。

提示 TextView也是View的子类,因此和Button一样,可为TextView设置View.OnClickListener监听器。

2.8 挑战练习:添加后退按钮

为GeoQuiz应用新增后退按钮(PREV),用户单击时,可以显示上一道测试题目。完成后的用户界面应如图2-14所示。

图2-14 添加了后退按钮的用户界面

这是个很棒的练习,需回顾本章和上一章的内容才能完成。

2.9 挑战练习:从按钮到图标按钮

如果前进与后退按钮上只显示指示图标,用户界面更清爽,如图2-15所示。

图2-15 只显示图标的按钮

要完成此练习,需将普通的Button组件替换成ImageButton组件。

ImageButton组件继承自ImageViewButton组件则继承自TextViewImageButtonButtonView间的继承关系如图2-16所示。

{%}

图2-16 ImageButtonButtonView间的继承关系

如以下代码所示,以ImageButton组件替换Button组件,删除NEXT按钮的text以及drawable属性定义,并添加ImageView属性:

<Button ImageButton
  android:id="@+id/next_button"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="@string/next_button"
  android:drawableRight="@drawable/arrow_right"
  android:drawablePadding="4dp"
  android:src="@drawable/arrow_right"
  />

当然,为了使用ImageButton,还要调整QuizActivity类的代码。

Button组件替换成ImageButton后,Android Studio会警告说找不到android:contentDescription属性定义。该属性能为视力障碍用户提供方便。在为其设置文字属性值后,如果设备的可访问性选项作了相应设置,那么在用户点击图形按钮时,设备便会读出属性值的内容。

最后,为每个ImageButton都添加上android:contentDescription属性定义。

目录

  • 版权声明
  • 献词
  • 致谢
  • 如何学习Android开发
  • 开发必备工具
  • 第 1 章 Android开发初体验
  • 第 2 章 Android与MVC设计模式
  • 第 3 章 activity的生命周期
  • 第 4 章 Android应用的调试
  • 第 5 章 第二个activity
  • 第 6 章 Android SDK版本与兼容
  • 第 7 章 UI fragment与fragment管理器
  • 第 8 章 使用RecyclerView显示列表
  • 第 9 章 使用布局与组件创建用户界面
  • 第 10 章 使用fragment argument
  • 第 11 章 使用ViewPager
  • 第 12 章 对话框
  • 第 13 章 工具栏
  • 第 14 章 SQLite数据库
  • 第 15 章 隐式intent
  • 第 16 章 使用intent拍照
  • 第 17 章 双版面主从用户界面
  • 第 18 章 应用本地化
  • 第 19 章 Android辅助功能
  • 第 20 章 数据绑定与MVVM
  • 第 21 章 音频播放与单元测试
  • 第 22 章 样式与主题
  • 第 23 章 XML drawable
  • 第 24 章 深入学习intent和任务
  • 第 25 章 HTTP与后台任务
  • 第 26 章 Looper、Handler和HandlerThread
  • 第 27 章 搜索
  • 第 28 章 后台服务
  • 第 29 章 broadcast intent
  • 第 30 章 网页浏览
  • 第 31 章 定制视图与触摸事件
  • 第 32 章 属性动画
  • 第 33 章 地理位置和Play服务
  • 第 34 章 使用地图
  • 第 35 章 material design
  • 第 36 章 编后语
合作: 博彩评级 赌二八杠 新葡京娱乐场