从源代码的角度分析--在BaseAdapter调用notifyDataSetChanged()之后发生了什么

来源:互联网 发布:小米手机4636设置网络 编辑:程序博客网 时间:2024/05/20 21:22

导师安排我做一个小项目,其中涉及到利用Adapter作为ListView的适配器,为ListView提供数据。选中某一项后,要让这一项变成选中状态,也就是背景图片要换一下。下面我就用一个小例子来模拟。重点不在于实现,而是了解Adapter中notifyDataSetChanged()背后的运行机制。

    我们先做一个小Demo(文中涉及的Demo在文章末尾),功能是选中某一项后,背景颜色会变红。代码非常简单,这里就不解释了。值得注意的是,当我们需要ListView进行刷新的时候,我们需要调用Adapter.notifyDataSetChanged()来让界面刷新。

复制代码
 1 public class MainActivity extends Activity { 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代码:

复制代码
 1 public class MyArrayAdapter extends ArrayAdapter<String> implements 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 @Override12 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的,不同版本可能有所出入。

1     public void notifyDataSetChanged() {2         super.notifyDataSetChanged();3         mNotifyOnChange = true;4     }

源代码就简单两句话,那么继续看看super是什么?

public class ArrayAdapter<T> extends BaseAdapter implements Filterable 

从类的声明中,父类就是ArrayAdapter,而ArrayList的父类是BaseAdapter。我们跟进BaseAdapter中看看。

复制代码
 1 public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { 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框架的编写者不大可能将业务逻辑放在这里面,不过我们还是要确认是不是跟我们所想的一样。

复制代码
 1 public class DataSetObservable extends Observable<DataSetObserver> { 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 monitored16      * 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看看。

复制代码
 1     public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) { 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)这个函数。 

复制代码
 1     public void setAdapter(ListAdapter adapter) { 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 selected39                 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 selected52             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()。

复制代码
 1   class AdapterDataSetObserver extends DataSetObserver 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中有具体的实现。

1     public void requestLayout() {2         checkThread();3         mLayoutRequested = true;4         scheduleTraversals();5     }

关于scheduleTraversals()的实现,涉及到Android中View的绘制流程,感兴趣的可以看看《Android视图状态及重绘流程分析,带你一步步深入了解View(三)》。

    到了这里,我们就清楚了notifyDataSetChange()背后的实现机制了,在不知不觉之间Android框架帮我们干了很多事情,不过需要提醒的时,每一次notifyDataSetChange()都会引起界面的重绘。当需要修改界面上View的相关属性的时候,最后先设置完成再调用notifyDataSetChange()来重绘界面。

 

 

 


作者:kissazi2 
出处:http://www.cnblogs.com/kissazi2/ 
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

0 0
原创粉丝点击