【进阶android】ListView源码分析——适配器及观察者模式
来源:互联网 发布:网络病毒有哪些 编辑:程序博客网 时间:2024/05/16 04:41
而在我们的印象中(未看源代码之前),ListView的setAdapter无非是如下实现:
public void setAdapter(ListAdapter adapter) { mAdapter = adapter;}很显然,此种想法是不完善的!
在真正的ListView源码之中,setAdapter方法不仅仅只是设置ListView的适配器,而是在此基础上,引入了观察者模式;不说废话,直接贴上源代码:
public void setAdapter(ListAdapter adapter) { if (mAdapter != null && mDataSetObserver != null) { 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; // AbsListView#setAdapter will update choice mode states. super.setAdapter(adapter);//多选择模式下,清空所有被选择的item if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();//是否所有项目都可选择 mOldItemCount = mItemCount;//保存上一次的item数量 mItemCount = mAdapter.getCount();//保存当前item的数量 checkFocus(); mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver);//注册数据观察者 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());//item共有多少中视图类型 int position; if (mStackFromBottom) {//从下到上 position = lookForSelectablePosition(mItemCount - 1, false); } else {//从上到下 position = lookForSelectablePosition(0, true); } setSelectedPositionInt(position);//当前选择的item的位置 setNextSelectedPositionInt(position);//下次布局时,选择的item的位置 if (mItemCount == 0) { // Nothing selected checkSelectionChanged(); } } else { ...... } requestLayout(); }
整个ListView的setAdapter方法流程可用总结如下:
1、如果存在mAdapter及mDataSetObserver(AbsListView.AdapterDataSetObserver实例),则注销适配器对观察者的注册;
2、调用resetList()方法,重置列表:1)将所有的header或footer对应的视图的参数(recycledHeaderFooter)设置为false;2)将一些变量初始化为无效值;
3、调用mRecycler.clear()方法,清空二维数组废弃视图堆、临时视图数组中所有的废弃视图;mRecycler便是ListView的重用视图工具,后文会详细介绍;
4、如果存在页眉或者页脚视图,则将传进方法里的适配器封装为一个HeaderViewListAdapter实例,再将其赋值给当前ListView的适配器;
5、将上一次所选择的item的位置及行ID置空;
6、调用super.setAdapter(adapter)方法;AbsListView中的setAdapter方法只干了一件事,那就是如果当前ListView能够选择多个item(多选择模式),清空已经选择的所有item;
7、更新mItemCount、mOldItemCount两个变量;前者当前处理的item数量;后者保存前一次处理的item数量;
8、重新实例化一个AbsListView.AdapterDataSetObserver对象,并注册到方法入参传来的适配器之中;
9、设置item的视图类型;mRecycler会对每一种视图类型创建一个对应的废弃视图堆,用以为该种视图类型提供重用的视图;
10、找到当前被选择的item的位置和行ID,并将其赋值于本次布局被选择item的位置和下次布局被选择item的位置设置;
11、请求布局。
以上11个步骤中,第一、四、八步骤是本章的重点;同时会结合BaseAdapter类来分析适配器的notifyDataSetChanged()和notifyDataSetInvalidated()两个方法的实现流程;综上所述,笔者将分四方面来阐述:
1、适配器注册、注销观察者;
2、HeaderViewListAdapter类;
3、BaseAdapter中的notifyDataSetChanged()方法;
4、BaseAdapter中的notifyDataSetInvalidated()方法;
一、适配器注册、注销观察者;
适配器(BaseAdapter为例子)之中存在一个DataSetObservable的实例,DataSetObservable类相当于是一个元素类型为观察者(DataSetObserver)的ArrayList的封装,而封装的主要目的就是确保这个ArrayList是在一种线程安全的状态下,添加、删除元素。
观察者DataSetObserver是一个抽象类,该类代码如下:
public abstract class DataSetObserver { /** * This method is called when the entire data set has changed, * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}. */ public void onChanged() { // Do nothing } /** * This method is called when the entire data becomes invalid, * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a * {@link Cursor}. */ public void onInvalidated() { // Do nothing }}
二、HeaderViewListAdapter类;
HeaderViewListAdapter类,是对ListAdapter(BaseAdapter实现了该接口)的一种封装;而封装的主要目的是使得适配器能够像处理正常的item数据那样去处理页眉或者页脚,也就是说如果调用HeaderViewListAdapter类中的getCount方法,不止会返回所有item的数量,还会返回页眉、页脚数据的数量(也就是三种数据数量之和)。此外,该类还会提供一些专门针对页眉、页脚的方法,例如删除页眉(脚)、添加页眉(脚),获取页眉(脚)一种类型的总数量等。
与一般item数据相比,页眉(脚)数据是一种ListView.FixedViewInfo类型,FixedViewInfo类型将数据和视图绑定在了一起;而一般的item数据则是将数据与视图分离,只有在调用ListAdapter的getView方法才将数据和视图绑定在一起。如果是一般的item数据,则当调用HeaderViewListAdapter类的getView方法时,实际上调用的是ListAdapter类的getView。
三、BaseAdapter中的notifyDataSetChanged()方法;
前两点主要讲述有关的都和适配器有关,第三、四点则侧重与观察者模式;在重点讲述ListView的观察者流程之前;先要明确一点,那就是什么是观察者模式;所谓观察者模式,即一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。将这个概念拿到ListView之中,进一步而言,拿到BaseAdapter之中,BaseAdapter是被观察者,DataSetObserver则是观察者,当BaseAdapter发觉到有数据变化时,则将通知所有注册于适配器之中的观察者。因此,适配器提供了两个方法用以主动通知所有的观察者。当使用适配器时,如果发生数据改变时,应该主动调用这两个方法。
言归正传,这两个方法分别为notifyDataSetChanged()和notifyDataSetInvalidated();我们先看notifyDataSetChanged()。
BaseAdapter中的notifyDataSetChanged()代码如下:
public void notifyDataSetChanged() { mDataSetObservable.notifyChanged();}
根据第一点所示,mDataSetObservable乃DataSetObservable的一个对象。DataSetObservable中的notifyChanged方法则主要依次调用注册于其中DataSetObserver对象的onChange方法。DataSetObserver是一个抽象类,其中主要定义了两个什么都没有做的方法:onChanged()和onInvalidated();BaseAdapter,或者直接说ListView之中并未直接使用DataSetObserver,而是使用它的一个直接子类AdapterView.AdapterDataSetObserver;
此时,我们可用进行一个总结,BaseAdapter的notifyDataSetChanged()方法的处理本质,DataSetObservable类中的onChanged()方法;而在ListView中,DataSetObservable对象实际上是AdapterDataSetObserver对象;因此,下面我们重点看看AdapterDataSetObserver的onChanged()方法。
AdapterDataSetObserver的onChanged()方法源码如下:
public void onChanged() { mDataChanged = true; mOldItemCount = mItemCount; mItemCount = getAdapter().getCount(); // Detect(检测) the case where a cursor that was previously //(之前) invalidated(作废) has // been repopulated with new data. if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null && mOldItemCount == 0 && mItemCount > 0) { AdapterView.this.onRestoreInstanceState(mInstanceState); mInstanceState = null; } else { /*当调用adpter中的notifyDataSetChanged方法后, *adpterView视图(例如ListView)的屏幕会恢复到 *调用notifyDataSetChanged方法之前的位置;其原 *因就是在重回界面之前调用rememberSyncState方法 *记录被选则项目的位置*/ rememberSyncState(); } checkFocus(); requestLayout(); }这个方法之中可以分为四个步骤:
1、将mDataChanged设置为true,标识此时的ListView数据已经改变;
2、更新item的数量,并记录上一次的item数量;
3、调用rememberSyncState()方法,保存当前同步状态,确保布局成功后能够恢复相应的状态;
4、检测焦点,请求布局。
四个步骤中,1、2、4此刻暂不细讲,直接进入remeberSyncState()方法,源码如下:
void rememberSyncState() { if (getChildCount() > 0) { mNeedSync = true; mSyncHeight = mLayoutHeight; if (mSelectedPosition >= 0) {//存在被选择的item // Sync the selection state View v = getChildAt(mSelectedPosition - mFirstPosition);//获取对应的视图 mSyncRowId = mNextSelectedRowId; mSyncPosition = mNextSelectedPosition; if (v != null) { mSpecificTop = v.getTop(); } mSyncMode = SYNC_SELECTED_POSITION; } else { // Sync the based on the offset of the first view View v = getChildAt(0); T adapter = getAdapter(); if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) { mSyncRowId = adapter.getItemId(mFirstPosition); } else { mSyncRowId = NO_ID; } mSyncPosition = mFirstPosition; if (v != null) { mSpecificTop = v.getTop(); } mSyncMode = SYNC_FIRST_POSITION; } } }该方法的主要目的是保存相关用以同步的变量(mSyncHeight、mSyncRowId、mSyncPosition及mSyncMode),从而确保在布局之后,能够通过这些同步变量的值来恢复相应的屏幕状态(例如屏幕位置、视图滚动量等)。
首先细说一下这四个同步变量的意义;
- mSyncHeight:同步时视图的高度;
- mSyncPosition:同步时,从哪个位置开始寻找具有mSyncRowId的item;
- mSyncRowId:进行同步时,从哪个item开始同步;
- mSyncMode:同步模式;一共有两种同步模式:SYNC_SELECTED_POSITION和SYNC_FIRST_POSITION;SYNC_SELECTED_POSITION表示同步的item是当前被选择的item;SYNC_FIRST_POSITION表示同步的item是当前展示屏幕中第一个子视图对应的item。
该方法之中,首先将mNeedSync设置为true表示布局之后需要同步,而后更新mSyncHeight,接着根据是否存在被选择的item来确定同步模式;若是SYNC_SELECTED_POSITION模式则将mNextSelectedRowId与mNextSelectedPosition分别设置给mSyncRowId及mSyncPosition;若是SYNC_FIRST_POSITION模式则将当前屏幕中的第一个视图对应的item的行ID及位置设置给mSyncRowId及mSyncPosition。
在ListView中mNextSelectedRowId与mNextSelectedPosition表示布局之后的选择item的行ID及位置。
四、BaseAdapter中的notifyDataSetInvalidated()方法;
与BaseAdapter类中的notifyDataSetChanged()方法类似,notifyDataSetInvalidated()方法实质是调用的DataSetObservable类中的onInvalidated()方法;而在ListView中,DataSetObservable对象实际上是AdapterDataSetObserver对象;因此我们直接分析AdapterDataSetObserver中的onInvalidated()方法。
源代码如下:
public void onInvalidated() { mDataChanged = true; if (AdapterView.this.getAdapter().hasStableIds()) { // Remember the current state for the case where our hosting activity is being // stopped and later restarted mInstanceState = AdapterView.this.onSaveInstanceState(); } // Data is invalid so we should reset our state mOldItemCount = mItemCount; mItemCount = 0; mSelectedPosition = INVALID_POSITION; mSelectedRowId = INVALID_ROW_ID; mNextSelectedPosition = INVALID_POSITION; mNextSelectedRowId = INVALID_ROW_ID; mNeedSync = false; checkFocus(); requestLayout(); }该方法主要的目的是将数据集设置为无效,进一步而言是将当前选择item及布局后的选择item设置为无效,并且不进行相应的同步。
最后,我们来对比一下notifyDataSetChanged()和notifyDataSetInvalidated()两者的异同点。
相同点:
- 两者都是采用数据观察者模式进行实现的;
- 两者都进行了数据的改变,将mDataChanged变量设置为true;
- 两者都更新了当前item数量,保存了上一次item的数量;
- 两者都检查了焦点,请求了重新布局;从而使得屏幕界面都将进行刷新。
不同点:
两者最主要的一个不同便是对同步信息的处理;notifyDataSetChanged()是需要保存同步信息的,及布局后要恢复布局前的同步信息;而notifyDataSetInvalidated()则是不必保存同步信息,布局后也无需恢复同步信息。举一个简单的例子,假设当前屏幕第一个视图对应的item的位置为5,那么调用notifyDataSetChanged()方法后,界面会使得屏幕第一个视图对应的item的位置恢复到5,而调用notifyDataSetInvalidated()方法之后,界面屏幕第一个视图对应的item的位置则将初始化为0。
- 【进阶android】ListView源码分析——适配器及观察者模式
- 观察者模式——Android适配器源码分析
- Android进阶系列--源码分析观察者模式在ListView的运用
- 【进阶android】ListView源码分析——总述
- 观察者设计模式--listView的源码分析
- Android源码之ListView的适配器模式
- Android源码之ListView的适配器模式
- Android源码之ListView的适配器模式
- 【进阶android】ListView源码分析——ListView的重用视图机制
- 【进阶android】ListView源码分析——ListView的滚动机制
- Android源码分析:Android中的设计模式——观察者模式
- Java进阶(极客)——观察者模式(一)概述及自定义观察者模式
- Java进阶(极客)——观察者模式(二)概述及内置观察者模式
- Android 最常用的设计模式三 安卓源码分析—Observer观察者模式
- Android设计模式源码解析之ListView观察者模式
- Android设计模式源码解析之ListView观察者模式
- 【进阶android】ListView源码分析——布局三大方法
- 【进阶android】ListView源码分析——子视图的七种填充方式
- Linux常见错误解决
- R语言-回归分析
- logrotate
- Boost智能指针——scoped_ptr
- java好难
- 【进阶android】ListView源码分析——适配器及观察者模式
- 端序和位域的关系
- 黑马day05 Cookie技术入门&记录上次访问的时间
- Android实战页面内容加载动画
- Ubuntu:安装MySql经GUI客户端MySql-Workbench
- Noise Reduction Using a Median Filter(噪声去除的中值滤波方法)
- Hashtable 和 HashMap 做为 Map 的基本特性
- validator
- R语言-循环语句