listview 源码分析
在日常的工作中,我们会经常和listview打交道,无论是直接使用,还是经过封装使用。其中最重要的步骤就是给listview设置一个adapter。
listView = new ListView(this);
listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1,getData()));
setAdapter()方法中都做了什么?
/**
* Sets the data behind this ListView.
*
* The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
* depending on the ListView features currently in use. For instance, adding
* headers and/or footers will cause the adapter to be wrapped.
*
* @param adapter The ListAdapter which is responsible for maintaining the
* data backing this list and for producing a view to represent an
* item in that data set.
*
* @see #getAdapter()
*/
@Override
public void setAdapter(ListAdapter adapter) {
//首先我们看到每次给一个listview设置adapter时,adapter会注销之前注册的mDataSetObserver
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
//重置list
resetList();
mRecycler.clear();
//当有headerView,footerView时,给headerView,footerView设置一个新的HeaderViewListAdapter
//listView设置的adapter并不是我们设置的adapter,而是包装过的HeaderViewListAdapter
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);
///当adapter不为空时,我们看看下面又干了什么
if (mAdapter != null) {
///所有的状态重置
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
//new 出一个新的mDataSetObserver,并且和adapter绑定,
//采用观察者设计模式,目的是对绑定Adapter的数据进行监测
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
//通过参数viewTypeCount的值,创建多少可复用View的List对象
//
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();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
//重新绘制布局
requestLayout();
}
因此,从上面的源码注释可以看出,在一个界面中,重复的new adapter,并给listview设置是会造成很大的资源浪费,不仅仅是new一个adapter的问题,而是把所有状态重置,我们在使用中的过程中最好避免这种使用方式。
notifyDataSetChanged()方法是如何实现view更新的?
我们在上面的代码中可以看到这样一段话
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
其实我们常用的adapter.notifyDataSetChanged();view的绘制和更新就是由它实现的。那我们平时使用中view的绘制是都全部绘制还是都更新呢?? 我们现看下更新的原理实现把。在日常使用中,listview经常是和baseAdapter一起使用的,BaseAapter就是一个观察者模式。
我们先看下数据观察者底层是如何实现的。
public class DataSetObservable extends Observable<DataSetObserver> {
/**
* Invokes {@link DataSetObserver#onChanged} on each observer.
* Called when the contents of the data set have changed. The recipient
* will obtain the new contents the next time it queries the data set.
*/
public void notifyChanged() {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
/**
* Invokes {@link DataSetObserver#onInvalidated} on each observer.
* Called when the data set is no longer valid and cannot be queried again,
* such as when the data set has been closed.
*/
public void notifyInvalidated() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onInvalidated();
}
}
}
我们Listview中用到的 AdapterDataSetObserver 是在 AbsListview中定义的一个内部类,是继承AdapterView<ListAdapter>.AdapterDataSetObserver
class AdapterDataSetObserver extends DataSetObserver {
private Parcelable mInstanceState = null;
@Override
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 {
rememberSyncState();
}
checkFocus();
//重绘布局
requestLayout();
}
@Override
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();
}
public void clearSavedState() {
mInstanceState = null;
}
}
通过源码我们可以知道,当Listview中数据变化时,通过adapter的notifyDataSetChanged,触发父类DataSetObervable方法,这个会调用所有观察者的onChanged,这个里面会重新绘制布局,刷新界面。这里就可以看出,一旦调用notifyDataSetChanged就会将整个界面重新绘制,而不是将更新的部分界面重绘。例如我们在listview数据变化的时候都会调用。 因此,我们在使用notifyDataSetChanged方法时应该考虑到重绘界面带来的性能消耗,在平时的使用中仅在必须的时候使用。
现在大家都逐渐使用reycleView来替代listview,而reycleView更新数据集不是用adapter.notifyDataSetChanged(),而是 notifyItemInserted(position) 与notifyItemRemoved(position),只有用后两种才会有动画。下面是源码:
public final void notifyItemChanged(int position, Object payload) {
this.mObservable.notifyItemRangeChanged(position, 1, payload);
}
public final void notifyItemInserted(int position) {
this.mObservable.notifyItemRangeInserted(position, 1);
}
public final void notifyItemRemoved(int position) {
this.mObservable.notifyItemRangeRemoved(position, 1);
}
当然,这只是部分代码,我们普通的adpater是没有这样的方法,所有reycleView需要用自己特定的adapter,与此对应的是,它也有专属AdapterDataObservable,与我们通常的区别如下,增加了一些专属方法:
public boolean hasObservers() {
return !this.mObservers.isEmpty();
}
public void notifyItemRangeChanged(int positionStart, int itemCount) {
this.notifyItemRangeChanged(positionStart, itemCount, (Object)null);
}
public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
for(int i = this.mObservers.size() - 1; i >= 0; --i) {
((AdapterDataObserver)this.mObservers.get(i)).onItemRangeChanged(positionStart, itemCount, payload);
}
}
public void notifyItemRangeInserted(int positionStart, int itemCount) {
for(int i = this.mObservers.size() - 1; i >= 0; --i) {
((AdapterDataObserver)this.mObservers.get(i)).onItemRangeInserted(positionStart, itemCount);
}
}
public void notifyItemRangeRemoved(int positionStart, int itemCount) {
for(int i = this.mObservers.size() - 1; i >= 0; --i) {
((AdapterDataObserver)this.mObservers.get(i)).onItemRangeRemoved(positionStart, itemCount);
}
}
public void notifyItemMoved(int fromPosition, int toPosition) {
for(int i = this.mObservers.size() - 1; i >= 0; --i) {
((AdapterDataObserver)this.mObservers.get(i)).onItemRangeMoved(fromPosition, toPosition, 1);
}
}
这么比较起来,recycleView要比listview强大些。但是平时的工作中我们可以根据自己项目的需求选择控件,同时注意使用过程中减少不必要的资源浪费。