Android修行手册-ViewPager定制页面切换以及实现原理剖析

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总
游戏脚本-辅助自动化Android控件全解手册再战Android系列
Scratch编程案例软考全系列Unity3D学习专栏
蓝桥系列ChatGPT和AIGC

👉关于作者

专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材、源码、游戏等)
有什么需要欢迎底部卡片私我,交流让学习不再孤单

在这里插入图片描述

👉实践过程

😜简述及原理

你是不是觉得 ViewPager 默认的切换效果有些平淡?其实,我们可以定制 ViewPager 的页面切换效果。定制 ViewPager 的页面切换效果,只需用到 ViewPager 的一个方法setPageTransformer(boolean reverseDrawingOrder, @Nullable PageTransformer transformer),实现一个接口 PageTransformer
包含 ViewPager2 ,大概看了下源码,动画是由ViewPager2.PageTransformer,其实跟ViewPager.PageTransformer接口一致。
这个接口主要为:

public interface PageTransformer {
    void transformPage(@NonNull View page, float position);
}

其中存在两个参数,比较不好理解,第一个参数page我们可以理解为我们即将要转换的对象,而对于position,我刚开始的理解为page的的当前的位置index,当看到postion是float的时候,我想我猜错了。
虽然我不知道这个position是什么意思,给的解释也是模棱两可的,然后我就打log记录这个position值,大致得出这样的结论:
大致的viewpager效果如下图:
在这里插入图片描述
那么在滑动的过程中:

前一个view的position变化当前view的position变化后一个view的position变化
当前view右滑时-1 ----> 00-------->11 ----> +∞
当前view左滑时-∞ ----> -10 -----> -11 ------->0

我们用动图模拟一下此时的三个view的position的动态变化:
当我们向右移动时:
在这里插入图片描述
当我们向左移动时:
在这里插入图片描述
我们模拟viewpager的滑动,此时可以看到三个position的趋势与上表是一致的,因此对于这个position我可以这样解释:

当前我们的viewpager存在一个currentItem,就是当前的current position位置,我们记录此时的坐标轴为0,那么向右移动时,前一个view的position也是像右移动的,只是它的坐标是由-1慢慢变大到0的,这种position的值是一个相对值,是相对于当前curerntItem的坐标位置的相对值;同理右边的view也会向右移动,只是它的相对值由1慢慢变得无限大。
同理,我们往左滑动时,这个position也是一个有方向的相对值。

还记得我们比较喜欢设置viewpager.setOffscreenPageLimit,它的意思就是屏幕之外的view保留几个,我们也称之为缓存view,其实这个limit的个数limitN与viewpager应该保持view的Count的关系为:

Count = limitN * 2 + 1

即需要viewPager保存(limitN * 2 + 1)个缓存状态view。为什么扯到这个东西呢?很简单,如果我们将setOffscreenPageLimit设置为2,那么

void transformPage(@NonNull View page, float position);

这个方法中将会有5中不同的数据回调,分别是:
在这里插入图片描述
我们做个测试,将view加上id:

        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container, int position) {
            ImageView iv = new ImageView(getApplicationContext());
            iv.setScaleType(ImageView.ScaleType.FIT_XY);
            
           	// 将id设置为 10000 + 当前的position
            iv.setId(10000 + position);
            
            ImageUtils.loadImage(imageList.get(position),iv);
            container.addView(iv);

            return iv;
        }

然后我们在滑动的时候,打印一下日志:

    @Override
    public void transformPage(@NonNull View page, float position) {
        Log.e("TAG", "page:" + page.getId() + "," + position);
    }

在这里插入图片描述
我们看到的确存在5个类型的page值,说明我们的推断是正确的。

😜clipChildren属性

clipChildren属性表示是否限制子控件在该容器所在的范围内,clipChildren属性配合layout_gravity属性,可以用来设置多余部分的显示位置,我这里举一个简单的例子,比如喜马拉雅FM这个应用的首页:

大家注意看这个应用底部导航栏中中间一个是要比另外四个高的,这种效果很多人就会想到使用一个RelativeLayout布局来实现,其实不用那么麻烦,这种效果一个clipChildren属性就能实现,示例Demo如下:

代码:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:clipChildren="false"    tools:context="org.lenve.clipchildren.MainActivity">     <LinearLayout        android:layout_width="match_parent"        android:layout_height="48dp"        android:layout_alignParentBottom="true"        android:background="#03b9fc"        android:orientation="horizontal">         <ImageView            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="1"            android:src="@mipmap/ic_launcher"/>         <ImageView            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="1"            android:src="@mipmap/ic_launcher"/>         <ImageView            android:layout_width="0dp"            android:layout_height="72dp"            android:layout_gravity="bottom"            android:layout_weight="1"            android:src="@mipmap/ic_launcher"/>         <ImageView            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="1"            android:src="@mipmap/ic_launcher"/>         <ImageView            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="1"            android:src="@mipmap/ic_launcher"/>    </LinearLayout></RelativeLayout>

大家看只需要在根节点添加clipChildren属性,然后在第三个ImageView上添加layout_gravity属性即可,layout_gravity属性值为bottom表示控件大小超出后控件底部对齐。效果如下:

OK,上面是对clipChildren属性一个简单介绍,算是一个铺垫,接下来我们来看看ViewPager。

😜ViewPager结合CardView

那么在这之前,我想先介绍一个属性,那就是clipToPadding,这个属性是什么意思呢?它表示是否允许ViewGroup在ViewGroup的padding中进行绘制,默认情况下该属性的值为true,即不允许在ViewGroup的padding中进行绘制。那如果我设置了false呢?我们来看看:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="org.lenve.myviewpagercards2.MainActivity">     <android.support.v4.view.ViewPager        android:id="@+id/viewpager"        android:layout_width="match_parent"        android:layout_height="200dp"        android:clipToPadding="false"        android:paddingBottom="24dp"        android:paddingLeft="48dp"        android:paddingRight="48dp"        android:paddingTop="24dp"></android.support.v4.view.ViewPager></RelativeLayout>

ViewPager的Adapter如下:

public class MyAdapter extends PagerAdapter {    private List<Integer> list;    private Context context;     public MyAdapter(Context context, List<Integer> list) {        this.context = context;        this.list = list;    }     @Override    public int getCount() {        return list.size();    }     @Override    public boolean isViewFromObject(View view, Object object) {        return view == object;    }     @Override    public Object instantiateItem(ViewGroup container, int position) {        ImageView iv = new ImageView(context);        iv.setImageResource(list.get(position));        container.addView(iv);        return iv;    }     @Override    public void destroyItem(ViewGroup container, int position, Object object) {        container.removeView((View) object);    }}

Activity中的代码:

        ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);        List<Integer> list = new ArrayList<>();        list.add(R.drawable.p001);        list.add(R.drawable.p002);        list.add(R.drawable.p003);        list.add(R.drawable.p004);        list.add(R.drawable.p005);        MyAdapter adapter = new MyAdapter(this, list);        viewPager.setAdapter(adapter);        viewPager.setPageMargin(20);

显示效果如下:

OK,那这个clipToPadding属性是我们在一个页面中显示多个ViewPager item的第二种方式。这个CardView式的ViewPager我们就使用这种方式来实现。先来看看效果图:

整体思路和上文其实是一致的,我们来看看activity的布局:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="org.lenve.myviewpagercards2.MainActivity">     <android.support.v4.view.ViewPager        android:id="@+id/viewpager"        android:layout_width="match_parent"        android:layout_height="300dp"        android:clipToPadding="false"        android:paddingBottom="24dp"        android:paddingLeft="80dp"        android:paddingRight="80dp"        android:paddingTop="24dp"></android.support.v4.view.ViewPager></RelativeLayout>

ViewPager中每一个item的布局:

<?xml version="1.0" encoding="utf-8"?><android.support.v7.widget.CardView android:id="@+id/cardview"                                    xmlns:android="http://schemas.android.com/apk/res/android"                                    xmlns:app="http://schemas.android.com/apk/res-auto"                                    android:layout_width="match_parent"                                    android:layout_height="wrap_content"                                    android:orientation="vertical"                                    app:cardCornerRadius="10dp">     <RelativeLayout        android:layout_width="match_parent"        android:layout_height="300dp">         <TextView            android:id="@+id/tv"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_centerInParent="true"            android:gravity="center"            android:text="我是一个TextView"/>         <Button            android:layout_width="96dp"            android:layout_height="36dp"            android:textColor="#ffffff"            android:layout_below="@id/tv"            android:layout_centerHorizontal="true"            android:layout_marginTop="12dp"            android:background="@color/colorAccent"            android:text="我是一个按钮"/>    </RelativeLayout></android.support.v7.widget.CardView>

Adapter:

public class MyAdapter extends PagerAdapter {    private List<Integer> list;    private Context context;    private LayoutInflater inflater;     public MyAdapter(Context context, List<Integer> list) {        this.context = context;        this.list = list;        inflater = LayoutInflater.from(context);    }    @Override    public int getCount() {        return list.size();    }     @Override    public boolean isViewFromObject(View view, Object object) {        return view == object;    }     @Override    public Object instantiateItem(ViewGroup container, int position) {        View view = inflater.inflate(R.layout.vp_item, container, false);        container.addView(view);        return view;    }     @Override    public void destroyItem(ViewGroup container, int position, Object object) {        container.removeView((View) object);    }}

Activity中的代码:

ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);        List<Integer> list = new ArrayList<>();        list.add(R.drawable.p001);        list.add(R.drawable.p002);        list.add(R.drawable.p003);        list.add(R.drawable.p004);        list.add(R.drawable.p005);        MyAdapter adapter = new MyAdapter(this, list);        viewPager.setAdapter(adapter);        viewPager.setPageMargin((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,                48, getResources().getDisplayMetrics()));        viewPager.setPageTransformer(false, new ScaleTransformer(this));

最后再来看看我们定义的PageTransformer:

public class ScaleTransformer implements ViewPager.PageTransformer {    private Context context;    private float elevation;     public ScaleTransformer(Context context) {        this.context = context;        elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,                20, context.getResources().getDisplayMetrics());    }     @Override    public void transformPage(View page, float position) {        if (position < -1 || position > 1) {         } else {            if (position < 0) {                ((CardView) page).setCardElevation((1 + position) * elevation);            } else {                ((CardView) page).setCardElevation((1 - position) * elevation);            }        }    }}

很简单,我只是对CardView的阴影做了处理 ,其他属性都没改,这样就有了我们刚才看到的效果。

😜例子和效果

1、手风琴效果

手风琴效果
其实就是水平缩放效果。在页面滑动时,
左边页面 position < 0,右边页面 position > 0;
左边页面以页面右边缘为缩放中心,右边页面以左边缘为缩放中心。
代码如下所示:

/**
 * 手风琴效果(水平方向缩放)
 */
public class AccordionTransformer implements ViewPager.PageTransformer {


    @Override
    public void transformPage(@NonNull View page, float position) {
        if (position < 0f) {
            page.setPivotX(page.getWidth());
            page.setScaleX(1f + position * 0.5f);
        } else if (position < 1f) {
            page.setPivotX(0f);
            page.setScaleX(1f - position * 0.5f);
        }
    }
}

2、下弧形效果

下弧形
实现此效果需以页面下边缘某一点为旋转中心旋转:

  • position < -1 时,旋转到最大角度,旋转中心为右下角;
  • -1 < position < 0 时,position 越靠近 0 ,旋转角度越小,旋转中心向下边缘中心靠拢;
  • 0 <= position <= 1 时,position 越靠近 0 ,旋转角度越小,旋转中心向下边缘中心靠拢;
  • position > 1 时,旋转到最大角度,旋转中心为左下角。

代码如下所示:

/**
 *  下弧形切换效果
 */
public class ArcDownTransformer implements ViewPager.PageTransformer {

    private static final float DEF_MAX_ROTATE = 12.0f;
    private float mMaxRotate = DEF_MAX_ROTATE;



    @Override
    public void transformPage(@NonNull View page, float position) {
        page.setPivotY( page.getHeight());
        if (position < -1f) {//[-Infinity, -1)
            page.setRotation(-mMaxRotate);
            page.setPivotX(page.getWidth());
        } else if (position <= 1f) {//[-1, 1]
            if (position < 0f) {//[-1, 0)
                page.setRotation(mMaxRotate * position);
                page.setPivotX(page.getWidth() * (0.5f - 0.5f * position));
            } else { //[0, 1]
                page.setRotation(mMaxRotate * position);
                page.setPivotX(page.getWidth() * (0.5f - 0.5f * position));
            }
        } else {//(1, +Infinity]
            page.setRotation(mMaxRotate);
            page.setPivotX(0f);
        }
    }
}

3、上弧形效果

上弧形
与下弧形相反,旋转中心以上边缘某一点:

  • position < -1 时,旋转到最大角度,旋转中心为右下角;
  • -1 < position < 0 时,position 越靠近 0 ,旋转角度越小,旋转中心向上边缘中心靠拢;
  • 0 <= position <= 1 时,position 越靠近 0 ,旋转角度越小,旋转中心向上边缘中心靠拢;
  • position > 1 时,旋转到最大角度,旋转中心为左下角。

代码如下:

/**
 * 上弧形切换效果
 */
public class ArcUpTransformer implements ViewPager.PageTransformer {

    private static final float DEF_MAX_ROTATE = 12.0f;
    private float mMaxRotate = DEF_MAX_ROTATE;

    @Override
    public void transformPage(@NonNull View page, float position) {
        page.setPivotY(0f);
        if (position < -1f) {//[-Infinity, -1)
            page.setRotation(mMaxRotate);
            page.setPivotX(page.getWidth());
        } else if (position <= 1f) {//[-1, 1]
            if (position < 0f) {//[-1, 0)
                page.setRotation(-mMaxRotate * position);
                page.setPivotX(page.getWidth() * (0.5f - 0.5f * position));
            } else { //[0, 1]
                page.setRotation(-mMaxRotate * position);
                page.setPivotX(page.getWidth() * (0.5f - 0.5f * position));
            }
        } else {//(1, +Infinity]
            page.setRotation(-mMaxRotate);
            page.setPivotX(0f);
        }
    }
}

4、立方翻转-外

立方翻转-外
其实是绕 Y 轴旋转,再加上缩放效果。绕 Y 轴旋转,用到了 View 的 setRotationY(float) 方法,此方法可以设置绕 Y 轴的旋转角度。

  • position < -1,逆时针旋转到最大角度,旋转中心为页面右边缘;
  • -1 <= position < 0,旋转中心为页面右边缘,position 越靠近 0,旋转角度越小,页面先缩小后放大,缩放值是关于 position 开口向上 对称线为 position = -0.5 的抛物线;
  • 0 <= position <= 1,旋转中心为左边缘,position 越靠近 0,旋转角度越小,页面先缩小后放大,缩放值是关于 position 开口向上 对称线为 position = 0.5 的抛物线;
  • position > 1,顺时针旋转到最大角度,旋转中心为左边缘。

引入抛物线计算缩放值,是为了让页面在滑动到一半(position 为 -0.5 和 0.5)时,缩放到最小。

需要注意 的是镜头距离,即图像与屏幕距离,距离较小时,旋转时会有较大的失真,效果很差,需要设置一下镜头距离(Camera Distance,参考 View#setCameraDistance(float))。

代码如下:

/**
 * 立方体翻转效果
 */
public class CubicOverturnTransformer implements ViewPager.PageTransformer {

    public static final float DEFAULT_MAX_ROTATION = 60f;
    public static final float DEF_MIN_SCALE = 0.86f;

    /**
     * 最大旋转角度
     */
    private float mMaxRotation = DEFAULT_MAX_ROTATION;

    /**
     * 最小缩放
     */
    private float mMinScale = DEF_MIN_SCALE;


    public CubicOverturnTransformer() {
        this(DEFAULT_MAX_ROTATION);
    }

    public CubicOverturnTransformer(float maxRotation) {
        this(maxRotation, DEF_MIN_SCALE);
    }

    public CubicOverturnTransformer(float maxRotation, float minScale) {
        mMaxRotation = maxRotation;
        this.mMinScale = minScale;
    }


    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    public void transformPage(@NonNull View page, float position) {
        page.setPivotY(page.getHeight() / 2f);

        float distance = getCameraDistance();
        page.setCameraDistance(distance);//设置 View 的镜头距离,可以防止旋转大角度时出现图像失真或不显示。
        if (position < -1) { // [-Infinity,-1)
            page.setRotationY(-mMaxRotation);
            page.setPivotX(page.getWidth());
        } else if (position <= 1) { // [-1,1]

            page.setRotationY(position * mMaxRotation);
            if (position < 0) {//[0,-1]
                page.setPivotX(page.getWidth());
                float scale = DEF_MIN_SCALE + 4f * (1f - DEF_MIN_SCALE) * (position + 0.5f) * (position + 0.5f);
                page.setScaleX(scale);
                page.setScaleY(scale);
            } else {//[1,0]
                page.setPivotX(0);
                float scale = DEF_MIN_SCALE + 4f * (1f - DEF_MIN_SCALE) * (position - 0.5f) * (position - 0.5f);
                page.setScaleX(scale);
                page.setScaleY(scale);
            }
        } else { // (1,+Infinity]
            page.setRotationY(mMaxRotation);
            page.setPivotX(0);
        }
    }

    /**
     * 获得镜头距离(图像与屏幕距离)。参考{@link View#setCameraDistance(float)},小距离表示小视角,
     * 大距离表示大视角。这个距离较小时,在 3D 变换(如围绕X和Y轴的旋转)时,会导致更大的失真。
     * 如果改变 rotationX 或 rotationY 属性,使得此 View 很大 (超过屏幕尺寸的一半),则建议始终使用
     * 大于此时图高度 (X 轴旋转)或 宽度(Y 轴旋转)的镜头距离。
     * @return  镜头距离 distance
     *
     * @see {@link View#setCameraDistance(float)}
     */
    private float getCameraDistance() {
        DisplayMetrics displayMetrics = VpsApplication.getAppContext().getResources().getDisplayMetrics();
        float density = displayMetrics.density;
        int widthPixels = displayMetrics.widthPixels;
        int heightPixels = displayMetrics.heightPixels;
        return 1.5f*Math.max(widthPixels, heightPixels)*density;
    }
}

使用:

mPageTransformer = new CubicOverturnTransformer(90f, 0.6f);
mVpImgs.setPageTransformer(reverseDrawingOrder, mPageTransformer);

最大旋转角度 90 度,最小缩放到 0.6 。

5、立方翻转-内

立方翻转-内

与上一个效果是同一套代码,绕 Y 轴旋转方向相反,使用时,让 最大旋转角度小于 0 即可,如下代码所示,最大旋转角度改为 -90f 就是内部翻转:

使用:

mPageTransformer = new CubicOverturnTransformer(-90f, 0.6f);
mVpImgs.setPageTransformer(reverseDrawingOrder, mPageTransformer);

最大旋转角度 90 度,最小缩放到 0.6 。

6、下沉效果

下沉效果

特殊的缩放效果,有最小缩放值,页面缩到最小值不再缩小,同时有透明度的变化(可以去掉透明度变化)。
旋转中心位置的调整主要是为了调整页面间隙。

/**
 * 下沉效果
 */
public class DipInTransformer implements ViewPager.PageTransformer {


    private static final float MIN_SCALE = 0.85f;
    private float mMinScale = MIN_SCALE;
    private static final float MIN_ALPHA = 0.5f;
    private float mMinAlpha = MIN_ALPHA;

    @Override
    public void transformPage(@NonNull View page, float position) {
        Log.i("DipInTransformer", "transformPage: id = " + page.getId() + ", position = " + position);
        int pageWidth = page.getWidth();
        int pageHeight = page.getHeight();
//        page.setPivotX(pageWidth * 0.5f);
        page.setPivotY(pageHeight * 0.5f);
        if (position < -1f) {//(-Infinity, -1]
            page.setAlpha(mMinAlpha);
            page.setScaleX(mMinScale);
            page.setScaleY(mMinScale);
            page.setPivotX(pageWidth*1f);
        } else if (position <= 1f) {//(-1, 1)
            float scaleFactor = Math.max(mMinScale, 1 - Math.abs(position));

            page.setScaleX(scaleFactor);
            page.setScaleY(scaleFactor);
            if (position < 0) {
                page.setPivotX(pageWidth * (0.5f + 0.5f * scaleFactor));

            } else {
                page.setPivotX(pageWidth * (0.5f - 0.5f * scaleFactor));
            }

            page.setAlpha(mMinAlpha + (scaleFactor - mMinScale) / (1f - mMinScale) * (1f - mMinAlpha));
        } else {//(1, +Infinity)
            page.setAlpha(mMinAlpha);
            page.setScaleX(mMinScale);
            page.setScaleY(mMinScale);
            page.setPivotX(pageWidth * 0f);
        }
    }
}

7、淡入淡出效果(透明度)

淡入淡出(透明度)
不多作解释,就是透明度属性动画。代码如下:

/**
 * 淡入淡出
 */
public class FadeInOutTransformer implements ViewPager.PageTransformer {

    private static final float DEF_MIN_ALPHA =0.5f;
    private float mMinAlpha = DEF_MIN_ALPHA;

    @Override
    public void transformPage(@NonNull View page, float position) {
        if (position < -1f) {//[-Infinity, -1)
            page.setAlpha(mMinAlpha);
        } else if (position <= 1f) {//[-1, 1]
            if (position < 0f) {//[-1, 0)
                page.setAlpha(1f + (1f - mMinAlpha) * position);
            } else { //[0, 1]
                page.setAlpha(1f - (1f - mMinAlpha) * position);
            }
        } else {//(1, +Infinity]
            page.setAlpha(mMinAlpha);
        }
    }
}

8、水平翻转效果(左右翻转)

左右翻转

即以水平中心线为旋转中心,绕 Y 轴旋转。代码如下所示,同样要注意镜头距离:

/**
 * 水平翻转效果
 */
public class FlipHorizontalTransformer implements ViewPager.PageTransformer {

    @Override
    public void transformPage(@NonNull View page, float position) {

        page.setCameraDistance(getCameraDistance());
        page.setTranslationX(-page.getWidth() * position);

        float rotation = 180f * position;
        page.setAlpha(rotation > 90f || rotation < -90f ? 0f : 1f);
        page.setPivotX(page.getWidth() * 0.5f);
        page.setPivotY(page.getHeight() * 0.5f);
        page.setRotationY(rotation);

        if (position > -0.5f && position < 0.5f) {
            page.setVisibility(View.VISIBLE);
        } else {
            page.setVisibility(View.INVISIBLE);
        }
    }


    private float getCameraDistance() {
        DisplayMetrics displayMetrics = VpsApplication.getAppContext().getResources().getDisplayMetrics();
        float density = displayMetrics.density;
        int widthPixels = displayMetrics.widthPixels;
        int heightPixels = displayMetrics.heightPixels;
        return 1.5f * Math.max(widthPixels, heightPixels) * density;
    }
}

9、竖直翻转效果(上下翻转)

上下翻转

即以竖直中心线为旋转中心,绕 X 轴旋转。代码如下所示,同样要注意镜头距离:

/**
 * 竖直翻转效果
 */
public class FlipVerticalTransformer implements ViewPager.PageTransformer {

    @Override
    public void transformPage(@NonNull View page, float position) {

        page.setCameraDistance(getCameraDistance());
        page.setTranslationX(-page.getWidth() * position);

        float rotation = 180f * position;
        page.setAlpha(rotation > 90f || rotation < -90f ? 0f : 1f);

        page.setPivotX(page.getWidth() * 0.5f);
        page.setPivotY(page.getHeight() * 0.5f);
        page.setRotationX(rotation);

        if (position > -0.5f && position < 0.5f) {
            page.setVisibility(View.VISIBLE);
        } else {
            page.setVisibility(View.INVISIBLE);
        }
    }


    private float getCameraDistance() {
        DisplayMetrics displayMetrics = VpsApplication.getAppContext().getResources().getDisplayMetrics();
        float density = displayMetrics.density;
        int widthPixels = displayMetrics.widthPixels;
        int heightPixels = displayMetrics.heightPixels;
        return 1.5f * Math.max(widthPixels, heightPixels) * density;
    }
}

10、浮出效果

浮出效果

让所有右边页面都移动到 正中位置,从右向左滑动切换页面时,左边从右向左滑出,右边页面放大淡入。

/**
 * 浮出效果
 */
public class RiseInTransformer implements ViewPager.PageTransformer {

    private static final float DEF_MIN_SCALE = 0.72f;

    private float mMinScale = DEF_MIN_SCALE;

    private static final float DEF_MIN_ALPHA = 0.5f;

    public RiseInTransformer() {
    }

    public RiseInTransformer(float minScale) {
        this.mMinScale = minScale;
    }

    public float getMinScale() {
        return mMinScale;
    }

    public void setMinScale(float minScale) {
        this.mMinScale = minScale;
    }

    @Override

    public void transformPage(@NonNull View page, float position) {

        if (position < 0f) {
            page.setTranslationX(0f);
        } else if (position <= 1) {
            page.setTranslationX(-position * page.getWidth());
            page.setScaleX(1f - (1f - mMinScale) * position);
            page.setScaleY(1f - (1f - mMinScale) * position);
            page.setAlpha(1f - (1f - DEF_MIN_ALPHA) * position);
        } else {
            page.setTranslationX(-position * page.getWidth());
            page.setScaleX(mMinScale);
            page.setScaleY(mMinScale);
            page.setAlpha(DEF_MIN_ALPHA);

        }

    }
}

调用时需要反转绘制顺序,即 reverseDrawingOrder = true,使左边页面先绘制,右边页面后绘制,否则效果无法实现。

int reverseDrawingOrder  = true;
mVpImgs.setPageTransformer(reverseDrawingOrder  , mPageTransformer);

11、下潜效果

下潜效果

让所有左边页面都移动到 正中位置,切换页面时,左边页面缩小淡出,右面页面从右向左滑入正中。

/**
 * 下潜效果
 */
public class DiveOutTransformer implements ViewPager.PageTransformer {

    private static final float DEF_MIN_SCALE = 0.72f;

    private float mMinScale = DEF_MIN_SCALE;

    private static final float DEF_MIN_ALPHA = 0.5f;

    public DiveOutTransformer() {
    }

    public DiveOutTransformer(float minScale) {
        this.mMinScale = minScale;
    }

    public float getMinScale() {
        return mMinScale;
    }

    public void setMinScale(float minScale) {
        this.mMinScale = minScale;
    }

    @Override

    public void transformPage(@NonNull View page, float position) {

        if (position < -1f) {
            page.setScaleX(mMinScale);
            page.setScaleY(mMinScale);
            page.setAlpha(DEF_MIN_ALPHA);
            page.setTranslationX(-position * page.getWidth());
        } else if (position <= 0) {
            page.setTranslationX(-position * page.getWidth());
            page.setScaleX(1f + (1f - mMinScale) * position);
            page.setScaleY(1f + (1f - mMinScale) * position);
            page.setAlpha(1f + (1f - DEF_MIN_ALPHA) * position);
        } else {
            page.setTranslationX(0f);
        }

    }


}

调用时不需要反转绘制顺序,即 reverseDrawingOrder = false,使右边页面先绘制,左边页面后绘制,否则效果无法实现。

int reverseDrawingOrder  = false;
mVpImgs.setPageTransformer(reverseDrawingOrder  , mPageTransformer);

12、堆叠效果

堆叠效果

所有右边页面移动到正中位置,即 0 位置,滑动时,把最上面页面滑掉,代码如下所示:

/**
 * 堆叠效果
 */
public class StackTransformer implements ViewPager.PageTransformer {
    @Override
    public void transformPage(@NonNull View page, float position) {
        page.setTranslationX(position < 0 ? 0f : -page.getWidth() * position);
    }
}

调用时需要反转绘制顺序,即 reverseDrawingOrder = true,使左边页面先绘制,右边页面后绘制,否则效果无法实现。

int reverseDrawingOrder  = true;
mVpImgs.setPageTransformer(reverseDrawingOrder  , mPageTransformer);

13、缩放效果

缩放效果
比较简单,与淡入淡出效果类似,不多作解释:

/**
 * 缩放效果
 */
public class ZoomInOutTransformer implements ViewPager.PageTransformer {

    private static final float DEF_MIN_SCALE = 0.9f;
    private float mMinScale = DEF_MIN_SCALE;


    @Override
    public void transformPage(@NonNull View page, float position) {
        if (position < -1f) {//[-Infinity, -1)
            page.setScaleX(mMinScale);
            page.setScaleY(mMinScale);
        } else if (position <= 1f) {//[-1, 1]
            if (position < 0f) {//[-1, 0)
                page.setScaleX(1f + (1f - mMinScale) * position);
                page.setScaleY(1f + (1f - mMinScale) * position);
            } else { //[0, 1]
                page.setScaleX(1f - (1f - mMinScale) * position);
                page.setScaleY(1f - (1f - mMinScale) * position);
            }
        } else {//(1, +Infinity]
            page.setScaleX(mMinScale);
            page.setScaleY(mMinScale);
        }
    }
}

14、并行覆盖效果

并行覆盖效果

通过调用 View 的 setScrollX() 方法,使页面内容随着 position 移动。

public class ParallaxTransformer implements ViewPager.PageTransformer {
    @Override
    public void transformPage(@NonNull View page, float position) {
        int width = page.getWidth();
        if (position <= -1f) {
            page.setScrollX(0);
        } else if (position < 1f) {

            if (position < 0f) {
                page.setScrollX((int) (width * 0.75f * position));
            } else {
                page.setScrollX((int) (width * 0.75f * position));
            }

        } else {
            page.setScrollX(0);
        }
    }
}

调用时需要反转绘制顺序,即 reverseDrawingOrder = true,使左边页面先绘制,右边页面后绘制,否则效果无法实现。

int reverseDrawingOrder  = true;
mVpImgs.setPageTransformer(reverseDrawingOrder  , mPageTransformer);

👉其他

📢作者:小空和小芝中的小空
📢转载说明-务必注明来源:https://zhima.blog.csdn.net/
📢这位道友请留步☁️,我观你气度不凡,谈吐间隐隐有王者霸气💚,日后定有一番大作为📝!!!旁边有点赞👍收藏🌟今日传你,点了吧,未来你成功☀️,我分文不取,若不成功⚡️,也好回来找我。

温馨提示点击下方卡片获取更多意想不到的资源。
空名先生

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/188190.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

论文阅读——Prophet(cvpr2023)

一、Framework 这个模型分为两阶段&#xff1a;一是答案启发生成阶段&#xff08;answer heuristics generation stage&#xff09;&#xff0c;即在一个基于知识的VQA数据集上训练一个普通的VQA模型&#xff0c;产生两种类型的答案启发&#xff0c;答案候选列表和答案例子&am…

强化学习,快速入门与基于python实现一个简单例子(可直接运行)

文章目录 一、什么是“强化学习”二、强化学习包括的组成部分二、Q-Learning算法三、迷宫-强化学习-Q-Learning算法的实现全部代码&#xff08;复制可用&#xff09;可用状态空间检查是否超出边界epsilon 的含义更新方程 总结 一、什么是“强化学习” 本文要记录的大概内容&am…

图形编辑器开发:缩放和旋转控制点

大家好&#xff0c;我是前端西瓜哥。好久没写图形编辑器开发的文章了。 今天来讲讲控制点。它是图形编辑器的不可缺少的基础功能。 控制点是吸附在图形上的一些小矩形和圆形点击区域&#xff0c;在控制点上拖拽鼠标&#xff0c;能够实时对被选中进行属性的更新。 比如使用旋…

LED面板显示屏驱动芯片

一、基本概述 TM1638是一种带键盘扫描接口的LED&#xff08;发光二极管显示器&#xff09;驱动控制专用IC,内部集成有MCU数字接口、数据锁存器、LED驱动、键盘扫描等电路。本产品质量可靠、稳定性好、抗干扰能力强。 二、主要应用场合 主要适用于家电设备(智能热水器、微波炉…

Hibernate 脏检查和刷新缓存机制

刷新缓存: Session是Hibernate向应用程序提供的操作数据库的主要接口,它提供了基本的保存,更新,删除和加载java对象的方法,Session具有一个缓存,可以管理和追踪所有持久化对象,对象和数据库中的相关记录对应,在某些时间点,Session会根据缓存中对象的变化来执行相关SQL语句,将对…

杂货铺 | Windows系统上解压缩tgz文件

文章目录 &#x1f4da;快速终端打开实现 & 解压缩实现步骤&#x1f4da;环境变量的一般配置步骤 & 问题解决思路 &#x1f4da;快速终端打开实现 & 解压缩实现步骤 将对应的tgz文件放入对应的文件夹。快速在指定文件夹下打开终端 打开对应的路径 双击地址栏 然后…

Cisco Packet Tracer配置命令——路由器篇

路由基础 路由器用于互联两个或多个网络&#xff0c;具有两项功能&#xff1a;为要转发的数据包选择最佳路径以及将数据包交换到正确的端口&#xff0c;概括为路由选择和分组转发。 路由选择 路由选择就是路由器根据目的IP地址的网络地址部分&#xff0c;通过路由选择算法确…

图的建立基本操作

#include <stdio.h> #include <stdlib.h> #include <stdbool.h> // 添加头文件#define MAX_VERTEX_NUM 100 //图中最大顶点数//struct ArcNode* nextarc; //ArcNode* firstarc; //这两个是很有必要的&#xff0c;如果你没有这两个指针&#xff0c;你就无法判…

力扣114. 二叉树展开为链表(java,用树模拟链表)

Problem: 114. 二叉树展开为链表 文章目录 题目描述思路解题方法复杂度Code 题目描述 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 1.展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左…

Selenium 4.11 正式发布--再也不用手动更新chrome driver 了

Selenium 4.11.0 正式发布了&#xff0c;先来看一下主要特性。 Chrome DevTools支持的版本现在是&#xff1a;v113、v114和v115&#xff08;Firefox仍然对所有版本使用v85&#xff09; 通过Selenium Manager支持Chrome For Testing&#xff08;CfT&#xff09; Selenium Manag…

LeetCode Hot100 105.从前序与中序遍历序列构造二叉树

题目&#xff1a;给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 代码&#xff1a; class Solution {private Map<Integer, Integer> indexM…

midjourney过时了?如何使用基于LCM的绘图技术画出你心中的画卷。

生成 AI 艺术在近年来迅速发展&#xff0c;吸引了数百万用户。然而&#xff0c;传统的生成 AI 艺术需要等待几秒钟或几分钟才能生成&#xff0c;这对于快节奏的现代社会来说并不理想。 近日&#xff0c;中国清华大学和 AI 代码共享平台 HuggingFace 联合开发了一项新的机器学习…

性能压测工具:wrk

一般我们压测的时候&#xff0c;需要了解衡量系统性能的一些参数指标&#xff0c;比如。 1、性能指标简介 1.1 延迟 简单易懂。green:一般指响应时间 95线&#xff1a;P95。平均100%的请求中95%已经响应的时间 99线&#xff1a;P99。平均100%的请求中99%已经响应的时间 平…

2023年亚太杯数学建模A题水果采摘机器人的图像识别功能(matlab 部分代码)

对于1-4问针对的是附录1 中的数据 clc; close all; clear; % 图像文件夹路径 folder_path E:/新建文件夹/yatai/Attachment/Attachment 1/; % 图像文件列表 image_files dir(fullfile(folder_path, *.jpg)); % 假设所有图片都是jpg格式% 解析文件名中的数字&#xff0c;并转…

2023年汉字小达人市级比赛在线模拟题的使用顺序、建议和常见问题

今天是2023年11月25日&#xff0c;星期六&#xff0c;上午举办了2023年第八届上海小学生古诗文大会的复选活动&#xff08;复赛&#xff09;&#xff0c;结束了复选活动&#xff0c;很多学霸孩子们马上就开始投入到第十届汉字小达人的市级活动&#xff08;市级比赛&#xff09;…

STM32 配置中断常用库函数

单片机学习 目录 一、配置AFIO相关库函数 1.1函数GPIO_AFIODeInit 1.2函数GPIO_EventOutputConfig 1.3函数GPIO_EventOutputCmd 1.4函数GPIO_EXTILineConfig 二、配置EXTI相关库函数 2.1函数EXTI_DeInit 2.2函数EXTI_Init 2.3函数EXTI_StructInit 2.4函数 EXTI_Gener…

python_接口自动化测试框架

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

02_MySQL体系结构及数据文件介绍

#课程目标 了解MySQL的体系结构了解MySQL常见的日志文件及作用了解事务的控制语句&#xff0c;提交和回滚能够查看当前数据库的版本和用户了解MySQL数据库如何存放数据能在使用SQL语句创建、删除数据库 #一、MySQL的体系结构 ##1、客户端(连接者) MySQL的客户端可以是某个客户…

vue2-006——使用脚手架搭建vue2项目+项目结构分析

一、创建项目&#xff1a;vue create 项目名 D:\EnyiWang\Documents\myStudy\vue>vue create vue_testVue CLI v5.0.8 ? Please pick a preset: Default ([Vue 2] babel, eslint)Vue CLI v5.0.8 ✨ Creating project in D:\EnyiWang\Documents\myStudy\vue\vue_test. &am…

Java基准测试工具JMH的简介与使用

JMH是一套Java基准测试工具&#xff0c;用于对Java执行进行基准测试以及生成测试报告。平时应用于Java一些基础Api或者一些工具类这种离开网络因素的纯系统测试。 使用方式 maven引入&#xff1a; <dependency><groupId>org.openjdk.jmh</groupId><art…