ViewPager在实现gallery时PageTransformer初始不生效问题解决

本文解决ViewPager在实现gallery时,PageTransformer特效需要滑动一下才能生效的问题。

ViewPager版本:androidx-1.0.0。

 

复现问题前先复习下ViewPager实现gallery的方法:

ViewPager可以实现gallery效果,核心代码就是把ViewPager及其父View的clipChild设置为false,使ViewPager的子view可以超出ViewPager的范围:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/vp_gallery"
        android:layout_width="240dp"
        android:layout_height="240dp"
        android:layout_gravity="center_horizontal"
        android:clipChildren="false"/>

</FrameLayout>

 为了给gallery加入动效,需要使用PagerTransformer(PagerTransformer细节不再赘述,本例中两侧的视图会进行拉伸);为了左右两边的视图都能同时实例化,设置offScreenPageLimit:

        vpGallery.setOffscreenPageLimit(3);
        vpGallery.setPageTransformer(false, new ViewPager.PageTransformer(){
            @Override
            public void transformPage(@NonNull View page, float position) {
                float scale = 1 - Math.abs(position) * .2f;
                page.setScaleX(scale);
                page.setScaleY(scale);
            }
        }, View.LAYER_TYPE_NONE);

问题的现象:

ViewPager初始化之后PagerTransformer不会把两侧的View变小,只有拖动之后才能使其生效。

思路:

既然初始时有问题,拖动时没问题,必然是transformPage回调情况存在差异,找出这种差异就能解决初始的问题。

追踪:

首先是拖动时,transformPage方法回调正常,对于位于两侧的View,其position参数不为0,保证这两个View的尺寸产生拉伸效果。

 

初始化时,追踪PagerTransformer的调用栈情况:

 发现transformPage被回调时position参数都为0,再向上追踪到ViewPager的onPageScrolled方法,代码如下:

    @CallSuper
    protected void onPageScrolled(int position, float offset, int offsetPixels) {
        // Offset any decor views if needed - keep them on-screen at all times.
        if (mDecorChildCount > 0) {
            final int scrollX = getScrollX();
            int paddingLeft = getPaddingLeft();
            int paddingRight = getPaddingRight();
            final int width = getWidth();
            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) continue;

                final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
                int childLeft = 0;
                switch (hgrav) {
                    default:
                        childLeft = paddingLeft;
                        break;
                    case Gravity.LEFT:
                        childLeft = paddingLeft;
                        paddingLeft += child.getWidth();
                        break;
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
                                paddingLeft);
                        break;
                    case Gravity.RIGHT:
                        childLeft = width - paddingRight - child.getMeasuredWidth();
                        paddingRight += child.getMeasuredWidth();
                        break;
                }
                childLeft += scrollX;

                final int childOffset = childLeft - child.getLeft();
                if (childOffset != 0) {
                    child.offsetLeftAndRight(childOffset);
                }
            }
        }

        dispatchOnPageScrolled(position, offset, offsetPixels);

        if (mPageTransformer != null) {
            final int scrollX = getScrollX();
            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) continue;
                final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
                mPageTransformer.transformPage(child, transformPos);
            }
        }

        mCalledSuper = true;
    }

发现这个transformPos是根据滚动以及left参数来计算的,断点进去发现left为0。

很显然,调用notifyDataSetChanged之后,会触发ViewPager的setCurrentItemInteral方法,此时还没有及时地layout生成上下左右参数,就回调了transformPage方法,才导致transformPage回调所有子View时position都为0。

此时第一个思路已经出现,就是在layout完之后再触发一下setCurrentItemInteral,进而触发transformPage回调。

阅读下ViewPager的源码,发现transformPage方法只有在onPageScrolled时才会调用,而触发onPageScrolled最方便的方法就是调用:

setCurrentItem(0, false);

方案一:

所以,一个方案就是,初始化之后,立即post一个Runnable去执行notifyDataSetChanged,此时layout已经完成,left已经生成,会回调正确的参数给transformPage方法。不过这个方法实测会产生闪动。

方案二:

回看自己的代码,ViewPager立即setAdapter(空数据),然后请求到网络数据,请求回来后才notifyDataSetChanged,transformPage走的是notifyDataSetChanged的流程。

如果Adapter初始是有数据的,流程会有不同吗?

下面是Adapter初始有数据时transformPage的回调情况:

这个就好多了,在onLayout中,left参数已经生成,所以会以准确的数据回调transformPage,进而产生拉伸效果。

结论:

使用ViewPager做gallery,在使用PageTransformer时,注意要在第一次setAdapter的时候就把PagerAdapter的相关视图的数据初始化好,以正确地回调PageTransformer。

不要在ViewPager初始化时设置空PagerAdapter然后过段时间再更新Adapter,否则就会产生本文的问题,虽然这应该算是个谷歌的bug。

有其他解法欢迎留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值