解决ViewPager和PagerAdapter中调用notifyDataSetChanged失效问题(从notifyDataSetChanged方法的源码入手,超详细)

从PagerAdapter的notifyDataSetChanged方法源码入手解决ViewPager和PagerAdapter中调用notifyDataSetChanged失效的解决办法

1:问题描述

周末了,总结一下这周在项目中通过ViewPagerPhotoView做一个照片查看器的效果,调用notifyDataSetChanged方法无法更新界面的问题。如下图:
在这里插入图片描述
问题是当我点击右上角的删除按钮时数据更新更新后调用了 PagerAdapternotifyDataSetChanged的,但是界面没更新**。即在ViewPager中当数据改变时,通过notifyDataSetChanged方法失效,无法刷新界面
附上点击删除按钮的代码:

 public void onClickRight() {
        //删除当前位置的数据
        mList.remove(mCurrentPosition);
        //更新界面        
        mPhotoPreviewAdapter.notifyDataSetChanged();//问题就是调用了notifyDataSetChanged界面没更新
        mTvTitle.setText("图片产看"+(mCurrentPosition+1)+"/"+mList.size());
        if (mList.size()==0) {
            finish();
        }
    }

2:解决办法

**思路:**既然PagerAdapternotifyDataSetChanged方法失效,那就得看它的源码到底做了些什么,这里简单描述一下notifyDataSetChanged方法的工作原理,着重讲解决办法,(注意:如果你想要更加深入的了解,解决此问题的原理请看文章的第3部分notifyDataSetChanged源码分析)

  1. notifyDataSetChanged方法通过一个观察者模式(observer.onChange()),将处理逻辑交给了PagerObserverdataSetChanged()方法
  2. dataSetChanged方法中通过getItemPosition(Object object)方法查询一遍所有child view进行遍历,注意child view由当前正在展示的item和预加载的item组成。
  3. getItemPosition(Object object)等于PagerAdapter.POSITION_UNCHANGED(默认值)表示当前页面不需要更新,不用销毁;
  4. getItemPosition(Object object)等于PagerAdapter.POSITION_NONE时需要更新,那么该child view所有item 都会被destroyItem(ViewGroup container, int position, Object object)方法remove掉,然后重新加载新的item。
  5. 总结一下notifyDataSetChanged 方法通过getItemPosition(Object object)方法的返回值判断是否需要销毁当前view创建新view。所以解决方案就是复写getItemPosition方法的返回值
方法1:复写adapter的getItemPosition方法
@Override
    public int getItemPosition(@NonNull Object object) {
      //返回值为   POSITION_NONE  即可实现刷新界面效果
        return POSITION_NONE;
    }

复写getItemPosition方法将返回值置为POSITION_NONE即可实现界面更新,但是存在资源浪费问题,看下图效果和日志(从adapter的destroyItem方法入手):在这里插入图片描述如上图:已基本实现了更新界面的效果,但是从日志中看出这种方案的缺陷:资源浪费,当要删除第2张图片时,预加载的item 图片1和图片3也被销毁了,造成了item不必要的销毁和创建工作。如果你的图片查看器中图片少,这种方案足以满足。但是如果你对代码性能有要求的话,请看方案二

方法2: 方案1的升级版,提升性能

即:删除指定的item时,预加载的条目的item不会被销毁和重新创建
这次先看实现效果如下图:
在这里插入图片描述看图中studio的日志和效果:当删除指定位置的图片时,直销毁当前位置的item,预加载的item
没有销毁,减少了不必要的销毁创建动作,提升了性能
。图上需要注意的是:日志中的position指的是下标,而模拟器中的图片title的数字是图片的个数,等于position+1,这俩不要弄混了。

接下来分析实现方案:
实现原理很简单:就是将getItemPosition()方法的返回值POSITION_NONE,做一下处理,即界面上要删除的item的postion和Adapter中销毁的item的position相等时返回POSITION_NONE, 不等时返回POSITION_UNCHANGED(这句话比较绕口),大白话讲就是比较两个位置是否相等。

此时解决问题的关键就是在adapter的getItemPosition(@NonNull Object object)中如何获取两个position进行比较

step1:获取activity中要删除的item的position
在自定义的Adapter中定义一个成员变量标志表示activity界面中要删除item的位置mPosition,并提供一个setPosition方法, 在ViewPager的addOnPageChangeListener监听中实时更新这个position

step2:通过在创建item时,即instantiateItem(@NonNull ViewGroup container, int position)方法中获Adapter中要销毁item的position**
自定义的Adapter中instantiateItem方法中设置tag,在getItemPosition(@NonNull Object object)方法中通过getTag获取position
过程如下图:
在这里插入图片描述
在ViewPager的addOnPageChangeListener监听中,实时更新这个界面要删除数据的position
在这里插入图片描述到此ViewPager和PagerAdapter中调用notifyDataSetChanged失效的问题已完美解决。

3:notifyDataSetChanged的源码分析

从第二部分中我们知道,notifyDataSetChanged 方法最终通过getItemPosition(Object object)方法,遍历viewpager中的所有的child item,这里就看一下最终掉的dataSetChanged方法,以后有时间专门会开一篇ViewPager的源码分析的文章。
这里推荐我的另一篇文章安卓性能优化从ViewPager的源码层理解实现fragment 的懒加载(仿微信头条)

void dataSetChanged() {
    // This method only gets called if our observer is attached, so mAdapter is non-null.
    final int adapterCount = mAdapter.getCount();
    mExpectedAdapterCount = adapterCount;
    // 判断是否需要走populate流程
    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获取itemPosition
        final int newPos = mAdapter.getItemPosition(ii.object);
		
        // 默认返回POSITION_UNCHANGED,即跳过该item
        if (newPos == PagerAdapter.POSITION_UNCHANGED) {
            continue;
        }

        // 返回POSITION_NONE,则移除该item
        if (newPos == PagerAdapter.POSITION_NONE) {
            mItems.remove(i);
            i--;

            if (!isUpdating) {
                mAdapter.startUpdate(this);
                isUpdating = true;
            }
			
            // 移除该item并标记需要走populate
            mAdapter.destroyItem(this, ii.position, ii.object);
            needPopulate = true;
			
            // 当前选中被移除,会判断mCurItem是否超出边界,超出则重新赋值有效索引
            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;
            }

            // 更新item的position,并标记需要走populate
            ii.position = newPos;
            needPopulate = true;
        }
    }

    if (isUpdating) {
        mAdapter.finishUpdate(this);
    }

    // mItems根据position重新排序
    Collections.sort(mItems, COMPARATOR);

    if (needPopulate) {
        // 重置所有child的宽度,等待populate中重新测量
        // 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();
    }
}

在代码中注视的很详细这里就不多解释了,祝大家周末愉快,有问题欢迎留言。

推荐我的另一篇文章安卓性能优化从ViewPager的源码层理解实现fragment 的懒加载(仿微信头条)

  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
ViewPager嵌套ListView时,可能会遇到ListView无法滑动的问题,这是因为ViewPager会拦截ListView的滑动事件。解决方法如下: 1. 自定义ListView,重写其onInterceptTouchEvent()方法,返回false,让ViewPager不拦截ListView的滑动事件。 ``` public class MyListView extends ListView { public MyListView(Context context) { super(context); } public MyListView(Context context, AttributeSet attrs) { super(context, attrs); } public MyListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: setParentScrollAble(false); break; case MotionEvent.ACTION_UP: setParentScrollAble(true); break; } return super.onInterceptTouchEvent(ev); } private void setParentScrollAble(boolean flag) { getParent().requestDisallowInterceptTouchEvent(!flag); } } ``` 2. 在ViewPager的适配器,将ListView所在的布局设置为android:descendantFocusability="blocksDescendants",防止ListView获取焦点而导致ViewPager无法滑动。 ``` <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:descendantFocusability="blocksDescendants"> <com.example.MyListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> ``` 以上两种方法都可以解决ViewPagerListView失效问题
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值