导师安排我做一个小项目,其中涉及到利用Adapter作为ListView的适配器,为ListView提供数据。选中某一项后,要让这一项变成选中状态,也就是背景图片要换一下。下面我就用一个小例子来模拟。重点不在于实现,而是了解Adapter中notifyDataSetChanged()背后的运行机制。
我们先做一个小Demo(文中涉及的Demo在文章末尾),功能是选中某一项后,背景颜色会变红。代码非常简单,这里就不解释了。值得注意的是,当我们需要ListView进行刷新的时候,我们需要调用Adapter.notifyDataSetChanged()来让界面刷新。
<span style="color: #008080;"> 1</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span> MainActivity <span style="color: #0000ff;">extends</span><span style="color: #000000;"> Activity {</span>
2 @Override
3 protected void onCreate(Bundle savedInstanceState) {
4
5 super.onCreate(savedInstanceState);
6 setContentView(R.layout.activity_main);
7
8 ListView main_list = (ListView)this.findViewById(R.id.main_list);
9 MyArrayAdapter mArrayList=new MyArrayAdapter(this,R.layout.list_item,getData());
10 main_list.setAdapter(mArrayList);
11 main_list.setOnItemClickListener(mArrayList);
12 }
13
14 private String[] getData() {
15 return new String[]{“测试数据1″,”测试数据2″,”测试数据3″,”测试数据4”};
16 }
17 }
适配器MyArrayAdapter代码:
<span style="color: #008080;"> 1</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span> MyArrayAdapter <span style="color: #0000ff;">extends</span> ArrayAdapter<String> <span style="color: #0000ff;">implements</span>
2 OnItemClickListener {
3
4 private int itemClicked;
5
6 public MyArrayAdapter(Context context, int textViewResourceId,
7 String[] objects) {
8 super(context, textViewResourceId, objects);
9
10 }
11 @Override
12 public View getView(int position, View convertView, ViewGroup parent) {
13
14 convertView=super.getView(position, convertView, parent);
15 //如果是被点击的项,变换颜色
16 if (position==this.itemClicked) {
17 convertView.setBackgroundColor(Color.RED);
18 }else {
19 convertView.setBackgroundColor(Color.WHITE);
20 }
21 return convertView;
22 }
23 @Override
24 public void onItemClick(AdapterView<?> parent, View view, int position,
25 long id) {
26 //设置某项被点击
27 itemClicked=position;
28 this.notifyDataSetChanged();
29 }
30
31 }
下面就让我们跟进去MyArrayAdapter.notifyDataSetChange()中看看。在本文中,我所查看的Android源代码是4.4.0的,不同版本可能有所出入。
<span style="color: #008080;">1</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> notifyDataSetChanged() {</span>
2 super.notifyDataSetChanged();
3 mNotifyOnChange = true;
4 }
源代码就简单两句话,那么继续看看super是什么?
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span> ArrayAdapter<T> <span style="color: #0000ff;">extends</span> BaseAdapter <span style="color: #0000ff;">implements</span> Filterable
从类的声明中,父类就是ArrayAdapter,而ArrayList的父类是BaseAdapter。我们跟进BaseAdapter中看看。
<span style="color: #008080;"> 1</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">abstract</span> <span style="color: #0000ff;">class</span> BaseAdapter <span style="color: #0000ff;">implements</span><span style="color: #000000;"> ListAdapter, SpinnerAdapter {</span>
2 private final DataSetObservable mDataSetObservable = new DataSetObservable();
3 //…省略不必要的代码
4 public void registerDataSetObserver(DataSetObserver observer) {
5 mDataSetObservable.registerObserver(observer);
6 }
7
8 public void unregisterDataSetObserver(DataSetObserver observer) {
9 mDataSetObservable.unregisterObserver(observer);
10 }
11
12 public void notifyDataSetChanged() {
13 mDataSetObservable.notifyChanged();
14 }
15
16 public void notifyDataSetInvalidated() {
17 mDataSetObservable.notifyInvalidated();
18 }
19 //…省略不必要的代码
20 }
我们发现其实就是DataSetObservable这个对象在发生作用,但是DataSetObservable这个对象估计就是一个简单的观察者的实现,Android框架的编写者不大可能将业务逻辑放在这里面,不过我们还是要确认是不是跟我们所想的一样。
<span style="color: #008080;"> 1</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span> DataSetObservable <span style="color: #0000ff;">extends</span> Observable<DataSetObserver><span style="color: #000000;"> {</span>
2 /**
3 * Invokes onChanged on each observer. Called when the data set being observed has
4 * changed, and which when read contains the new state of the data.
5 */
6 public void notifyChanged() {
7 synchronized(mObservers) {
8 for (DataSetObserver observer : mObservers) {
9 observer.onChanged();
10 }
11 }
12 }
13
14 /**
15 * Invokes onInvalidated on each observer. Called when the data set being monitored
16 * has changed such that it is no longer valid.
17 */
18 public void notifyInvalidated() {
19 synchronized (mObservers) {
20 for (DataSetObserver observer : mObservers) {
21 observer.onInvalidated();
22 }
23 }
24 }
25 }
果然,跟预想的一样,它只是简单地调用了绑定在它身上的回调接口。那么BaseAdapter.notifyDataSetChange()的接口具体是在哪里绑定的呢?很有可能在构造函数中绑定,我们跟进ArrayListAdapter看看。
<span style="color: #008080;"> 1</span> <span style="color: #0000ff;">public</span> ArrayAdapter(Context context, <span style="color: #0000ff;">int</span> textViewResourceId, List<T><span style="color: #000000;"> objects) {</span>
2 init(context, textViewResourceId, 0, objects);
3 }
4
5 private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
6 mContext = context;
7 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
8 mResource = mDropDownResource = resource;
9 mObjects = objects;
10 mFieldId = textViewResourceId;
11 }
ArrayListAdapter中有很多构造函数,但是几经辗转全部都会转到init()函数中,很遗憾,我们扑空了。那么还在哪里可能绑定notifyDataSetChange()回调函数呢?其实从MainActivity中Adapter的初始化过程中,基本上只能锁定在MainActivity第十行中setAdapter函数中。接下去看看 public void setAdapter(ListAdapter adapter)这个函数。
<span style="color: #008080;"> 1</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> setAdapter(ListAdapter adapter) {</span>
2 if (null != mAdapter) {
3 mAdapter.unregisterDataSetObserver(mDataSetObserver);
4 }
5
6 resetList();
7 mRecycler.clear();
8
9 if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
10 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
11 } else {
12 mAdapter = adapter;
13 }
14
15 mOldSelectedPosition = INVALID_POSITION;
16 mOldSelectedRowId = INVALID_ROW_ID;
17 if (mAdapter != null) {
18 mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
19 mOldItemCount = mItemCount;
20 mItemCount = mAdapter.getCount();
21 checkFocus();
22
23 mDataSetObserver = new AdapterDataSetObserver();
24 mAdapter.registerDataSetObserver(mDataSetObserver);
25
26 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
27
28 int position;
29 if (mStackFromBottom) {
30 position = lookForSelectablePosition(mItemCount – 1, false);
31 } else {
32 position = lookForSelectablePosition(0, true);
33 }
34 setSelectedPositionInt(position);
35 setNextSelectedPositionInt(position);
36
37 if (mItemCount == 0) {
38 // Nothing selected
39 checkSelectionChanged();
40 }
41
42 if (mChoiceMode != CHOICE_MODE_NONE &&
43 mAdapter.hasStableIds() &&
44 mCheckedIdStates == null) {
45 mCheckedIdStates = new LongSparseArray<Boolean>();
46 }
47
48 } else {
49 mAreAllItemsSelectable = true;
50 checkFocus();
51 // Nothing selected
52 checkSelectionChanged();
53 }
54
55 if (mCheckStates != null) {
56 mCheckStates.clear();
57 }
58
59 if (mCheckedIdStates != null) {
60 mCheckedIdStates.clear();
61 }
62
63 requestLayout();
64 }
setAdapter(…)这个函数有点长,不过我们只需要关注跟notifiDataSetChange()有关的实现,也就是第23、24行。不过这里另一个值得关注的点就是第63行,requestLayout()这个函数,它主要就是用来刷新界面,让界面重新绘制的。在23,、24行,绑定了一个AdapterDataSetObserver对象,下面我们就跟进去看看。从前面DataSetObservable的实现中,我们知道了它在notifyDataSetChange()的时候会调用DataSetObserver的onChange()。
<span style="color: #008080;"> 1</span> <span style="color: #0000ff;">class</span> AdapterDataSetObserver <span style="color: #0000ff;">extends</span><span style="color: #000000;"> DataSetObserver</span>
2 {
3 private Parcelable mInstanceState = null;
4
5 AdapterDataSetObserver() {
6 }
7 public void onChanged() { mDataChanged = true;
8 mOldItemCount = mItemCount;
9 mItemCount = getAdapter().getCount();
10
11 if ((getAdapter().hasStableIds()) && (mInstanceState != null) && (mOldItemCount == 0) && (mItemCount > 0))
12 {
13 onRestoreInstanceState(mInstanceState);
14 mInstanceState = null;
15 } else {
16 rememberSyncState();
17 }
18 checkFocus();
19 requestLayout();
20 }
21 //…省略不必要代码
22 }
终于,在第19行,我们看见了requestLayout(),它就是用来重绘界面的,它在ViewRootImpl.java中有具体的实现。
<span style="color: #008080;">1</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> requestLayout() {</span>
2 checkThread();
3 mLayoutRequested = true;
4 scheduleTraversals();
5 }
关于scheduleTraversals()的实现,涉及到Android中View的绘制流程,感兴趣的可以看看《Android视图状态及重绘流程分析,带你一步步深入了解View(三)》。
到了这里,我们就清楚了notifyDataSetChange()背后的实现机制了,在不知不觉之间Android框架帮我们干了很多事情,不过需要提醒的时,每一次notifyDataSetChange()都会引起界面的重绘。当需要修改界面上View的相关属性的时候,最后先设置完成再调用notifyDataSetChange()来重绘界面。