自定義ViewPager切換動畫

實(shí)現(xiàn)基礎(chǔ)的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屬性都需要設(shè)置為false,目的是讓ViewPager左邊右邊的界面也顯示出來,不然ViewPager只會顯示中間ViewPager在布局上所占的那部分空間。如果不清楚直接看下圖:

clipChildren=“false”
clipChildren=“true”

2.ViewPager的Adapter類,在構(gòu)造函數(shù)中傳入要顯示的數(shù)據(jù)源,這里就是每個page上要顯示的文本。在instantiateItem()方法中加載要顯示的頁面,找到其中的TextView,設(shè)置要顯示的文本。至于這四個函數(shù)的作用可以參考ViewPager 詳解(二)---詳解四大函數(shù)

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() {
        //創(chuàng)建數(shù)據(jù)源
        String[] stringArr = new String[8];
        for(int i=0;i<stringArr.length;i++){
            stringArr[i] = "Page" + String.valueOf(i);
        }
        //設(shè)置Adapter
        viewPager.setAdapter(new MyPagerAdapter(this,stringArr));
        //設(shè)置緩存頁面數(shù)量
        viewPager.setOffscreenPageLimit(stringArr.length/2);
        //設(shè)置頁面間距
        viewPager.setPageMargin(10);
    }
}

寫到這里我們可以來運(yùn)行一下,結(jié)果發(fā)現(xiàn)只有中間滑動有效果,而如果在兩邊滑動時(shí),ViewPager是不會切換頁面的。這是因?yàn)辄c(diǎn)擊事件被容器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設(shè)置OnTouchListener使得當(dāng)其接收到觸摸事件時(shí)會將該事件轉(zhuǎn)給ViewPager進(jìn)行分發(fā)。這樣就能讓ViewPager處理左右兩邊的觸摸事件了。

至此,一個簡單的ViewPager就完成了,來看看效果。

PageTransformer###

這個類是我們實(shí)現(xiàn)自定義切換動畫的核心。ViewPager有一個setPageTransformer()方法用于設(shè)置切換動畫。goolgle官方提供了兩個動畫效果,DepthPageTransformer和ZoomOutPageTransformer,具體效果可以參考一下鴻洋大神的這篇博客Android 實(shí)現(xiàn)個性的ViewPager切換動畫 實(shí)戰(zhàn)PageTransformer(兼容Android3.0以下)。關(guān)于下面要講到的position的意義,如果覺得博主講的不清楚也可以參考這篇博客Android 自定義 ViewPager 打造千變?nèi)f化的圖片切換效果

我們看到PageTransformer中要實(shí)現(xiàn)這樣一個函數(shù):

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

 }  

解釋一下,這里的page指的就是在滑動的頁面,而position是指該頁面的位置,用下面這張圖解釋:

2017-02-26_195739.png

position取值可以看成該頁面左上角的位置

  • 當(dāng)position<-1的時(shí)候,表示該page處在當(dāng)前顯示頁面的左邊一個頁面位置以外。
  • 當(dāng)position=-1的時(shí)候,表示該page處在當(dāng)前顯示頁面的左邊一個頁面位置,此時(shí)該頁面的右上角就是當(dāng)前顯示頁面的左上角。
  • 當(dāng)position=0的時(shí)候,表示該page處在當(dāng)前顯示頁面位置
  • 當(dāng)position=1的時(shí)候,表示該page處在當(dāng)前顯示頁面的右邊一個頁面位置,此時(shí)該頁面的左上角就是當(dāng)前顯示頁面的右上角。
  • 當(dāng)position>1的時(shí)候,表示該page處在當(dāng)前顯示頁面的右邊一個頁面位置以外。

舉個栗子:


上圖中page0的position為-2,page1為-1,page2為當(dāng)前顯示頁面,position為0,page3為1,page4為2。在page2往左邊劃的過程中,page2的position值會從1變到0,同理,page3的position值會從1變到0。

有了這樣的一個position值,便可以開始制作動畫了。
制作動畫前先看一下要用到的幾個函數(shù)(這里可以參考這篇博客【Android開發(fā)】View的平移、縮放、旋轉(zhuǎn)以及位置、坐標(biāo)系)

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

    這兩個函數(shù)用于設(shè)置page在x,y方向的位移
  • page.setScaleX(float scaleX);
    page.setScaleY(float scaleY);

    這兩個函數(shù)用于設(shè)置page在x,y方向的放縮比例
  • page.setAlpha(float alpha)
    這個函數(shù)用于設(shè)置page的透明度
  • page.setRotation(float rotation)
    page.setRotationX(float rotationX)
    page,setRotationY(float rotationY)

    這三個函數(shù)用于設(shè)置page在x,y,z軸方向的旋轉(zhuǎn)角度
  • page.setPivotX(float pivotX)
    page.setPivotY(float pivotY)

    這兩個函數(shù)是最重要的,也相對比較難理解,用于設(shè)置view縮放和旋轉(zhuǎn)的錨點(diǎn)坐標(biāo),正常情況下page的錨點(diǎn)在正中心位置

錨點(diǎn)的位置,將決定page縮放后所在的位置。因?yàn)槟J(rèn)是中心坐標(biāo),所以縮放后,其結(jié)果在水平中心位置或垂直中心位置。但是,如果錨點(diǎn)的位置變了,那么View縮放后的位置也將發(fā)生變化。
旋轉(zhuǎn)時(shí),將會以錨點(diǎn)所在的軸線為軸進(jìn)行旋轉(zhuǎn),比如:

  • 不同錨點(diǎn)setRotation(90)的效果(紅點(diǎn)為錨點(diǎn)位置)
2017-02-26_205747.png
  • 不同錨點(diǎn)setRotationY(45)的效果(紅點(diǎn)為錨點(diǎn)位置)


    setRotationY(45)
注意兩張page0的區(qū)別

了解以上幾個函數(shù)的效果就可以動手寫動畫了,所謂動畫無非就是像總結(jié)一個函數(shù)一樣,對于任意的position能找到一個特定的屬性與之對應(yīng),這個值可以是透明度、旋轉(zhuǎn)角度、位移等等。
舉個栗子,如果我們想實(shí)現(xiàn)下面這種動畫,只要找出這樣一個對應(yīng)的函數(shù)就可以了。

** 總結(jié)規(guī)律:**

  • position<=-1時(shí), translationY = 100
  • -1<position<0時(shí), translationY = -position*100(此時(shí)position為負(fù))
  • position=0時(shí), translationY = 0
  • 0<position<1時(shí), translationY = position*100
  • position>=1時(shí), translationY =100;

我們還可以根據(jù)上面的規(guī)律畫成圖像:


translationY對應(yīng)的函數(shù)圖像

有了這樣清晰的概念寫出動畫就不是什么難事了。下面看源碼:

@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);
    }
}

再看個復(fù)雜點(diǎn)的

@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);
    }
}

最后啰嗦兩句###

總算寫完了,第一次寫博客,找各種各樣的工具花了好長時(shí)間,錄屏使用的是手機(jī)app錄屏專家,再傳到電腦上使用Free Video to GIF Converter轉(zhuǎn)成gif,斷斷續(xù)續(xù)花了一晚上。如果大家有好的錄屏軟件推薦麻煩給博主留個言,謝謝了!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內(nèi)容