最近基于SwipeRefreshLayout自定义了一个刷新加载组件,但在加载的时候发现一个问题,每次加载更多后listview总会跳回到头部,这样用户体验很不好。虽然通过notifyDataSetChanged解决了。但我们不能一知半解,还是带着问题在源码中分析。
- 首先,讲点题外话.这里我们要首先要看一下adapter中viewholder的优化机制
public void setAdapter(ListAdapter adapter) {
···
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
这里我们可以看到,Listview源码中将adapter赋值给全局变量mAdapter。但在Listview的源码中我们找不到mAdapter的声明,但我们可以在其父类AbsListView中可以找到。
既然是了解viewholder的优化机制,我们就必须要找到adapter.setview这个方法,在obtainView中我们可以找到,下面我们看一下其中的几处代码。
这里我们看到,mRecycler(这个一会再说)里获取transientView,如果transientView不为空,就和mAdapter获取的view进行匹配,如果不相等的时候,就添加到mRecycler里去。
如果transientView为空,从mRecycler取得scrapView,如果scrapView和mAdapter获取的view不相等,就添加到mRecycler里去。最后返回Child。
这里我们就可以看到mRecycler的重要性,那么mRecycler是什么呢
我们可以看到,mRecycler是RecycleBin的一个实例。而RecycleBin则提供了一种缓存机制,他把listview的item分为两部分,当前在屏幕显示的为ActiveViews,不在屏幕显示的全部放到ScrapViews里去。
这里就可以看到上面两段代码的作用。首先,从RecycleBin中取出view,作为mAdapter.getView的第二个参数,并和mAdapter.getView的返回值进行匹配,如果相等,就返回RecycleBin中缓存的view。如果不相等,就将mAdapter.getView的返回值添加到缓存里去。
那么就很清楚了,mAdapter.getView的第二个参数,经过RecycleBin的处理,在对view的缓存中起到关键性的作用。而我们使用viewholder以setTag的方式就是为了应用这种缓存机制。
现在回到我们的主题,如果我们在加载更多后使用setAdaper来刷新数据。那么会是一种什么状态呢,首先在listview里重新执行setAdaper,而在setAdaper里会清空之前所保存的缓存。等于说使用了两个不同的adapter实例对listview进行适配,是不可能保存之前的加载位置的。所以我们应该使用notifyDataSetChanged来执行数据刷新,那这里又引申出一个问题。
- notifyDataSetChanged和notifyDataSetInvalidated的区别
我测试了一下,notifyDataSetChanged在加载更多的时候可以保存当前位置,而notifyDataSetInvalidated则不行,现在我们来了解一下这两个方法
我们可以看到,notifyDataSetChanged是在隐藏的数据发生变化时,让对应的item刷新数据,而notifyDataSetInvalidated则是在隐藏数据失效或不可用时调用,这样两者的区别就非常明显了,但我们应该找到具体的执行方法,我们继续看下去。
我们看到notifyDataSetInvalidated和notifyDataSetChanged里面是调用了DataSetObservable类中的方法,而当我们继续追踪到DataSetObserver中时,我们发现了只是两个空方法。
这好像只是一个单纯的观察者模式实现,我们并没有找到相应的代码逻辑。然而我们可以断定,Listview中肯定存在相应的处理逻辑。
回到Listview的父类AbsListview中,在OnAttachToWindow方法中,我们发现了观察者的注册代码
AdapterDataSetObserver继承自AdapterView.AdapterDataSetObserver,在AdapterDataSetObserver中发现如下代码,
在onchange方法中,通过rememberSyncState,同步当前的屏幕状态。而onInvalidataed则对状态进行了重置。OVER~~
PS:这是第一次来走源代码,在思路上还是懵懵懂懂。有什么不对的,求大神指出