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

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

 public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); ListView main_list = (ListView)this.findViewById(R.id.main_list);
MyArrayAdapter mArrayList=new MyArrayAdapter(this,R.layout.list_item,getData());
main_list.setAdapter(mArrayList);
main_list.setOnItemClickListener(mArrayList);
} private String[] getData() {
return new String[]{"测试数据1","测试数据2","测试数据3","测试数据4"};
}
}

适配器MyArrayAdapter代码:

 public class MyArrayAdapter extends ArrayAdapter<String> implements
OnItemClickListener { private int itemClicked; public MyArrayAdapter(Context context, int textViewResourceId,
String[] objects) {
super(context, textViewResourceId, objects); }
@Override
public View getView(int position, View convertView, ViewGroup parent) { convertView=super.getView(position, convertView, parent);
//如果是被点击的项,变换颜色
if (position==this.itemClicked) {
convertView.setBackgroundColor(Color.RED);
}else {
convertView.setBackgroundColor(Color.WHITE);
}
return convertView;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
//设置某项被点击
itemClicked=position;
this.notifyDataSetChanged();
} }

下面就让我们跟进去MyArrayAdapter.notifyDataSetChange()中看看。在本文中,我所查看的Android源代码是4.4.0的,不同版本可能有所出入。

     public void notifyDataSetChanged() {
super.notifyDataSetChanged();
mNotifyOnChange = true;
}

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

public class ArrayAdapter<T> extends BaseAdapter implements Filterable 

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

 public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
private final DataSetObservable mDataSetObservable = new DataSetObservable();
//...省略不必要的代码
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
} public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
} public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
} public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
//...省略不必要的代码
}

我们发现其实就是DataSetObservable这个对象在发生作用,但是DataSetObservable这个对象估计就是一个简单的观察者的实现,Android框架的编写者不大可能将业务逻辑放在这里面,不过我们还是要确认是不是跟我们所想的一样。

 public class DataSetObservable extends Observable<DataSetObserver> {
/**
* Invokes onChanged on each observer. Called when the data set being observed has
* changed, and which when read contains the new state of the data.
*/
public void notifyChanged() {
synchronized(mObservers) {
for (DataSetObserver observer : mObservers) {
observer.onChanged();
}
}
} /**
* Invokes onInvalidated on each observer. Called when the data set being monitored
* has changed such that it is no longer valid.
*/
public void notifyInvalidated() {
synchronized (mObservers) {
for (DataSetObserver observer : mObservers) {
observer.onInvalidated();
}
}
}
}

果然,跟预想的一样,它只是简单地调用了绑定在它身上的回调接口。那么BaseAdapter.notifyDataSetChange()的接口具体是在哪里绑定的呢?很有可能在构造函数中绑定,我们跟进ArrayListAdapter看看。

     public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) {
init(context, textViewResourceId, 0, objects);
} private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
mContext = context;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mResource = mDropDownResource = resource;
mObjects = objects;
mFieldId = textViewResourceId;
}

ArrayListAdapter中有很多构造函数,但是几经辗转全部都会转到init()函数中,很遗憾,我们扑空了。那么还在哪里可能绑定notifyDataSetChange()回调函数呢?其实从MainActivity中Adapter的初始化过程中,基本上只能锁定在MainActivity第十行中setAdapter函数中。接下去看看 public void setAdapter(ListAdapter adapter)这个函数。

     public void setAdapter(ListAdapter adapter) {
if (null != mAdapter) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
} resetList();
mRecycler.clear(); if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
} mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus(); mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver); mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); int position;
if (mStackFromBottom) {
position = lookForSelectablePosition(mItemCount - 1, false);
} else {
position = lookForSelectablePosition(0, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position); if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
} if (mChoiceMode != CHOICE_MODE_NONE &&
mAdapter.hasStableIds() &&
mCheckedIdStates == null) {
mCheckedIdStates = new LongSparseArray<Boolean>();
} } else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
} if (mCheckStates != null) {
mCheckStates.clear();
} if (mCheckedIdStates != null) {
mCheckedIdStates.clear();
} requestLayout();
}

setAdapter(...)这个函数有点长,不过我们只需要关注跟notifiDataSetChange()有关的实现,也就是第23、24行。不过这里另一个值得关注的点就是第63行,requestLayout()这个函数,它主要就是用来刷新界面,让界面重新绘制的。在23,、24行,绑定了一个AdapterDataSetObserver对象,下面我们就跟进去看看。从前面DataSetObservable的实现中,我们知道了它在notifyDataSetChange()的时候会调用DataSetObserver的onChange()。

   class AdapterDataSetObserver extends DataSetObserver
{
private Parcelable mInstanceState = null; AdapterDataSetObserver() {
}
public void onChanged() { mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount(); if ((getAdapter().hasStableIds()) && (mInstanceState != null) && (mOldItemCount == 0) && (mItemCount > 0))
{
onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
}
//...省略不必要代码
}

终于,在第19行,我们看见了requestLayout(),它就是用来重绘界面的,它在ViewRootImpl.java中有具体的实现。

     public void requestLayout() {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}

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

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

最新文章

  1. http错误代码含义中英文对照
  2. ZooKeeper个人笔记Session管理
  3. 2016年12月31日 学习java 第一天
  4. IOS开发之--UIScrollView pagingEnabled自定义翻页宽度
  5. ORA-00030: User session ID does not exist.
  6. 在web.xml中classpath和classpath*的区别
  7. github pages简易指南
  8. JVM内存管理及垃圾回收
  9. hdu 4753 Fishhead’s Little Game
  10. Xcode 那些简单实用的插件推荐
  11. 关于hibernate子查询参数的问题
  12. php 实现简单购物车
  13. jquery.zclip.js 不起作用
  14. SqlServer中循环给多张表建立聚簇索引
  15. flv.js怎么用?全面解读flv.js代码
  16. CentOS7 linux下yum安装mysql5.7
  17. Echarts地图展示及属性分析
  18. [k8s]k8s pod的4种网络模式最佳实战(externalIPs )
  19. slice.indices()/collections.Counter笔记
  20. Beta阶段敏捷冲刺前准备

热门文章

  1. 循序渐进Python3(四) -- 装饰器、迭代器和生成器
  2. SAP无损耗,FP前台和回写均有2%损耗
  3. delphi之多线程编程
  4. JSP(forward动作)登录功能
  5. xUtils框架的使用
  6. 自定义UIPageControl,可设置任意图片image。
  7. iOS真机调试问题-App installation failed,The maximum number of apps for free development profiles has been reached.
  8. OC学习1
  9. Hibernate 根据实体名称得到DB表名以及表对应的Sequence name
  10. jQuery Mobile 过渡效果