BaseAdapter的notifyDataSetChanged方法[通俗易懂]

2022-09-12 11:56:26 浏览数 (2)

大家好,又见面了,我是你们的朋友全栈君。

都用过 BaseAdapter的notifyDataSetChanged()方法,用法很简单,当BaseAdapter的数据更新了,需要更改显示,这时候就要调用notifyDataSetChanged()方法来更新数据,当然你可以用一种比较恶心的方式,在你所使用的AdapterView(这里是指AdapterView的子类,ListView,GridView,Gallery等等),调setAdapter()方法。好好分析一下如何使用以及为什么要使用notifyDataSetChanged方法。

首先我们先看一下,AdapterView有哪些子类:An AdapterView is a view whose children are determined by an Adapter.See ListView, GridView, Spinner and Gallery for commonly used subclasses of AdapterView.从这句描述中,我们可以看出,AdapterView就是结合Adapter使用的ViewGroup,其好处是不言而喻的,直接来说,这种View能实现View的复用,对节省内存很有帮助。

下面我们分别看一下源码中setAdapter()函数,

AdapterView的setAdapter函数:

代码语言:javascript复制
/**
* Sets the adapter that provides the data and the views to represent the data
* in this widget.
*
* @param adapter The adapter to use to create this view's content.
*/
public abstract void setAdapter(T adapter);

从这段代码中,我们可以看出,setAdapter在AdapterView中是没有实现的,需要在子类中进行实现。ListView的setAdapter()函数,虽然ListView不是直接继承AdapterView,但是并不影响我们分析setAdapter的机制,为了方便大家阅读,我在代码中做一些注释:

代码语言:javascript复制
/**
* 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) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);//解除数据监听
}
resetList();//清除ListView中的数据
mRecycler.clear();//mRecyler 是一个用来管理View复用的类,清空其中的View数据。
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);//如果你对ListView调用过addHeaderView或者是AooterView,
//adapter将被转换为HeaderViewListAdapter。
} else {
mAdapter = adapter;
}
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
// AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter);//调用父类的setAdapter方法。
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
mDataSetObserver = new AdapterDataSetObserver();//生成一个数据监听器,
mAdapter.registerDataSetObserver(mDataSetObserver);//给Adapter注册数据监听
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();//重新请求分配布局
}
代码语言:javascript复制
从这里我们可以看出,对ListView调用setAdapter会清除所有的数据,然后重新设置数据,这样对软件性能的损耗是不言而喻的。下面我们列举以下,其他的几个Adapter子类的setAdapter方法。GridView的setAdapter()方法:
代码语言:javascript复制
/**
* Sets the data behind this GridView.
*
* @param adapter the adapter providing the grid's data
*/
@Override
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
resetList();
mRecycler.clear(); 
mAdapter = adapter;
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
// AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter);
if (mAdapter != null) {
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
mDataChanged = true;
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);
checkSelectionChanged();
} else {
checkFocus(); 
// Nothing selected
checkSelectionChanged();
}
requestLayout();
}

我们可以看到GridView的setAdapter()方法中的内容和ListView中的内容几乎是一样的,这里先不做分析。

代码语言:javascript复制
/**
* The Adapter is used to provide the data which backs this Spinner.
* It also provides methods to transform spinner items based on their position
* relative to the selected item.
* @param adapter The SpinnerAdapter to use for this Spinner
*/
@Override
public void setAdapter(SpinnerAdapter adapter) {
if (null != mAdapter) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
resetList();
} 
mAdapter = adapter;
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
if (mAdapter != null) {
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
int position = mItemCount > 0 ? 0 : INVALID_POSITION;
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
} 
} else {
checkFocus(); 
resetList();
// Nothing selected
checkSelectionChanged();
}
requestLayout();
}

几乎还是跟之前一样的方式,另外一个Spinner的这个方法的源码,这里就不列出来了。总是分析而言,setAdapter方法会重置所有的数据,虽然能到达数据更新的效果,但是对软件性能的损耗很大,不建议这么做,也就是不建议大家频繁的使用setAdapter函数来更新数据。

代码语言:javascript复制
public interface Adapter {
void registerDataSetObserver(DataSetObserver observer);
void unregisterDataSetObserver(DataSetObserver observer);
int getCount(); 
Object getItem(int position);
long getItemId(int position);
boolean hasStableIds();
View getView(int position, View convertView, ViewGroup parent);
static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE;
int getItemViewType(int position);
int getViewTypeCount();
static final int NO_SELECTION = Integer.MIN_VALUE;
boolean isEmpty();
}

从这里我们看到其实Adapter是一个接口只要实现了这个接口的任何类都可以用于setAdapter。那么BaseAdapter又是怎么一回事呢?,我们来看一下:

代码语言:javascript复制
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public boolean hasStableIds() {
return false;
} 
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
} 
/**
* 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();
}
/**
* Notifies the attached observers that the underlying data is no longer valid
* or available. Once invoked this adapter is no longer valid and should
* not report further data set changes.
*/
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
public boolean areAllItemsEnabled() {
return true;
}
public boolean isEnabled(int position) {
return true;
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
} 
public boolean isEmpty() {
return getCount() == 0;
}
}
public interface ListAdapter extends Adapter {
public boolean areAllItemsEnabled();
boolean isEnabled(int position);
}
public interface SpinnerAdapter extends Adapter {
public View getDropDownView(int position, View convertView, ViewGroup parent);
}

ListAdapter和SpinnerAdapter是继承了Adapter的另外两个接口,而BaseAdapter同时实现了这两个接口。从Adapter的源码中不难看出,关于数据更新的代码只有两个函数:

代码语言:javascript复制
public interface Adapter {
/**
* Register an observer that is called when changes happen to the data used by this adapter.
*
* @param observer the object that gets notified when the data set changes.
*/
void registerDataSetObserver(DataSetObserver observer);
/**
* Unregister an observer that has previously been registered with this
* adapter via {@link #registerDataSetObserver}.
*
* @param observer the object to unregister.
*/
void unregisterDataSetObserver(DataSetObserver observer);
//.....
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
} 
/**
* 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();
}
/**
* Notifies the attached observers that the underlying data is no longer valid
* or available. Once invoked this adapter is no longer valid and should
* not report further data set changes.
*/
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}

这四个函数前两个用于注册和接触内容监听器,后两个是用来通知数据发生变化。

细心的同学可能会发现,在BaseAdapter中以notify开头的函数有四个,其实都是跟通知有关的,但是前两个是Object函数中实现的,用于线程的锁相关的。我们只关心后两个。

代码语言:javascript复制
//..........
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
//.....
/*这个类是AdapterView的内部类*/
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;
}
}

package android.database;
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();
}
}
}
public void notifyInvalidated() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onInvalidated();
}
}
}
}
package android.database;
/**
* Receives call backs when a data set has been changed, or made invalid. The typically data sets
* that are observed are {@link Cursor}s or {@link android.widget.Adapter}s.
* DataSetObserver must be implemented by objects which are added to a DataSetObservable.
*/
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
}
}

看到这里我想大家就清除明了了,AdapterView在内部实现了一个AdapterDataSetObserver类,对BaseAdapter调用NotifyDataSetChanged就是调用了这个类的onChanged()函数,细看以下AdapterDataSetObserver 的onChanged函数,发现,其实这个函数并没有什么奇特的写法。这个函数中做了这么几件事: 下面我们看一下更新是如何完成的,在listView中有如下函数:

代码语言:javascript复制
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child;
if (!mDataChanged) {
// Try to use an existing view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flow, childrenLeft, selected, true);
return child;
}
}
// Make a new view for this position, or convert an unused view if possible
child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}

这里只是其中的一处,如果没DataChanged为True也就是如果数据变化就重新获取View,如果数据没有变化就从mRecyler中获取activieView。其他mDateChanged的使用地方源代码中还有很多,大家有兴趣可以去参考源代码。总结而言,NotifyDataSetChanged就是告诉ListView数据变化了,进行数据改变,setAdapter函数将清空所有的数据,并且重新设置监听和添加View。这点有很大的不同。

Galler的setAdapter()方法,查看之下,我们发现Gallery本身并没有setAdapter方法,但是Galler的父类AbsSpinner实现了这个方法,

AbsSpinner.setAdapter(SpinnerAdapter adapter):

下面我们来分析BaseAdapter的notifyDataSetChanged()函数。值得一提的是,这个函数在Adapter类中并不存在,是baseAdapter中才出现的。

不多说,上源码,先看一下Adapter的源码和BaseAdapter的源码,为了简明,我删去了原本的注释:

这里的英文我就不做翻译了,友情提示一下大家,想做软件开发英语不好的抓紧学,否则就不要做了。也就是说在Adapter接口中提供了registerDataSetObserver(DataSetObserver observer)和unregisterDataSetObserver(DataSetObserver observer),补充一句,这些都是见名知义的函数,不做解释了。而这两个函数的实现确实在BaseAdapter中,BaseAdapter同时实现了ListAdapter和SpinnerAdapter接口,所以大家通常情况下只要使用baseAdapter就行了,在BaseAdapter中有四个数据更新相关的函数:

其实我们很容易发现,这两个函数仅仅是调用了BaseAdapter函数的内部成员变量的的notifyInvalidated()函数和notifyChanged()函数,而这个成员变量则是通registerDataSetObserver()函数进行设置的。回头看一下ListView的setAdapter的源码有这么一段:

可以看出,内容监听器是在这里设置的,设置了一个AdapterDataSetObserver()的内容监听器。这又是一个怎么的监听器呢?:继续看源码:

a.设置了数据发生变化的表示 mDataChanged=true;

b.将没ItemCount赋值给mOldItemCount,也就是数据变化了,但是保留之前的数据数量。mOldItemCount = mItemCount;

c.更新mItemCount的大小 mItemCount = getAdapter().getCount();

d.最后,重新布局,relayout();

其实这里的关键就是设置数据变化表示,然后更新数量,重新布局。所谓的数据变化监听就是这么回事。就是通知数据变了,然后ListView通知变化。那执行完这段代码之后会执行什么呢?requestLayout();熟悉这个函数的都知道,这个函数的调用会让子控件,也就是当前的ListView重新向父控件请求layout。具体请看 android 图形系统requestLayout的流程。从这里开始,将重新布局和分配空间。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/149611.html原文链接:https://javaforall.cn

0 人点赞