糖尿病康复,内容丰富有趣,生活中的好帮手!
糖尿病康复 > ios android 动态更新数据类型 FragmentStatePagerAdapter支持动态更新数据

ios android 动态更新数据类型 FragmentStatePagerAdapter支持动态更新数据

时间:2022-01-24 05:47:28

相关推荐

ios android 动态更新数据类型 FragmentStatePagerAdapter支持动态更新数据

在Android开发中,我们应该使用到很高频率的一个控件就是ViewPager。但是在使用ViewPager的过程中,我们会发现有两个问题,一是不能关闭预加载;二是更新ViewPager的Adapter不生效。所以我在这里以FragmentStatePagerAdapter为例,探讨一下为什么更新adapter无法生效,并且提出解决方案。

为什么Adapter更新不生效

更新不生效其实很简单,我们看一下源码的调用过程

PagerAdapter.java

// PagerAdapter.java

/**

* This method should be called by the application if the data backing this adapter has changed

* and associated views should update.

*/

public void notifyDataSetChanged() {

synchronized (this) {

if (mViewPagerObserver != null) {

mViewPagerObserver.onChanged();

}

}

mObservable.notifyChanged();

}

ViewPager.java

// ViewPager.java

private class PagerObserver extends DataSetObserver {

PagerObserver() {

}

@Override

public void onChanged() {

dataSetChanged();

}

@Override

public void onInvalidated() {

dataSetChanged();

}

}

// 接着调用dataSetChanged

void dataSetChanged() {

// This method only gets called if our observer is attached, so mAdapter is non-null.

final int adapterCount = mAdapter.getCount();

mExpectedAdapterCount = adapterCount;

boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1

&& mItems.size() < adapterCount;

int newCurrItem = mCurItem;

boolean isUpdating = false;

for (int i = 0; i < mItems.size(); i++) {

final ItemInfo ii = mItems.get(i);

// 调用adapter的getItemPosition方法,获取新位置

final int newPos = mAdapter.getItemPosition(ii.object);

if (newPos == PagerAdapter.POSITION_UNCHANGED) {

continue;

}

if (newPos == PagerAdapter.POSITION_NONE) {

mItems.remove(i);

i--;

if (!isUpdating) {

mAdapter.startUpdate(this);

isUpdating = true;

}

mAdapter.destroyItem(this, ii.position, ii.object);

needPopulate = true;

if (mCurItem == ii.position) {

// Keep the current item in the valid range

newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));

needPopulate = true;

}

continue;

}

if (ii.position != newPos) {

if (ii.position == mCurItem) {

// Our current item changed position. Follow it.

newCurrItem = newPos;

}

ii.position = newPos;

needPopulate = true;

}

}

if (isUpdating) {

mAdapter.finishUpdate(this);

}

Collections.sort(mItems, COMPARATOR);

if (needPopulate) {

// Reset our known page widths; populate will recompute them.

final int childCount = getChildCount();

for (int i = 0; i < childCount; i++) {

final View child = getChildAt(i);

final LayoutParams lp = (LayoutParams) child.getLayoutParams();

if (!lp.isDecor) {

lp.widthFactor = 0.f;

}

}

setCurrentItemInternal(newCurrItem, false, true);

requestLayout();

}

}

PageAdapter.getItemPosition(ii.object)

public int getItemPosition(@NonNull Object object) {

return POSITION_UNCHANGED;

}

默认是返回POSITION_UNCHANGED,所以结合上面的逻辑

if (newPos == PagerAdapter.POSITION_UNCHANGED) {

continue;

}

这里跳出循环,所以其实什么事情都没有做,也没办法因为数据集的变更而变更UI。

如何解决

其实ViewPager.dataSetChanged()方法已经为我们预留了更新的逻辑。

......

for (int i = 0; i < mItems.size(); i++) {

final ItemInfo ii = mItems.get(i);

final int newPos = mAdapter.getItemPosition(ii.object);

if (newPos == PagerAdapter.POSITION_UNCHANGED) {

continue;

}

if (newPos == PagerAdapter.POSITION_NONE) {

mItems.remove(i);

i--;

if (!isUpdating) {

mAdapter.startUpdate(this);

isUpdating = true;

}

mAdapter.destroyItem(this, ii.position, ii.object);

needPopulate = true;

if (mCurItem == ii.position) {

// Keep the current item in the valid range

newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));

needPopulate = true;

}

continue;

}

// 这里的newPos就是上面从PagerAdapter.getItemPosition()获取到的位置信息

if (ii.position != newPos) {

if (ii.position == mCurItem) {

// Our current item changed position. Follow it.

newCurrItem = newPos;

}

ii.position = newPos;

needPopulate = true;

}

}

......

所以我们要跳过newPos == PagerAdapter.POSITION_UNCHANGED 和 newPos == PagerAdapter. POSITION_NONE 的逻辑,需要对PagerAdapter.getItemPosition()进行改造。

复制FragmentStatePagerAdapter源码

由于改造getItemPosition()方法需要对源码进行操作,所以我们首先需要复制一份源码,暂时叫DynamicFragmentStatePagerAdapter

改造getItemPosition

@Override

public int getItemPosition(@NonNull Object object) {

int index = indexOfFragments(object);

return index != -1 ? index : super.getItemPosition(object);

}

private int indexOfFragments(Object object) {

if (object instanceof Fragment) {

return mFragments.indexOf(object);

}

return -1;

}

getItemPosition方法的注释我们了解一下,

/**

* Called when the host view is attempting to determine if an item's position

* has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given

* item has not changed or {@link #POSITION_NONE} if the item is no longer present

* in the adapter.

*

*

The default implementation assumes that items will never

* change position and always returns {@link #POSITION_UNCHANGED}.

*

* @param object Object representing an item, previously returned by a call to

* {@link #instantiateItem(View, int)}.

* @return object's new position index from [0, {@link #getCount()}),

* {@link #POSITION_UNCHANGED} if the object's position has not changed,

* or {@link #POSITION_NONE} if the item is no longer present.

*/

简单来说就是就是根据这个方法,来判断当前item的位置是否发生了改变,返回值包括POSITION_UNCHANGED、POSITION_NONE、以及[0, {@link #getCount()}。所以当我们数据集发生变化的时候,其实fragment的位置,也要相应的发生变化否则就会在错误的位置获取到错误的页面,会发生页面显示错乱。

mFragments对象是用来缓存当前adapter维护的在内存中的fragment缓存对象。是一个数组结构,数组会随着数据集的增大而增大,数组的内容是fragment对象,会根据当前viewpager位置,保存的ViewPager.setOffscreenPageLimit(size)中size的大小的实例,ViewPager进行滑动时,超出page limit的页面会被执行destroyItem方法,并且预加载的fragment会执行instantiateItem方法,确保adapter中只保留相应数量的fragment实例。这里不展开说,可以详细看下FragmentStatePagerAdapter.instantiateItem(@NonNull ViewGroup container, int position)的方法源码。

mSavedState对象用来缓存adapter中所有fragment执行onSaveInstanceState()之后的数据,在destroyItem方法中调用被销毁的fragment的onSaveInstanceState方法,并把数据放在mSavedState对应的位置。随后在执行instantiateItem的时候,首先从mFragments找相应位置的fragment,并且找到mSavedState相应位置的state数据,进行页面的恢复。

有了上面的这两个知识点,我们就知道接下来要怎么做,首先我们在给fragment确定位置的时候,也就是在getItemPosition方法中,我们根据当前的fragment对象在mFragments确定新的位置。如果位置发生变化,则会重新刷新。代码如下:

void dataSetChanged() {

// This method only gets called if our observer is attached, so mAdapter is non-null.

final int adapterCount = mAdapter.getCount();

mExpectedAdapterCount = adapterCount;

boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1

&& mItems.size() < adapterCount;

int newCurrItem = mCurItem;

boolean isUpdating = false;

for (int i = 0; i < mItems.size(); i++) {

final ItemInfo ii = mItems.get(i);

final int newPos = mAdapter.getItemPosition(ii.object);

......

......

// 这里发现位置和原来的位置不一样,说明发生了变化,

if (ii.position != newPos) {

if (ii.position == mCurItem) {

// Our current item changed position. Follow it.

newCurrItem = newPos;

}

ii.position = newPos;

needPopulate = true;

}

}

if (isUpdating) {

mAdapter.finishUpdate(this);

}

Collections.sort(mItems, COMPARATOR);

// 这里会做刷新操作,设置新的current item位置

if (needPopulate) {

// Reset our known page widths; populate will recompute them.

final int childCount = getChildCount();

for (int i = 0; i < childCount; i++) {

final View child = getChildAt(i);

final LayoutParams lp = (LayoutParams) child.getLayoutParams();

if (!lp.isDecor) {

lp.widthFactor = 0.f;

}

}

setCurrentItemInternal(newCurrItem, false, true);

requestLayout();

}

}

所以一切的根源,就是在数据集发生变化时,需要通知对应位置的** mFragments和mSavedState**对象,插入null对象。

举例

如果我们要在数据集的头部插入数据,我们可以这么做,在copy源码的DynamicFragmentStatePagerAdapter类中加入我们的自定义方法,例如:

public void insertEmptyHeaderFragment() {

mFragments.add(0, null);

mSavedState.add(0, null);

}

在数据集插入单个数据的同时,也在该方法中插入对应的null,这样位置和值才能对的上,以至于恢复页面的时候,不会找错save state而在对的位置产生错误的页面。举例

fun insertData(data: XXX) {

dataSet?.add(0, data)

insertEmptyHeaderFragment()

}

插入其他位置,我相信对同学们应该也不难了。记得数据集更新后要调用adapter.notifyDataSetChanged()方法。这样就会去刷新数据集了。

总结

重点是改变getItemPosition()的位置计算,并且在更新数据集的时候,更新mFragments和mSavedState的位置。本文是根据使用ViewPager+FragmentStatePagerAdapter来举例,如果是其他adapter类,相信你经过上面的介绍之后,应该不是什么难事。最后就是,遇到困难不用怕,分析,跟踪源码,不能通过继承实现的,就copy源码来改,只要思想不滑坡,方法总比困难多。欢迎有不清楚的同学,可以线下持续交流。

如果觉得《ios android 动态更新数据类型 FragmentStatePagerAdapter支持动态更新数据》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。