您的位置:首页 > 移动开发 > Android开发

notifyDataSetChanged()源码分析以及如何实现ListView局部刷新

2017-05-27 10:40 691 查看

notifyDataSetChanged()

当我们调用BaseAdapter的NotifyDataSetChanged()时,为什么ListView会全局刷新呢?

/**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}


其中调用了DataSetObservable的notifyChanged()

public class DataSetObservable extends Observable<DataSetObserver> {
……
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();
}
}
}
……
}


mObservers是DataSetObservable的成员变量,一个ArrayList,可以在DataSetObservable的父类Observable< T >中看出:

public abstract class Observable<T> {
/**
* The list of observers.  An observer can be in the list at most
* once and will never be null.
*/
protected final ArrayList<T> mObservers = new ArrayList<T>();
……
}


从以上代码可以看出notifyChanged()调用的是DataSetObserver的onChanged()

public void onChanged() {
// Do nothing
}


DataSetObserver是一个抽象类,onChanged()默认是空实现,那我们就试着去找它的具体子类。

猜想应该是在ListView的setAdapter()中设置了DataSetObserver的子类:

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);

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();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}

requestLayout();
}


可以看到new了一个AdapterDataSetObserver,并且作为参数传进了BaseAdapter的registerDataSetObserver()

public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}


其中调用了Observable的registerObserver()

public void registerObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
if (mObservers.contains(observer)) {
throw new IllegalStateException("Observer " + observer + " is already registered.");
}
mObservers.add(observer);
}
}


这里把刚才new的AdapterDataSetObserver添加到了观察者的集合中,当调用notifyDataSetChanged()时就会调用AdapterDataSetObserver的onChange()

class AdapterDataSetObserver ext
4000
ends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}

@Override
public void onInvalidated() {
super.onInvalidated();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
}


AdapterDataSetObserver 是ListView的一个内部类,它继承自AdapterView的一个也叫AdapterDataSetObserver 内部类:

@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();
}


在这里可以看到,notifyDataSetChanged()最终调用了requestLayout(),请求了View的重绘。

关于requestLayout(),可以看这篇文章View的绘制流程(Android开发艺术探索学习笔记)

很多时候我们只要刷新ListView中的一条item即可,不用每次都notifyDataSetChanged(),因为每次对整个ListView进行重绘,很耗费性能,所以下面来介绍一下ListView的局部刷新。

其实很简单就是手动调用一下Adapter的getView(),但是还是要根据实际情况,倘若你需要刷新的这个item还没在界面上展示就没必要刷新了,因为等到它要出现在界面上的时候自然会调用getView()。下面看一下Google推荐的做法:

private void updateItem(int position) {
/**第一个可见的位置**/
int firstVisiblePosition = listView.getFirstVisiblePosition();
/**最后一个可见的位置**/
int lastVisiblePosition = listView.getLastVisiblePosition();

/**在看见范围内才更新,不可见的滑动后自动会调用getView方法更新**/
if (position >= firstVisiblePosition && position <= lastVisiblePosition) {
/**获取指定位置view对象**/
View view = listView.getChildAt(position - firstVisiblePosition);
adapter.getView(position, view, listView);
}
}


参考:

1.Android源码分析之NotifyDataSetChanged()

2.Android ListView优化之局部刷新(更新)(非notifyDataSetChanged)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐