一、程序结构
Android原生应用采用了MVC的架构设计模式,因此可以将一个Android APP中的对象归为Model、View或Controller中的一种。
具体到某个实际的APP结构中,它一般会由若干个activity、layout文件和自定义类组成,activity是Android SDK中Activity类的实例,负责管理用户与应用界面的交互,应用的功能是通过编写Activity子类来实现的;layout文件则用于定义需要显示的UI对象以及指定它们在屏幕上所处的位置,layout文件的后缀为XML。
由此可知,layout文件等属于视图对象,此外Android还自带了很多可配置的视图类,在后面逐步了解。控制器则通常是Activity、Fragment或Service的子类。
二、layout文件内容
layout文件定义了界面显示的UI组件及其布局方式,对于下面这样一个简单界面
其layout文件内容为:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <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" /> <Button android:id="@+id/cheat_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"/> </LinearLayout> </LinearLayout>
其组成有:
一个垂直的LinerLayout组件,包含一个TextView组件和一个水平的LinerLayout组件;
水平的LinerLayout组件中又包含4个Button组件。
先看一下这里面包含的一些后续会经常用到的属性:
1. android:layout_width和android:layout_height,可以设置为wrap_content(视图与其父视图大小相同)或match_content(视图会根据其内容自动调整大小)。可以看到作为根节点的LinerLayout也有layout_width和layout_height属性,这是因为Android系统为其提供了容纳整个应用的父视图。
2.android:orientation,orientation是LinerLayout组件具有的属性,它指定LinerLayout的子组件是水平放置还是水平放置。
3.android:text,TextView和Button具有text属性,指定组件要显示的文字内容。
三、字符串资源
可以看到android:text的值为类似"@string/next_button"这样的形式,这是对字符串资源的应用,字符串资源(string resource)在string.xml中定义。通过把字符串内容放置在字符串资源,然后再间接引用它们,这样可以方便地修改需要显示的内容,更重要的是便于应用的本地化。
比如@string/next_button在字符串资源中的形式为:
<string name="next_button">Next</string>
那么为什么在layout文件中输入“@string/”后,Android会自动提示出“next_button”呢,这要从资源ID说起。非代码形式的内容都属于资源,比如图像文件、音频文件、XML文件等,资源文件都存放在app/res的子目录下,string.xml的路径为app/res/values,layout.xml的路径为app/res/layout,要获取这些xml中定义的资源需要先知道对应的资源ID,所有资源的ID都存放在R.java文件中,将Android Studio的项目视图切换为Project后,可根据路径app/build/generated/source/r/debug/对于项目包名称/R.java找到R文件,在这里我们可以在public static final class string下找到
public static final int next_button=0x7f0b0025,这便是next_button的资源ID,资源ID都是int型。
R文件在编译时自动生成,手动修改可能会导致未知错误。修改资源内容后,R文件不会实时刷新,只有在应用安装到模拟器或物理设备时才会重新生成,Android Studio还另外保存有一份用于代码编译的隐藏的R文件。
四、Activity
1.组件的引用
一个页面的Activity与layout的名称有对应关系,比如activity_quiz.xml对应的activity名称为QuizActivity.java。在Activity中使用组件的第一步,也是通过资源ID获取该组件,比如引用一个Button时的代码为:
private Button mTrueButton;
...
mTrueButton=(Button) findViewById(R.id.true_button);
拿到组件后,就可以做后续的操作了,比如为Button设置监听器(listener):
mTrueButton.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
}
});
或者改变TextView的显示内容:
mQuestionTextView=(TextView) findViewById(R.id.question_text_view);
mQuestionTextView.setText(“New Text”);
2.Activity的生命周期
在使用Android Studio向导创建一个Activity后,默认会有onCreate方法:
@Override
protected void onCreate(Bundle savedinstanceState){
}
onCreate属于Activity的生命周期方法,此外还有onStart(), onResume(), onPause(), onStop(), onDestroy()。页面在不同的状态间切换时,这些方法会被Android系统执行
启动APP时,依次执行onCreate, onStart, onResume,直接进入运行态;
按Home键回到主页时,依次执行onPause, onStop,进入停止态;
点击返回键退出APP时,则会依次执行onPause, onStop和onDestroy,页面被销毁;
需要注意的是,旋转屏幕时也会依次执行onCreate, onStart, onResume,这是因为发生屏幕旋转时,Android会销毁当前activity,寻找合适的备选资源并重新创建。这一特性对于需要保存页面状态的activity会造成问题,因为重新创建activity会丢失当前页面的操作状态,这是我们不希望发生的。要解决这个问题,可以覆盖系统的onSaveInstanceState(Bundle)方法,在APP转入停止状态时,这个方法在onStop之前由系统调用,我们可以在这个方法中将activity视图状态相关的数据存入Bundle对象中:
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putInt(KEY_INDEX, mCurrentIndex);
}
然后在每次重新创建activity时尝试读取Bundle中的内容:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mCurrentIndex = savedInstanceState.getInt(KEY_INDEX);
}
....
}
五、页面通信
一般来说一个APP不可能只有一个页面,通过主页面打开子页面时就涉及到了页面间的通信。
Android使用基于Intent的通信方式,intent对象是component用来与操作系统通信的媒介工具。activity就是一种component对象。Intent是一种多用途通信工具,Intent类有多个构造方法,能满足不同的使用需要。
1.从父页面启动子页面
假设父页面为QuizActivity,子页面为CheatActivity,则从父页面启动子页面的方法为:
...
Intent intent = new Intent(QuizActivity.this, CheatActivity.class);
intent.putExtra(EXTRA_ANSWER_IS_TRUE, answerIsTrue);
startActivity(intent);
...
使用intent.putExtra方法可以在启动子页面的同时传入需要的值,然后子页面可读取传入值的方法为:
mAnswerIsTrue = getIntent().getBooleanExtra(EXTRA_ANSWER_IS_TRUE, false);
2.子页面返回结果给父页面
在很多场景中都需要子页面返回结果给父页面。有两个方法可以使用:
public final void setResult(int resultCode)
public final void setResult(int resultCode, Intent data)
resultCode的值为预定义常量,有:
Activity.RESULT_OK,对应确认操作
Activity.RESULT_CANCELED,对应取消操作
Activity.RESULT_FIRST_USER,用于自定义结果代码
使用public final void setResult(int resultCode, Intent data)即可将结果数据通过Intent返回给父页面。
相应的,在父页面要取得子页面的返回结果,需要覆盖onActivityResult(int, int, Intent方法)
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != Activity.RESULT_OK) { return; } if (requestCode == REQUEST_CODE_CHEAT) { if (data == null) { return; } //TODO } }