一、Android开发初体验 开发一个应用,名叫GeoQuiz,它能给出一道道地理知识问题,用户点击TRUE或FALSE来回答屏幕上的问题,GeoQuiz会及时给出反馈,应用界面如下图所示。
应用由一个activity和一个布局(layout)组成。按照Android studio提示新建一个普通的项目,activity name设置为”QuizActivity”(当然名字可以随便取,不过为了具有辨识度我们还是根据activity的作用来取),layout名”activity_quiz”。修改默认的布局文件如下:
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 <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=".QuizActivity"> <TextView android:id="@+id/question_text_view" 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/true_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/true_button"/> <Button android:id="@+id/false_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/false_button"/> </LinearLayout> <Button android:id="@+id/cheate_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/cheat_button"/> <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>
添加字符串资源(strings.xml文件)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <resources> <string name="app_name">GeoQuiz</string> <string name="question_australia">Canberra is the capital of Australia.</string> <string name="question_oceans">The Pacific Ocean is large than the Atlantic Ocean.</string> <string name="question_mideast">The suez is 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> <string name="warning_text">Are you sure you want to do this?</string> <string name="show_answer_button">Show Answer</string> <string name="cheat_button">Cheat!</string> <string name="judgment_toast">Cheating is wronging!</string> <string name="true_button">True</string> <string name="false_button">False</string> <string name="correct_toast">Correct!</string> <string name="next_button">Next!</string> <string name="incorrect_toast">Incorrect!</string> </resources>
布局文件中,LinearLayout里嵌有一个LinearLayout,外层LinearLayout是垂直排列,里层是水平排列,在水平排列的布局中,放置了两个Button,用来选择答案的true或false。
2,从XML布局文件到视图对象 setContentView(R.layout.activity_quiz);这行代码生成指定的视图并将其放在屏幕上。布局是一种资源,资源则是非代码形式的内容,图像文件、音频、xml等都是资源。布局文件放在app/res子目录下,activity_quiz.xml文件放在res/layout/目录下,strings.xml在res/values/目录下。可以使用资源ID在代码中获取相应的资源,activity_quiz.xml布局的资源ID为R.layout.activity_quiz。
3,组件的实际应用 1),在QuizActivity中添加两个成员变量,按IDE提示导入包。
1 2 private Button mTrueButton; private Button mFalseButton;
2), 引用组件
1 public View findViewById(int id);
以上代码用于引用已生成的组件,使用按钮的资源id获取视图对象,赋值给对应的成员变量,如下代码所示
1 2 mTrueButton = findViewById(R.id.true_button); mFalseButton = findViewById(R.id.false_button);
3),设置监听器 Android应用属于典型的事件驱动类型,不像命令行或脚本程序,事件驱动应用启动后,即开始等待行为事件的发生,如用户点击某个按钮(事件也可以由操作系统或其他应用触发,但是用户触发的点击事件更直观,如点击按钮)。应用在等待某个特定事件的发生,可以说应用正在”监听”特定事件,为相应某个事件而创建的对象叫做监听器(listener)。监听器会实现特定事件的监听器接口(listener interface)。
Android Sdk为我们设置了很多的监听器接口,我们不需要自己实现,当前我们要监听用户的按钮”点击”事件,因此要实现View.OnClickListener接口。在QuizActivity中添加如下代码,并导入View类:
1 2 3 4 5 6 7 mTrueButton = findViewById(R.id.true_button); mTrueButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //业务逻辑代码 } });
这是一个匿名内部类实现了OnClickListener接口,使用匿名内部类有两大好处,第一,可以相对集中地实现监听器方法,便于观察。第二,事件监听器一般只在一个地方使用,使用匿名内部类,就不用再创建繁琐的命名类了。匿名内部类实现了OnClickListener接口,因此必须实现接口中唯一的onClick方法,onClick现在是空方法,至于如何实现这个方法,则取决于我们自己,同样为false button设置监听器。
1 2 3 4 5 6 7 mFalseButton = findViewById(R.id.false_button); mFalseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //do something here } });
4),创建提示消息
创建一个toast提示消息,这是用来通知用户的简短弹出消息。用toast来反馈答案。首先在strings.xml中添加消息显示的字符串。 代码如下:
1 2 3 4 5 6 7 Toast.makeText(QuizActivity.this, R.string.correct_toast,Toast.LENGTH_SHORT) .show(); ... Toast.makeText(QuizActivity.this, R.string.incorrect_ toast,Toast.LENGTH_SHORT).show();
运行程序,点击按钮会有toast提示。
二、Android与MVC设计模式 2.1 创建新类 新建Question类,新增两个成员变量和一个构造方法以及getter和setter方法:
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 public class Question { private int mTextResId; private boolean mAnswerTrue; public Question(int mTextResId, boolean mAnswerTrue) { this.mTextResId = mTextResId; this.mAnswerTrue = mAnswerTrue; } public int getmTextResId() { return mTextResId; } public void setmTextResId(int mTextResId) { this.mTextResId = mTextResId; } public boolean ismAnswerTrue() { return mAnswerTrue; } public void setmAnswerTrue(boolean mAnswerTrue) { this.mAnswerTrue = mAnswerTrue; } }
2.2 MVC模式 MVC现在已经用的不多,MVP和MVVM模式是主流,我们修改QuizActivity类配合Question类使用,现在看看各个类是如何协同工作的。模型(Question)、控制器(QuizActivity)、视图(布局,mTrueButton、mFalseButton、TextView、Button、Button、Button)
1,Android与MVC设计模式 GeoQuiz应用基于模型–视图–控制器的结构模式进行设计,MVC模式表明,应用的任何对象,归根结底都属于模型对象、视图对象以及控制器对象中的一种。
2,MVC各个模块分工
a,模型对象:储存应用的数据业务逻辑。模型类通常用来映射与应用相关的一些事物,如用户、商店里的商品、服务器上的图片。
b,视图对象:它知道怎样在屏幕上绘制自己,以及如何响应用户的输入,如触摸事件。
c,控制器对象:含有应用的逻辑单元,是视图对象与模型对象联系纽带,控制器对象响应视图对象触发的各类事件,此外还管理着模型对象与视图层间的数据流动。
GeoQuiz应用的控制器层目前仅有QuizActivity类。MVC设计模式的好处:随着应用功能的扩展,应用往往会变得过于复杂让人难以理解,以java类组织代码有助于从整体视角设计和理解应用,这样我们可以按类而不是按变量和方法思考设计开发问题。同样,把java类以模型层、视图层和控制器层进行分类组织,也有助于我们设计和理解Android应用,这样我们可以按层而非一个个类来考虑设计开发了。
2.3 更新视图层 在QuizActivity中添加一个NEXT按钮,GeoQuiz应用唯一的布局定义在activity_quiz.xml文件中,添加一个NEXT BUTTON。
1 2 3 4 5 6 7 <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"/>
同时对文本视图(TextView)进行调整,删除TextView的android:text属性定义,不用硬编码地理知识问题,同时为TextView新增android:id属性,TextView需要资源ID,以便于在QuizActivity代码中为它设置要显示的文字。同时更新字符串资源定义,就是strings.xml中,添加其他地理知识问题的字符串。
2.4 更新控制器层 在QuizActivity中添加TextView和Button变量,再创建一个Question对象数组以及一个该数组的索引变量,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 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) , }; priavte int mCurrentIndex = 0;
在这里,我们会通过多次调用Question类的构造方法,创建了Question对象数组(在更复杂的项目里,这类数组的创建和存储会单独处理)。
1 2 3 mQuestionTextView = findViewById(R.id.question_text_view); int question = mQuestionBank[mCurrentIndex].getmTextResId(); mQuestionTextView.setText(question);
运行应用,可以看到数组存储的第一个问题显示在TextView上了。 接下来处理,NEXT按钮,首先引用NEXT按钮,然后为其设置监听器View.OnClickListener。该监听器的作用是:递增数组索引并相应地更新TextView的文本内容,代码如下所示:
1 2 3 4 5 6 7 8 9 mNextbutton = 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].getmTextResId(); mQuestionTextView.setText(question); } });
把公共代码提取出来,然后在不同的地方分别调用它。
1 2 3 4 private void updateQuestion() { int question = mQuestionBank[mCurrentIndex].getmTextResId(); mQuestionTextView.setText(question); }
我们提取代码生成update方法,运行应用,验证新增的NEXT按钮。当前应用认为所有的问题答案都是true,下面解决这个逻辑错误,同样为了避免重复代码,我们把代码封装在一个方法里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void checkAnswer (boolean userPressedTrue) { boolean answerIsTrue = mQuestionBank[mCurrentIndex].ismAnswerTrue(); int messageResId = 0; if (mIsCheater) { messageResId = R.string.judgment_toast; }else{ if (userPressedTrue == answerIsTrue){ messageResId = R.string.correct_toast; } else { messageResId = R.string.incorrect_toast; } } Toast.makeText(this,messageResId,Toast.LENGTH_SHORT).show(); }
方法接收boolean值,也就是用户按的是true还是false按钮,将用户的答案和当前Question对象中的答案作比较,最后判断答案是否正确。生成一个Toast消息反馈给用户。 在按钮的监听器里,调用checkAnswer方法,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ... public void onClick(View v) { // Toast.makeText(QuizActivity.this, // R.string.correct_toast,Toast.LENGTH_SHORT) // .show(); // setGravity(5,5,5); checkAnswer(true); } public void onClick(View v) { // Toast.makeText(QuizActivity.this, // R.string.incorrect_ // toast,Toast.LENGTH_SHORT).show(); checkAnswer(false); } ...
2.5 添加图标资源 使应用中的NEXT按钮能够显示向右的图标,这样会更加用户友好。将图标资源(图片)复制到res目录下的drawable目录。然后在布局文件中引用:
1 2 android:drawableRight="@drawable/arrow_right" android:drawablePadding="4dp"
三、activity的生命周期 现在应用运行后,旋转屏幕后,应用状态就重置,这就涉及到activity的生命周期问题了。