activity是Android SDK中Activity的一个实例,负责控制各组件与用户的交互
-
布局定义了一系列组件,包括Button、TextView、 RecyclerView。布局和组件之间的关系可用下图表示:
常用组件和布局的继承关系如下图:
- 项目的app/res/values目录下保存了一系列的资源,包括字符串资源,图片资源等,包括布局也是资源的一种。它们都通过资源ID被引用。如果控件也需要被引用,则在xml文件中定义它们时,可以加上android:id属性,为其设置ID。
- xml布局是如何转换为视图对象的呢?activity子类被创建后,onCreate(Bundle)方法会被调用,它再调用
public void setContentView(int layoutRestId)
根据传入的布局资源ID,使用LayoutInflater类实例化该布局中定义的每一个View。activity子类都需要声明在AndroidManifest.xml配置文件中,在该文件中会设置一个launcher activity,app启动时会创建该activity。
-
MVC设计模式。MVC即模型,视图,控制,activity或者fragment(service还未学习)可以作为控制器,它们将视图即View实例对象显示在屏幕,将模型(实例类)中的数据显示更新在屏幕上。GeoQuiz项目的MVC示意图如下:
- activity的生命周期。
在activity的各个阶段,Android系统会自动调用相应的方法。重载这些方法,添加log,切换手机横竖屏,可以通过logcat看到activity的生命周期中相应方法的调用。
- 保存数据以应对设备旋转。
protected void onSaveInstanceState(Bundle outstate)
该方法通常在onStop()方法前被调用,除非用户按了后退键。该方法的默认实现把所有activity视图状态保存在Bundle中,Bundle是一种存储键-值对的一种结构。可以通过覆盖onSaveInstanceState()方法,将一些信息保存在Bundle中:
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
Log.i(TAG, "onSaveInstanceState");
savedInstanceState.putInt(KEY_INDEX, mCurrentIndex);
savedInstanceState.putBoolean(KEY_IS_CHEATER, mIsCheater);
}
在onCreate()方法中获取信息:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate(Bundle) called");
setContentView(R.layout.activity_quiz);
if (savedInstanceState != null) {
mCurrentIndex = savedInstanceState.getInt(KEY_INDEX);
mIsCheater = savedInstanceState.getBoolean(KEY_IS_CHEATER);
}
...
}
还可以存BooleanArray。操作系统会将Bundle对象放入activity记录中,可以理解为暂存。覆盖onSaveInstanceState()保存当前activity的小的或状态的数据;覆盖onStop()方法,保存永久性数据,如用户编辑的文字等。(?如何理解?)
- tools命名空间。使用该命名空间,可以覆盖某个组件的任意属性,改属性仅在预览中生效。
- 启动activity并互相传递数据。一个activity启动另一个activity最简单的方式是用startActivity方法:
public void startActivity(Intent intent)
activity调用该方法后,调用请求发给了操作系统的ActivityManager。ActivityManager启动哪个activity呢?答案在传入的参数intent。intent有很多构造方法,其中一种为:
public Intent(Context packageContext, Class> cls)
传入的Class类型参数告诉ActivityManager要启动的activity,Context类型参数告诉ActivityManager在哪里可以找到它(?不是在manifest配置文件中声明了吗?)。可以在startActivity(Intent intent)的intent上附加上extra信息,传递给启动的activity。如同在Bundle中保存信息一样,extra信息也是一种键值对,在子activity中提供一个借口,父activity把信息通过借口传过来即可,无需关心“键”是什么样的:
//子activity
public static Intent newIntent(Context packageContext, boolean answerIsTrue) {
Intent intent = new Intent(packageContext, CheatActivity.class);
intent.putExtra(EXTRA_ANSWER_IS_TRUE, answerIsTrue);
return intent;
}
//父activity
mCheatButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//start cheatactivity
boolean answerIsTrue = mQuestionBank[mCurrentIndex].isAnswerTrue();
Intent intent = CheatActivity.newIntent(QuizActivity.this, answerIsTrue);
startActivity(intent);
}
});
子activity在onCreate方法中就可以获取该extra信息:
mAnswerIsTrue = getIntent().getBooleanExtra(EXTRA_ANSWER_IS_TRUE, false);
可以用intent传递更多的信息,使用多参数版本的intent构造函数即可(还未实践)。
以上是父activity传递信息给子activity,要获取子activity的信息,可以修改startActivity方法:
startActivityForResult(intent, REQUEST_CODE_CHEAT);
子activity可以调用:
public final void setResult(int resultCode)
public final void setResult(int resultCode, Intent intent)
如果子activity没有调用setResult,而父activity又是调用startActivityForResult启动子activity的,父activity会收到Activity.RESULT_CANCELLED。在项目实践中,子activity的Button监听回调被调用后,它设置了返回结果:
private void setAnswerShownResult(boolean isAnswerShown) {
Intent intent = new Intent();
intent.putExtra(EXTRA_ANSWER_SHOWN, isAnswerShown);
setResult(RESULT_OK, intent);
}
public static boolean wasAnswerShown(Intent result) {
return result.getBooleanExtra(EXTRA_ANSWER_SHOWN, false);
}
ActivityManager在子activity被销毁后调用父类的onActivityResult()方法,故父activity要重载该方法:
@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;
}
mIsCheater = CheatActivity.wasAnswerShown(data);
}
}
这里,解析信息放在了子activity定义的一个接口中,因为“键”存在子activity中,最终的还是通过intent来解析到的。
- Fragment。fragment与activity很类似,它也是负责创建并管理用户界面,与模型对象进行交互。fragment的生命周期与activity类似,但是fragment的生命周期方法由它的托管activity而不是操作系统调用。fragment并没有像activity一样在onCreate方法中完成布局和组件view的实例化,而是在onCreateView方法中完成的,如下:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, container, false);
mTitleField = v.findViewById(R.id.crime_title);
...
mDateButton = v.findViewById(R.id.crime_date);
...
mSolvedCheckBox = v.findViewById(R.id.crime_solved);
...
return v;
}
第二个参数是视图的父视图。以上fragment类中完成了主要的工作,但它的视图显然还无法显示出来,它需要托管给activity。Activity类中添加了FragmentManager类,它负责管理fragment并将它们的视图添加到activity的视图层级结构中。具体做法:
public class CrimeActivity extends SingleFragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragment_container);
if (fragment == null) {
fragment = new createFragment();
fm.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
}
}
fragment事务的add接口,第一个参数为容器视图资源ID,第二个参数是要加入的Fragment。二者视图关系可用下图表示,fragment视图要放置在activity的视图层级结构中:
- RecyclerView。RecyclerView是ViewGroup的子类,每一个列表项是作为View子对象来显示的。它们的显示依赖于两个类:ViewHolder和Adapter。在控制器中重载这两个类。实践项目中RecyclerView作为fragment布局的一个组件,在onCreateView方法中要先调用:
mCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
接下来,控制器把要显示的模型对象(一般是ArrayList对象?)作为参数构造了一个Adapter,然后调用:
mCrimeRecyclerView.setAdapter(mAdapter);
接着,Adapter调用getItemCount()方法获取要显示的列表项的个数,根据该个数,循环调用onCreateViewHolder( ViewGroup viewGroup, int i)和onBindViewHolder(CrimeHolder crimeHolder, int i)。ViewHolder的任务是什么呢?它首先会把列表项布局实例化,然后再通过各组件的ID,找到它们的引用。在onBindViewHolder,Adapter会把要显示的模型参数传递给ViewHolder,它通过组件显示在屏幕上。项目实践中相关代码示例:
private class CrimeHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
......
public CrimeHolder(LayoutInflater inflater, ViewGroup parent) {
super(inflater.inflate(R.layout.list_item_crime, parent, false));
itemView.setOnClickListener(this);
mTitleTextView = itemView.findViewById(R.id.crime_title);
mDateTextView = itemView.findViewById(R.id.crime_date);
mSolvedImageView = itemView.findViewById(R.id.crime_solved);
}
public void bind(Crime crime) {
mCrime = crime;
mTitleTextView.setText(mCrime.getTitle());
mDateTextView.setText(mCrime.getDate().toString());
mSolvedImageView.setVisibility(mCrime.isSolved() ? View.VISIBLE : View.GONE);
}
......
}
@Override
private class CrimeAdapter extends RecyclerView.Adapter {
......
public CrimeHolder onCreateViewHolder( ViewGroup viewGroup, int i) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
return new CrimeHolder(layoutInflater, viewGroup);
}
@Override
public void onBindViewHolder(CrimeHolder crimeHolder, int i) {
Crime crime = mCrimes.get(i);
crimeHolder.bind(crime);
}
......
}