ViewPager(六)让ViewPager用起来更顺滑——设置间距与添加转场动画

用法概述:

1、换页监听与换页方法
2、懒加载及预加载定制
3、设置间距与添加转场动画
4、轮播、禁止滑动与指示器的配合

这篇和下一篇都是偏向技巧的东西,对于前端开发者来讲,开发的应用是直接面对用户的,用户体验和个性化设计就显得非常重要,同样的一个控件,可能需要你在不同的应用中呈现不同的效果。对于ViewPager来讲,基本上每个应用都会用到的高频控件,而且地位和重要性都比较靠前。要想玩出花来,就是要对它有足够的认识,转场动画就是一项技能,类似我们页面进入退出的转场一样,ViewPager为每一个子View预留了灵活的动画扩展接口。这样只要我们掌握了规则,加上自己的一些创意和算法的相关知识,就能设计出丰富多彩的转场动画来。
本篇博客主要讲的是转场动画,但是有时候却也离不开对子View的间距的设置和控制。所以在正式介绍转场之前,我们有必要介绍一下margin的用法。

设置间距和添加转场动画

造成间距通常是两个变量导致的,一个是padding,一个是margin。而严格意义margin更能体现这个意义。而padding只是子View在自己的地盘上做了偏移。这里我们对ViewPager设置padding,会让子View都会在一个更小的空间里显示,也就是这时候的padding会压缩子View的显示空间(如果对子View设置padding会产生不一样的效果,下边的例子会说)。margin可以实现我们我们队子View的间距调整,这得得益于ViewPager暴露的公开方法,让我们设置。转场动画的实现方式是注入,这就在想要的时候就能生效。

设置间距

默认的ViewPager是从左到右依次连接所有子View,这样的效果就如同粘在一起的一张抽纸,如果我们想让每个子View之间拉开一定的距离,在一般的XML布局之中,我们通常是设置子View之间的margin,但是ViewPager并没有将子View的layout暴露给我们,那么我们是不是就没办法设置了呢?
答案是否定的!虽然我们不能像在XML中灵活设置margin,但是我们依然可以通过代码调整子View的布局,并且还可以添加一些炫酷或者美观的分界线,通过以下几个方法,通常是一些get,set方法:

int getPageMargin() 获得margin数值
void setPageMargin(int marginPixels) 设置margin相应的像素
setPageMarginDrawable(@Nullable Drawable d) 设置相应的margin的drawable
setPageMarginDrawable(@DrawableRes int resId) 设置相应margin的drawable的resId

在设置之前我们最好获取一下,就用第一个方法,如果新设置的值和获得的值一样,我们就不重新设置像素值得子margin了
第二个方法就是设置具体像素的margin了,注意是像素,所以适配的话你最好通过屏幕密度换算一下对应的dp值
第三个方法和第四个方法其实是一个,是利用重载方便用户加载drawable资源,但这里应该注意一下,如果你单独设置Drawable是不会生效的,必须和setPageMargin一起使用,也就是绘制的具体的间隔drawable高是ViewPager的高,宽就是我们通过setPageMargin设置的宽。所以你不设置像素间距,设置的drawable就会落在子View的下面,那么就不用画了,我们来看ViewPager三段源码来验证下:

public void setPageMargin(int marginPixels) {
   //前边省略
    mPageMargin = marginPixels;
    //后边省略
}

第一个方法我们只保留了关键的给属性赋值

public void setPageMarginDrawable(@Nullable Drawable d) {
    mMarginDrawable = d;
   //后边省略
}

第二个方法也是只保留了关键的给属性赋值

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    // Draw the margin drawable between pages if needed.
//翻译:如果有需要的话,在子页面之间绘制间隔图案
    if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
        //具体绘制drawable省略,这里只分析绘制drawable的启动机制
    }
}

第三个方法大家都很熟悉,没错,真正的drawable就是通过这个方法绘制出来的。

因为是分析为什么只设置drawable不会生效,我们看绘制onDraw方法的条件判断,这句是判断是否绘制的关键,他有四个因素,之间是与的关系,所以必须同时满足才行:
1、间隔的像素值必须大于0,这个值默认就是0,通过setPageMargin设置(见第一段源码)
2、间隔的图案不能为空,这个值默认就是null,通过setPageMarginDrawable设置(见第二段源码)
3、必须要有子View,没有子View就不存在什么子View间隔了,这个好理解
4、适配器必须不为空,不设置适配器也相当于没有子View,所以也没问题

1、2两点证明必须是要一起使用,drawable才能生效

那到这里有童鞋又会有疑问,四个条件如果只设置margin的像素值,而看源码是不执行绘制的,那么应该不会有间隔吧?实际上是有的,因为间隔值不需要绘制,通过布局将距离拉开就好了,你不用绘制任何东西,显示的就是背景色。具体的逻辑在源码分析章节会详细的说,逻辑较复杂。

其实,子View是Fragment的情况,并不需要设置间隔,这样有画蛇添足之嫌。而设置Margin通常是在一个画廊,就是我们经常用到的ViewPager里边包含了很多图片(图片上也可以有相应的文字信息),这样滑动的时候我们希望它能有更出色的交互体验,这里就引出了另一个技巧就是转场动画,其实margin是配合着转场动画使用的,从而可以实现更加丰富多彩的交互方式。下一节我们来介绍转场动画。

添加转场动画

上边已经说过了,ViewPager的一大用途是滑动的相册。默认的ViewPager的滑动动画,就是左右平行移动,就像在拖拉一幅很长很长的画。
我们知道RecyclerView对于子View的处理可以增加动画,那么可以猜测,同为V4
支持包推出的控件ViewPager也应该会有这样优秀的基因。

ViewPager还是将动画交给了一个接口去处理,既然是接口,如果使用就一定要去实现:

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

PageTransformer页面变换器,专注处理动画。

注意:由于ViewPager的转场动画利用的是属性动画的原理,而属性动画在不引入第三方支持包的基础上,是支持3.0及以上的,所以在3.0以下的手机,转场动画会被忽视,并不会报错。

页面变换器只需要我们实现一个方法transformPage即可,我们来分析下这个方法的参数是什么意思:

参数page:这个是你即将把动画赋予的子页面
参数position:一看是个float值,就要和作为int值的它区分开,float是个相对位置,它是一个-1到1的值,相对位置提供给开发者,可以控制动画进度和方式。具体的:0的时候是要执行动画的页面处于页面前端并居中的位置,1是要执行动画的页面向右移动到看不见(宽度为一整个页面),-1是要执行的动画向左移动到看不见的位置(宽度为一整个页面),正好对应一个进入动画,一个退出动画。
position位置

既然是属性动画,而每个子item的父类都是View,所以这里罗列一些设置属性动画的方法:

setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) 透明度
setTranslationX(float translationX) X轴平移
setTranslationY(float translationY) Y轴平移
setTranslationZ(float translationZ) Z轴平移
setRotation(float rotation) 设置相对中心点的旋转角度,正值按照顺时针转动
setRotationX(float rotationX) 设置相对X轴(水平轴)旋转,正值为从X轴向下看顺时针旋转
setRotationY(float rotationY)设置相对Y轴(竖直轴)旋转,正值为从Y轴向下看顺时针旋转
setPivotX(float pivotX) 设置X轴附近的轴心点的X坐标
setPivotY(float pivotY) 设置Y轴附近的轴心点的Y坐标
setScaleX(float scaleX) 设置X轴方向的缩放比例
setScaleY(float scaleY) 设置Y轴方向的缩放比例

其中setPivotX通常和setRotation和setScaleX (旋转和缩放)组合使用
其中setPivotY通常和setRotation和setScaleY(旋转和缩放)组合使用
注意:默认pivotX和PivotY组成的轴点(mRenderNode )是(0,0),但是你一旦通过设置pivotX或者pivotY改变了默认值,那么将破坏了默认旋转缩放规则,只能显示调用这两个方法了。

下面利用之前讲的提供几个动画实现:

页面变换器实现了,要与ViewPager建立联系才能发挥作用,可以通过以下方法注入:

setPageTransformer(boolean reverseDrawingOrder,@Nullable PageTransformer transformer)
setPageTransformer(boolean reverseDrawingOrder,@Nullable PageTransformer transformer, int pageLayerType) 

以上设置变换器有两个重载方法,一个是两个参数,一个是三个参数

参数reverseDrawingOrder:是否执行相反的绘制命令,绘制后面的page在前面page之上,就为false,反之则为true
参数PageTransformer:这个就是我们实现的页面变换器接口,这个提供一个实例
参数pageLayerType:这个是和SurfaceView绘制相关,主要有以下几种图层类型
View.LAYER_TYPE_HARDWARE = 2; 采用硬件加速图层
View.LAYER_TYPE_SOFTWARE = 1; 不论硬件加速是否打开,都会使用软件渲染pipe通道
View.LAYER_TYPE_NONE = 0; 指示View没有图层

为什么会区分这三种模式呢?这里涉及复杂的绘制原理,这里我们只要记住,如果你的ViewGroup中包含了SurfaceView并且没有调用setZOrderOnTop(boolean)方法,那么就会出一些bug了。这个时候为了避免这种情况,你需要调用三个参数的方法,并且需要传递LAYER_TYPE_NONE这个值给pageLayerType。两个参数,系统会默认采用LAYER_TYPE_HARDWARE图层。除此之外,没有影响,就调用默认两个参数的方法。

看下面的实际效果:

//旋转缩放效果
 class MyPageTransformer implements ViewPager.PageTransformer {

        private final float MIN_SCALE = 0.75f;

        @Override
        public void transformPage(@NonNull View page, float position) {
            /*缩放旋转*/
            if (position <= 0f) {
                page.setTranslationX(0f);
                page.setScaleX(1f);
                page.setScaleY(1f);
            } else if (position <= 1f) {
                final float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position));
                page.setAlpha(1 - position);
                page.setPivotY(0.5f * page.getHeight());
                page.setTranslationX(page.getWidth() * -position);
                page.setScaleX(scaleFactor);
                page.setScaleY(scaleFactor);
            }
            page.setRotation(360 * position);
        }
    }

缩放旋转变换

 class MyPageTransformer implements ViewPager.PageTransformer {
        @Override
        public void transformPage(@NonNull View page, float position) {
			//3D旋转
            int width = page.getWidth();
            int pivotX = 0;
            if (position <= 1 && position > 0) {// right scrolling
                pivotX = 0;
            } else if (position == 0) {

            } else if (position < 0 && position >= -1) {// left scrolling
                pivotX = width;
            }
            //设置x轴的锚点
            page.setPivotX(pivotX);
            //设置绕Y轴旋转的角度
            page.setRotationY(90f * position);
        }
    }

3D旋转

//这种转变需要结合我们之前讲过的margin以及padding来实现
//在此之前我们需要在layout.xml文档中关闭剪切子View开关
//android:clipChildren="false"
//并且注意,是ViewPager及其父布局都要设置,否则不生效
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:clipChildren="false"
    android:layout_height="match_parent">
    <com.hzx.viewpagerdirector.view.BannerViewPager
        android:id="@+id/banner"
        android:layout_width="match_parent"
        android:clipChildren="false"
        android:layout_height="match_parent"/>
</LinearLayout>

//接下来是转场变换器
 class MyPageTransformer implements ViewPager.PageTransformer {

        //        private final float MIN_SCALE = 0.75f;
        private final float MIN_SCALE = 0.5f;
        private final float MIN_ALPHA = 0.5f;

        @Override
        public void transformPage(@NonNull View page, float position) {
            float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position));
            float alphaFactor = MIN_ALPHA + (1 - MIN_ALPHA) * (1 - Math.abs(position));
            page.setScaleY(scaleFactor);
            page.setAlpha(alphaFactor);
        }
    }

//然后还有适配器加载子View方法
class MyBaseAdapter extends PagerAdapter {
		//其他必须继承的方法这里省略,前边的文章有详细介绍
        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container, int position) {
            ImageView image = mImageList.get(position);
            container.addView(image);
            ViewGroup.LayoutParams params = image.getLayoutParams();
            params.width = 1080;
            params.height = ViewGroup.LayoutParams.MATCH_PARENT;
            image.setLayoutParams(params);
           	//对,关键是instantiateItem的这个padding的设置方法
            image.setPadding(200, 80, 200, 80);
            image.setScaleType(ImageView.ScaleType.FIT_XY);
            image.setImageDrawable(getResources().getDrawable(mPicIds[position]));
            return image;
        }
    }
    
	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
		//最后通过设置margin生效
 		mBaseAdapter = new MyBaseAdapter();
        mPager.setPageMargin(-300);
        mPager.setOffscreenPageLimit(3);
        mPager.setAdapter(mBaseAdapter);
        mPager.setPageTransformer(true, new MyPageTransformer());
    }
		

放大居中进入

别着急,如果你的公司还有以下的变态需求(见下方gif):
首页尾页靠边转换
这个和上一个很相似,不知道读者有没有看到区别:
上一个每个子View都居中,而下边这个第一个靠左,最后一个靠右,其他的才居中
实现这个的关键,是需要我们动态调整padding,同时还需要匹配子View的width,覆写适配器的另一个方法,好了下面看代码:

//除了layout.xml,转换器,初始化设置保持相同外,以下是不同之处,只贴这部分代码:
//主要是适配器的两个方法
class MyBaseAdapter extends PagerAdapter {
		//其他方法省略,只讲以下两个方法
        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container, int position) {
            ImageView image = mImageList.get(position);
            container.addView(image);
            ViewGroup.LayoutParams params = image.getLayoutParams();
            params.width = 1080;
            params.height = ViewGroup.LayoutParams.MATCH_PARENT;
            image.setLayoutParams(params);
            //这里设置第一页靠左,最后一页靠右,其他居中的效果
            if (position == 0) {
                image.setPadding(50, 80, 200, 80);
            } else if (position == mImageList.size() - 1) {
                image.setPadding(200, 80, 50, 80);
            } else {
                image.setPadding(200, 80, 200, 80);
            }
            image.setScaleType(ImageView.ScaleType.FIT_XY);
            image.setImageDrawable(getResources().getDrawable(mPicIds[position]));
            return image;
        }

        @Override
        public float getPageWidth(int position) {
            super.getPageWidth(position);
            float width = 1.0f;
            if (position == 0 || position == mImageList.size() - 1) {
            //这个方法有些童鞋可能熟悉,这个是ViewPager在测量确定子View所占宽度的时候用到的,
            //由于在instantiateItem方法中第一个和最后一个特殊处理了,导致在展示的时候,
            //第一个的右边间距过大,最后一个左边间距过大,为了调整这部分差异,
            //需要实现这个方法,并且对第一个和最后一个宽度进行计算
            //下边代码中150,正是第一个和最后一个异常间距的差异值
                width = (float) (DensityUtil.getScreenWidth(AdapterActivity.this) - 150)
                        / DensityUtil.getScreenWidth(AdapterActivity.this);
            }
            return width;
        }

好了经过以上调整,即使是这么变态的效果,我们也能轻松实现,只要大家多想想,多结合一些动画方式,就能定制化各种效果。

下边我们总结一下本章的内容:

1、增加转场效果,你需要实现ViewPager.PageTransformer的唯一方法,在这个方法中的参数值区间是[-1,1],其中-1是从左边退出到头,-1到0,是左边到中间的一个进度值,0是正好稳定显示的位置,0到1是中间到右边的一个进度值,1就是正好右边退出到头;根据这个关系,我们结合属性动画,就能实现更加丰富多彩的转场动画效果了;

2、padding可以在适配器的加载方法中设置,通过view.setPadding();方法能够调整当前页面的相对位置;

3、如果你想让左右两边的View部分进入当前View,首先你需要关闭clipChildren开关,然后设置负的margin值,不关闭的话,默认会剪切,那么负的就会被剪切掉;导致效果失效;

ViewPager应用系列马上就迎来最后一篇博客了,撒花,

ViewPager (七) 让ViewPager用起来更顺滑——轮播、禁止滑动与指示器的配合

  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ViewPager是一个Android中的View容器,可以让用户在多个页面之间进行滑动切换。要实现多页面滑动切换以及动画效果,可以按照以下步骤: 1. 在XML布局文件中添加ViewPager控件,并添加对应的布局文件,例如: ``` <android.support.v4.view.ViewPager android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 2. 在Java代码中为ViewPager设置Adapter,用于显示多个页面。例如: ``` ViewPager viewPager = findViewById(R.id.viewPager); MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager()); viewPager.setAdapter(adapter); ``` 其中,MyPagerAdapter是自定义的PagerAdapter类,需要继承自FragmentPagerAdapter或FragmentStatePagerAdapter。 3. 在自定义的PagerAdapter类中实现getItem()方法,用于返回每个页面的Fragment实例。例如: ``` @Override public Fragment getItem(int position) { switch (position) { case 0: return new Fragment1(); case 1: return new Fragment2(); case 2: return new Fragment3(); default: return null; } } ``` 其中,Fragment1、Fragment2、Fragment3是自定义的Fragment类,用于显示对应页面的内容。 4. 如果需要添加页面切换的动画效果,可以在Java代码中为ViewPager设置PageTransformer。例如: ``` viewPager.setPageTransformer(true, new DepthPageTransformer()); ``` 其中,DepthPageTransformer是自定义的PageTransformer类,用于实现页面切换时的动画效果。可以参考以下代码示例: ``` public class DepthPageTransformer implements ViewPager.PageTransformer { private static final float MIN_SCALE = 0.75f; public void transformPage(View view, float position) { int pageWidth = view.getWidth(); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. view.setAlpha(0f); } else if (position <= 0) { // [-1,0] // Use the default slide transition when moving to the left page view.setAlpha(1f); view.setTranslationX(0f); view.setScaleX(1f); view.setScaleY(1f); } else if (position <= 1) { // (0,1] // Fade the page out. view.setAlpha(1 - position); // Counteract the default slide transition view.setTranslationX(pageWidth * -position); // Scale the page down (between MIN_SCALE and 1) float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)); view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); } else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0f); } } } ``` 以上就是实现AndroidViewPager多页面滑动切换以及动画效果的步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值