BaseAdapter的notifyDataSetChanged方法[通俗易懂]

BaseAdapter的notifyDataSetChanged方法[通俗易懂]都用过BaseAdapter的notifyDataSetChanged()方法,用法很简单,当BaseAdapter的数据更新了,需要更改显示,这时候就要调用notifyDataSetChanged()方法来更新数据,当然你可以用一种比较恶心的方式,在你所使用的AdapterView(这里是指AdapterView的子类,ListView,GridView,Gallery等等),调setAdap

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

都用过 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 ListViewGridViewSpinner and Gallery for commonly used subclasses of AdapterView.从这句描述中,我们可以看出,AdapterView就是结合Adapter使用的ViewGroup,其好处是不言而喻的,直接来说,这种View能实现View的复用,对节省内存很有帮助。

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

AdapterView的setAdapter函数:

/**
* 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的机制,为了方便大家阅读,我在代码中做一些注释:

/**
* 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();//重新请求分配布局
}

从这里我们可以看出,对ListView调用setAdapter会清除所有的数据,然后重新设置数据,这样对软件性能的损耗是不言而喻的。下面我们列举以下,其他的几个Adapter子类的setAdapter方法。

GridView的setAdapter()方法:

/**
* 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中的内容几乎是一样的,这里先不做分析。

/**
* 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函数来更新数据。

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又是怎么一回事呢?,我们来看一下:

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的源码中不难看出,关于数据更新的代码只有两个函数:

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的notifyDataSetChanged方法[通俗易懂]


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

//..........
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中有如下函数:

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的流程。从这里开始,将重新布局和分配空间。




版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • sshfs 挂载_cifs挂载

    sshfs 挂载_cifs挂载1.安装软件sudoapt-getinstallsshfsfusemount2.实例操作在此实例中,我们需要将远程主机”192.168.1.218″上的“/home”挂载到本地系统的“/mnt/server218”下,我们已经具备了挂载前的一切必须条件:拥有”192.168.1.218″的账号“human”和密码,并且218主机提供了ssh访问。新建目录“

    2022年10月24日
  • 【机器学习】数据归一化——MinMaxScaler理解

    【机器学习】数据归一化——MinMaxScaler理解文章目录前言公式实例前言前阵在查sklearn的归一化方法MinMaxScaler的时候,发现找到的文章解释的一塌糊涂,一般都是扔个公式加一堆代码就敷衍了事了,所以这次写一篇讲述MinMaxScaler核心功能的文章。公式会查MinMaxScaler的基本上都应该理解数据归一化,本质上是将数据点映射到了[0,1]区间(默认),但实际使用的的时候也不一定是到[0,1],你也可以指定参数feature_range,映射到其他区间,这个后面再讲。首先了解该计算公式:Xstd=X−X.min(axis

    2022年10月11日
  • SSDP协议_sntp协议

    SSDP协议_sntp协议近来在研究SSDP,SimpleServiceDiscoveryProtocol(简单服务发现协议)。这是用来实现无配置,自发现局域网内部服务的协议。由IPv4下有固定的239.255.255.250:1900这一固定的地址来负责多播数据。不过,从我的学习经历来说,要啃这种东西,最好的方法还是用例子搞懂名词,并实践一次。其实SSDP协议的请求就三种:byeby

    2022年10月11日
  • bat倒计时代码

    bat倒计时相信大家都想要代码吧,下面是代码。@echooffseta=(这里是几分不支持10分)setb=(这里是几秒):dao1set/ab=b-1ping-n2-w500127.1>nulclsecho倒计时:%a%分%b%秒if%b%==0(set/aa=a-1)else(gotodao1)if%a%==-1(goto…

  • exponential backoff algorithm「建议收藏」

    exponential backoff algorithm「建议收藏」在看NDN的默认转发策略BestRouteStrategy中提到了指数退避算法,回忆了一下,即为:在一个共享信道的情况下,当网络上的节点在发生冲突时,每个节点节点等待一定的时间后重新发送。在二进制指数退避算法中,等待时间随着以二为底的指数增长。如果重试失败,那么下次的等待时间将会是上次的等待时间二倍。如果重试次数大于最大重试次数,那么包将从包队列中去除。

  • WPF中ListView排序

    WPF中ListView排序//后台代码privatevoidlvList_Click_1(objectsender,RoutedEventArgse){if(e.OriginalSourceisGridViewColumnHeader){//获得点击的列

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号