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

相关推荐

  • 统计学——单因素方差分析「建议收藏」

    统计学——单因素方差分析「建议收藏」概念方差分析:又称变异分析,是英国统计学家R.A.Fisher于1923年提出的一种统计方法,故有时也称为F检验。可简写为ANOVA。用于多组均数之间的显著性检验。要求:各组观察值服从正态分布或近似正态分布,并且各组之间的方差具有齐性。基本思想:将所有测量值间的总变异按照其变异的来源分解为多个部份,然后进行比较,评价由某种因素所引起的变异是否具有统计…

    2022年10月15日
  • 基于python的情感分析案例_关于python爬虫的情感分析

    基于python的情感分析案例_关于python爬虫的情感分析今天给大家分享的是通过情感词典来对文本进行情感分析最后计算出情感得分通过情感得分来判断正负调性主要步骤:数据准备本次情感词典采用的是BosonNLP的情感词典,来源于社交媒体文本,所以词典适用于处理社交媒体的情感分析本次分析准备的文本数据有:BosonNLP情感词典停用词表否定词表程度副词表生成停用…

  • matlab画图常用符号,matlab画图特殊符号[通俗易懂]

    matlab画图常用符号,matlab画图特殊符号[通俗易懂]在MATLAB中使用LaTex字符1.Tex字符表在text对象的函数中(函数title、xlabel、ylabel、zlabel或text),说明文字除使用标准的ASCII字符外,还可……matlab特殊字符_工学_高等教育_教育专区。本文说明了matlab中如何输入特殊字符,如希腊字母字符映射表C:\\WINDOWS\\system32\\charmap….

  • 大屏数据可视化案例「建议收藏」

    大屏数据可视化案例「建议收藏」数据可视化:把相对复杂的、抽象的数据通过可视的、交互的方式进行展示,从而形象直观地表达数据蕴含的信息和规律。数据可视化是数据空间到图形空间的映射,是抽象数据的具象表达。数据可视化交互的基本原则:总览为先,缩放过滤按需查看细节。大屏数据可视化是当前可视化领域的一项热门应用,通常可以分为信息展示类、数据分析类及监控预警类。大屏数据可视化应用的难点并不在于图表类型的多样化,而在于如何能在…

  • 1. Git安装与配置

    1. Git安装与配置本文介绍Windows下的Git安装与配置

  • SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)

    SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)使用SSM(Spring、SpringMVC和Mybatis)已经有三个多月了,项目在技术上已经没有什么难点了,基于现有的技术就可以实现想要的功能,当然肯定有很多可以改进的地方。之前没有记录SSM整合的过程,这次刚刚好基于自己的一个小项目重新搭建了一次,而且比项目搭建的要更好一些。以前解决问题的过程和方法并没有及时记录,以后在自己的小项目中遇到我再整理分享一下。这次,先说说三大框架整合过程。个人认

发表回复

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

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