自定义ViewPager切换动画

实现基础的ViewPager###

1.主布局文件activity_main.xml,一个用于放ViewPager的RelativeLayout容器,里面就是我们用于展示的ViewPager了。

<?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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg"
    tools:context="com.momo.pagetransformerdemo.activity.MainActivity">

    <RelativeLayout
        android:id="@+id/rl_container"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        android:clipChildren="false"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="50dp">

        <android.support.v4.view.ViewPager
            android:id="@+id/view_pager"
            android:layout_width="100dp"
            android:layout_height="match_parent"
            android:clipChildren="false"
            android:layout_centerHorizontal="true">
        </android.support.v4.view.ViewPager>

    </RelativeLayout>
</RelativeLayout>

这里有个需要注意的地方:RelativeLayout和ViewPager的clipChildren属性都需要设置为false,目的是让ViewPager左边右边的界面也显示出来,不然ViewPager只会显示中间ViewPager在布局上所占的那部分空间。如果不清楚直接看下图:

clipChildren=“false”
clipChildren=“true”

2.ViewPager的Adapter类,在构造函数中传入要显示的数据源,这里就是每个page上要显示的文本。在instantiateItem()方法中加载要显示的页面,找到其中的TextView,设置要显示的文本。至于这四个函数的作用可以参考ViewPager 详解(二)---详解四大函数

MyPagerAdapter.java

public class MyPagerAdapter extends PagerAdapter {
    private String[] mDatas;
    private Context mContext;

    public MyPagerAdapter(Context context,String[] mDatas) {
        this.mContext =context;
        this.mDatas = mDatas;
    }

    @Override
    public int getCount() {
        return mDatas.length;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.view_pager_item,null);
        TextView tv = (TextView) view.findViewById(R.id.tv_text);
        tv.setText(mDatas[position]);
        container.addView(view);
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        View view = (View) object;
        container.removeView(view);
    }

3.ViewPager的每个界面布局view_pager_item.xml,就是一个Textview,没啥好说的。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/item_bg">

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Page 1"
        android:textSize="20dp"
        android:layout_centerInParent="true"
        android:textColor="#ffffff"
        />
</RelativeLayout>

4.MainActivity.java

public class MainActivity extends AppCompatActivity {

    private RelativeLayout rl_container;
    private ViewPager viewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findId();
        initViewPager();
    }

    private void findId() {
        viewPager = (ViewPager) findViewById(R.id.view_pager);
        rl_container = (RelativeLayout) findViewById(R.id.rl_container);
    }

    private void initViewPager() {
        //创建数据源
        String[] stringArr = new String[8];
        for(int i=0;i<stringArr.length;i++){
            stringArr[i] = "Page" + String.valueOf(i);
        }
        //设置Adapter
        viewPager.setAdapter(new MyPagerAdapter(this,stringArr));
        //设置缓存页面数量
        viewPager.setOffscreenPageLimit(stringArr.length/2);
        //设置页面间距
        viewPager.setPageMargin(10);
    }
}

写到这里我们可以来运行一下,结果发现只有中间滑动有效果,而如果在两边滑动时,ViewPager是不会切换页面的。这是因为点击事件被容器RelativeLayout拦截了。加入下面的代码就能解决问题了。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        ......
        initViewPager();
        
        rl_container.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return viewPager.dispatchTouchEvent(event);
            }
        });
    }

给RelativeLayout设置OnTouchListener使得当其接收到触摸事件时会将该事件转给ViewPager进行分发。这样就能让ViewPager处理左右两边的触摸事件了。

至此,一个简单的ViewPager就完成了,来看看效果。

PageTransformer###

这个类是我们实现自定义切换动画的核心。ViewPager有一个setPageTransformer()方法用于设置切换动画。goolgle官方提供了两个动画效果,DepthPageTransformer和ZoomOutPageTransformer,具体效果可以参考一下鸿洋大神的这篇博客Android 实现个性的ViewPager切换动画 实战PageTransformer(兼容Android3.0以下)。关于下面要讲到的position的意义,如果觉得博主讲的不清楚也可以参考这篇博客Android 自定义 ViewPager 打造千变万化的图片切换效果

我们看到PageTransformer中要实现这样一个函数:

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

 }  

解释一下,这里的page指的就是在滑动的页面,而position是指该页面的位置,用下面这张图解释:

2017-02-26_195739.png

position取值可以看成该页面左上角的位置

  • 当position<-1的时候,表示该page处在当前显示页面的左边一个页面位置以外。
  • 当position=-1的时候,表示该page处在当前显示页面的左边一个页面位置,此时该页面的右上角就是当前显示页面的左上角。
  • 当position=0的时候,表示该page处在当前显示页面位置
  • 当position=1的时候,表示该page处在当前显示页面的右边一个页面位置,此时该页面的左上角就是当前显示页面的右上角。
  • 当position>1的时候,表示该page处在当前显示页面的右边一个页面位置以外。

举个栗子:


上图中page0的position为-2,page1为-1,page2为当前显示页面,position为0,page3为1,page4为2。在page2往左边划的过程中,page2的position值会从1变到0,同理,page3的position值会从1变到0。

有了这样的一个position值,便可以开始制作动画了。
制作动画前先看一下要用到的几个函数(这里可以参考这篇博客【Android开发】View的平移、缩放、旋转以及位置、坐标系)

  • page.setTranslationX(float x)
    page.setTranslationY(float y)

    这两个函数用于设置page在x,y方向的位移
  • page.setScaleX(float scaleX);
    page.setScaleY(float scaleY);

    这两个函数用于设置page在x,y方向的放缩比例
  • page.setAlpha(float alpha)
    这个函数用于设置page的透明度
  • page.setRotation(float rotation)
    page.setRotationX(float rotationX)
    page,setRotationY(float rotationY)

    这三个函数用于设置page在x,y,z轴方向的旋转角度
  • page.setPivotX(float pivotX)
    page.setPivotY(float pivotY)

    这两个函数是最重要的,也相对比较难理解,用于设置view缩放和旋转的锚点坐标,正常情况下page的锚点在正中心位置

锚点的位置,将决定page缩放后所在的位置。因为默认是中心坐标,所以缩放后,其结果在水平中心位置或垂直中心位置。但是,如果锚点的位置变了,那么View缩放后的位置也将发生变化。
旋转时,将会以锚点所在的轴线为轴进行旋转,比如:

  • 不同锚点setRotation(90)的效果(红点为锚点位置)
2017-02-26_205747.png
  • 不同锚点setRotationY(45)的效果(红点为锚点位置)


    setRotationY(45)
注意两张page0的区别

了解以上几个函数的效果就可以动手写动画了,所谓动画无非就是像总结一个函数一样,对于任意的position能找到一个特定的属性与之对应,这个值可以是透明度、旋转角度、位移等等。
举个栗子,如果我们想实现下面这种动画,只要找出这样一个对应的函数就可以了。

** 总结规律:**

  • position<=-1时, translationY = 100
  • -1<position<0时, translationY = -position*100(此时position为负)
  • position=0时, translationY = 0
  • 0<position<1时, translationY = position*100
  • position>=1时, translationY =100;

我们还可以根据上面的规律画成图像:


translationY对应的函数图像

有了这样清晰的概念写出动画就不是什么难事了。下面看源码:

@Override
public void transformPage(View page, float position) {
    page.setPivotY(page.getHeight()/2);
    float maxTransform = page.getHeight()/4;
    if (position <= -1)
    { // [-Infinity,-1)
        // This page is way off-screen to the left.
        page.setTranslationY(maxTransform);
    } else if (position < 1)
    { // [-1,1]
        // Modify the default slide transition to shrink the page as well
        if (position < 0)//[0,-1]
        {
            page.setTranslationY(-position * maxTransform);
        } else//[1,0]
        {
            page.setTranslationY(position * maxTransform);
        }
    } else
    { // (1,+Infinity]
        // This page is way off-screen to the right.
        page.setTranslationY(maxTransform);
    }
}

再看个复杂点的

@Override
public void transformPage(View page, float position) {
    page.setPivotY(page.getHeight()/2);
    float maxRotate = 35f;
    float minScale = 0.8f;
    float maxTranslationX = page.getWidth()/5;
    if (position <= -1)
    { // [-Infinity,-1)
        // This page is way off-screen to the left.
        page.setRotationY(maxRotate);
        page.setPivotX(0);
        page.setScaleX(minScale);
        page.setScaleY(minScale);
        page.setTranslationX(maxTranslationX);
    } else if (position < 1)
    { // [-1,1]
        page.setRotationY(-position * maxRotate);
        if (position < 0)//[0,-1]
        {
            page.setPivotX(0);
            page.setScaleX(1 + position * (1-minScale));
            page.setScaleY(1 + position * (1-minScale));
            page.setTranslationX(-position * maxTranslationX);
        } else//[1,0]
        {
            page.setPivotX(page.getWidth());
            page.setScaleX(1 - position * (1-minScale));
            page.setScaleY(1 - position * (1-minScale));
            page.setTranslationX(-position * maxTranslationX);
        }
    } else
    { // (1,+Infinity]
        // This page is way off-screen to the right.
        page.setRotationY(-1 * maxRotate);
        page.setPivotX(page.getWidth());
        page.setScaleX(minScale);
        page.setScaleY(minScale);
        page.setTranslationX(-maxTranslationX);
    }
}

最后啰嗦两句###

总算写完了,第一次写博客,找各种各样的工具花了好长时间,录屏使用的是手机app录屏专家,再传到电脑上使用Free Video to GIF Converter转成gif,断断续续花了一晚上。如果大家有好的录屏软件推荐麻烦给博主留个言,谢谢了!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容