美文网首页Android 技术文章Android 自定义控件Android开发经验谈
ViewPager中复用View导致在某些自定义动画下会有问题的

ViewPager中复用View导致在某些自定义动画下会有问题的

作者: 蜗牛学开车 | 来源:发表于2019-11-15 17:18 被阅读0次

    前言

    最近在封装轮播图的时候,为了提高性能我使用了缓存,将每个被释放的view进行缓存然后在下次要用到是直接使用缓存的Veiw。这样做的好处就是不会一直inflate布局,也不会有一直设置图片标题等赋值操作。先来看下我PagerAdapter中的关键代码吧。

       /**
        * 用来放置可以复用的页面的View。
        */
        private SparseArray<View> itemViewCache = new SparseArray<>();
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            int index = getIndex(position);
            View entryView = itemViewCache.get(index);
            if (entryView == null) {
                BannerEntry bannerEntry = mItems.get(index);
                entryView = bannerEntry.onCreateView(container);
                entryView.setTag(KEY_INDEX_TAG, index);
                entryView.setOnClickListener(this);
                entryView.setOnLongClickListener(this);
                entryView.setOnTouchListener(mTouchListener);
            } else {
                itemViewCache.remove(index);
            }
            container.addView(entryView);
            return entryView;
        }
    
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            View view = (View) object;
            container.removeView(view);
            int index = getIndex(position);
            if (itemViewCache.get(index) == null) {
                itemViewCache.put(index, view);
            }
        }
    

    就是每次public Object instantiateItem(ViewGroup container, int position)方法执行的时候就去获取缓存的view,如果没有缓存的view则创建一个这里的创建包括了填充布局和设置图片。如果有缓存的则将这个View从缓存池中移除。然后执行ViewPager的addVeiw()方法。然后在public void destroyItem(ViewGroup container, int position, Object object)方法执行的时候先removeView,然后将被remove的View进行缓存,以索引为key,View为值。网上许多VeiwPager的缓存方式和我都大同小异。就不在多说了。

    Bug描述

    在通过上面的方式使用缓存的View后再通常情况下是没有问题的,只有一种情况下有问题,就是给VeiwPager通过public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer)方法设置自定义翻页动画并且这个动画在默写时候会出现重叠这个时候Bug就产生了,上面Bug呢,就是会出现某一张盖在另一张上导致顺序错乱。下面通过一张Gif图片说明问题。

    从上面的图片中可以看出在动画的执行过程中第一、第二、第四张翻页到下一张都是正常的,只有第三张被加载之后第四张会出现在第三张的上面,并且会抢夺焦点和点击事件。如果你也遇到过我这样的问题不一定会和我出现问题的顺序一样,但是是有规律的,具体是哪一张有问题,我不知道怎么描述(语言组织能力差),这里就不说了。下面说下导致这个Bug的原因吧。

    导致的原因及解决办法

    一开始我也不知道原因是什么,我就想难道是我复用View的原因,然后我就把复用View的代码注掉,不让它复用,每次加载页面都重新创建。结果就真的没有这个问题了,然后我就想,是不是我通过container.addView(entryView);方法addVeiw之后ViewPager帮我做了什么事情改变了我的Veiw?于是我就翻起了源码,翻过ViewPager源码的应该都知道,代码还挺多了,3000+行。而且里面有些关键代码是看不到源码的。So,我没有找到原因。
    后来我通过打断点的方式看看我新创的View被加进去之前的参数和被remove之后有什么区别,通过对比我发现基本都一样,只有LayoutParams中的两个字段发生了变化引起了我的注意,分别为“position”和“widthFactor”,我发现新创建的View被add之前position都等于0,widthFactor都等于0.0。被remove之后position都等于public void destroyItem(ViewGroup container, int position, Object object)方法中的position参数,widthFactor基本都等于1.0。然后我想在remove之后将这两个参数的值重置会不会就好了呢,果然,问题解决了。至于为什么在destroyItem的时候ViewPager没有帮我重置,也没有提供api让我重置我就不清楚了,下面奉上代码,因为这两个字段都是本地成员所以要用反射的方式去修改字段。

        private void reSetLayoutParams(ViewPager.LayoutParams lp) {
            try {
                Field positionField = getField(ViewPager.LayoutParams.class, "position");
                if (positionField != null) {
                    positionField.setInt(lp, 0);
                }
                Field widthFactorField = getField(ViewPager.LayoutParams.class, "widthFactor");
                if (widthFactorField != null) {
                    widthFactorField.setFloat(lp, 0.f);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        private Field getField(Class cls,String fieldName) {
            Field positionField = null;
            try {
                positionField =cls.getDeclaredField(fieldName);
                positionField.setAccessible(true);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
            return positionField;
        }
    

    定义好上面的方法然后在destroyItem的时候调用就OK了。下面是destroyItem方法的最终代码:

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            View view = (View) object;
            container.removeView(view);
            reSetLayoutParams((ViewPager.LayoutParams) view.getLayoutParams());
            int index = getIndex(position);
            if (itemViewCache.get(index) == null) {
                itemViewCache.put(index, view);
            }
        }
    

    下面奉上最终的效果图:


    想了解更多的欢迎光顾GitHub。

    源码GitHub地址

    GitHub地址传送门 如果您觉得本篇文章有用请帮忙点赞,如果有上面疑问也可以在下方评论区留言。如果你喜欢我的项目,Start和Fork都是对我的支持,您的支持是对我最大的鼓励。谢谢!!!

    相关文章

      网友评论

        本文标题:ViewPager中复用View导致在某些自定义动画下会有问题的

        本文链接:https://www.haomeiwen.com/subject/olmsictx.html