ListActivity源码详解

前言

最近在研究PreferenceActivity发现是继承自ListActivity的,打开看了下ListActivity的源码,发现也不长,就详细阅读认识一下。

正文

ListActivity简单到只要在onCreate()中调用setListAdapter()方法就可以实现了。
支持空数据显示。

点进去我们看到前两个field很熟悉,就是一个ListView+Adapter
很容易就知道这两个field就是ListActivity的核心,数据存储在Adapter中,展示在ListView中。

1
2
3
4
5
6
7
8
9
10
11
protected ListAdapter mAdapter;
protected ListView mList;
/** 省略部分代码 */
public void setListAdapter(ListAdapter adapter) {
//加上锁,防止多个线程同时调用这个方法
synchronized (this) {
ensureList();//内含setContentView的方法
mAdapter = adapter;
mList.setAdapter(adapter);
}
}

在调用了setListAdapter()之后,对Adapter重新赋值,并重新设置ListViewAdapter,并且调用了ensureList()方法,重新创建布局。
点进ensureList()方法。

1
2
3
4
5
6
private void ensureList() {
if (mList != null) {
return;
}
setContentView(com.android.internal.R.layout.list_content_simple);
}

哈!原来每次setListAdapter()都会把Adapter替换掉,并且重新setContentView(),怪不得ListActivity不用我们操心布局。
我们再来看看这个布局的内容

1
2
3
4
5
6
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawSelectorOnTop="false"
/>

也就是说,你可以自己setContentView(),只要你的布局有一个viewIdandroid:id="@android:id/list"的就行。
看到这你可能会无语,就一个简单的ListView就完事了?这么简单我也会啊!
别急,这只是完成了基本的功能而已,还要开放一些接口给子Activity调用。
有了ListView当然要实现各种事件啊。

1
2
3
4
5
6
7
8
9
public void setSelection(int position) {
mList.setSelection(position);
}
public int getSelectedItemPosition() {
return mList.getSelectedItemPosition();
}
public long getSelectedItemId() {
return mList.getSelectedItemId();
}

当然还有点击事件,我们发现有一个点击的监听器mOnClickListener

1
2
3
4
5
6
7
8
private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
onListItemClick((ListView)parent, v, position, id);
}
};
protected void onListItemClick(ListView l, View v, int position, long id) {
//空方法,交由子类实现
}

既然有监听器,那又是在哪里设置了这个监听器呢?我们发现有一个onContentChanged()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void onContentChanged() {
super.onContentChanged(); //Activity中是空方法
View emptyView = findViewById(com.android.internal.R.id.empty); //空数据时显示
mList = (ListView)findViewById(com.android.internal.R.id.list); //绑定ListView
if (mList == null) {
throw new RuntimeException( "Your content must have a ListView whose id attribute is "
+ "'android.R.id.list'");
}
if (emptyView != null) {
mList.setEmptyView(emptyView); //多好啊,recyclerView就没有这个方法
}
mList.setOnItemClickListener(mOnClickListener); //设置点击事件
if (mFinishedStart) {
// 这个if不知道有什么用意
setListAdapter(mAdapter);
}
mHandler.post(mRequestFocus); //待会解释
mFinishedStart = true;
}

可是翻遍源代码也没发现哪里调用了onContentChanged()
从字面上看,onContentChanged()Content改变时的回调接口,等等,Content
我们不是在ensureList()中调用了setContentView()方法吗?
答案是正确的,在调用了setContentView(),会调用onContentChanged()

1
2
3
4
5
6
7
8
// Activity的setContentView()调用Window(具体子类是PhoneWindow)的setContentView()方法
// 以下代码出现在PhoneWindow中
@Override
public void setContentView(int layoutResID) {
/** 省略部分代码 */
mLayoutInflater.inflate(layoutResID, mContentParent);
getCallback().onContentChanged(); //注意这里!!!
}

再解释下,上面的mHandler.post(mRequestFocus);

1
2
3
4
5
6
7
8
9
10
11
private Runnable mRequestFocus = new Runnable() {
public void run() {
// 用来使ListView获取焦点。
mList.focusableViewAvailable(mList);
}
};
protected void onDestroy() {
// Activity销毁时移除
mHandler.removeCallbacks(mRequestFocus);
super.onDestroy();
}

至此,ListActivity已经认识的差不多了。有什么讲错了希望能有大牛来提点一下。
有兴趣的可以自己用recyclerView替换listView实现一个RecyclerActivity.