一步步實(shí)現(xiàn)Viewpager卡片翻頁效果
這個(gè)CardStackViewpager的靈感來自Github上面的 FlippableStackView開源項(xiàng)目,而我想實(shí)現(xiàn)的效果方向上恰好與FlippableStackView相反,并且細(xì)節(jié)上也有些區(qū)別,詳見下面的效果對(duì)比圖:
FlippableStackView運(yùn)行效果圖:
CardStackViewpager運(yùn)行效果圖:
這里講一個(gè)小插曲,自己嘗試實(shí)現(xiàn)CardStackViewpager的過程中,由于一開始對(duì)PageTransformer的onTransform(View page, float position)實(shí)在很困惑,于是我用自己小學(xué)般的英語寫了一封郵件給FlippableStackView的開發(fā)者,尷尬的是,至今他沒回我郵件。
回歸正題,下面我就來具體講一下CardStackViewpager的實(shí)現(xiàn)思路,其實(shí)整個(gè)核心就在下面這一段代碼,把下面這段代碼搞懂了,就可以通過自定義自己的PageTransformer實(shí)現(xiàn)各種各樣想要的Viewpager效果了。
核心的VerticalStackTransformer的onTransform方法最終版
@Override protected void onTransform(View page, float position) { if (position <= 0.0f) { page.setAlpha(1.0f); Log.e("onTransform", "position <= 0.0f ==>" + position); page.setTranslationY(0f); //控制停止滑動(dòng)切換的時(shí)候,只有最上面的一張卡片可以點(diǎn)擊 page.setClickable(true); } else if (position <= 3.0f) { Log.e("onTransform", "position <= 3.0f ==>" + position); float scale = (float) (page.getWidth() - ScreenUtils.dp2px(context, spaceBetweenFirAndSecWith * position)) / (float) (page.getWidth()); //控制下面卡片的可見度 page.setAlpha(1.0f); //控制停止滑動(dòng)切換的時(shí)候,只有最上面的一張卡片可以點(diǎn)擊 page.setClickable(false); page.setPivotX(page.getWidth() / 2f); page.setPivotY(page.getHeight() / 2f); page.setScaleX(scale); page.setScaleY(scale); page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - scale) + ScreenUtils.dp2px(context, spaceBetweenFirAndSecHeight) * position); } }
在分析上面的代碼之前,我們需要有以下幾個(gè)知識(shí)準(zhǔn)備:
1.Viewpager的setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer)方法的第一個(gè)參數(shù),用來控制加入到Viewpager的Views對(duì)象是正序的還是倒序的,這里為了實(shí)現(xiàn)我們想要的效果,需要讓第一個(gè)添加到布局的View來到第一個(gè)展示,所以傳入true;
2.Viewpager的setOffscreenPageLimit(int limit)方法,設(shè)置有多少的緩存Views,這個(gè)將決定我們的卡片重疊展示的效果顯示幾層卡片效果。
現(xiàn)在我們繼續(xù)看上面的onTransform(View page, float position)方法,這個(gè)方法設(shè)計(jì)的很巧妙,當(dāng)初我在探索的時(shí)候,通過打印日志來判斷這個(gè)方法是如何執(zhí)行的時(shí)候,發(fā)現(xiàn)這這個(gè)position的值看似毫無規(guī)律,后來我想到以前數(shù)學(xué)里推理定理時(shí)的方法,從特殊情況入手,再一點(diǎn)點(diǎn)分析其他情況,然后一步步的實(shí)現(xiàn)上面的代碼。
第一步,分析應(yīng)用初始化進(jìn)來的時(shí)候的position
此時(shí)的onTransform(View page, float position)方法如下:
@Override protected void onTransform(View page, float position) { Log.e("onTransform","position ==>"+position); //設(shè)置每個(gè)卡片y方向偏移量,這樣可以使卡片都完全疊加起來 page.setTranslationY(-page.getHeight() * position); }
對(duì)應(yīng)日志如下:
根據(jù)這個(gè)日志很明顯的可以判斷得到:由于我現(xiàn)在設(shè)置的setOffscreenPageLimit(int limit)值為4,所以可以看到position有上面幾種情況,顯而易見,每個(gè)position對(duì)應(yīng)了一張卡片,這個(gè)時(shí)候界面的效果如圖:
現(xiàn)在猜想2,3,4,5號(hào)卡片就在1號(hào)卡片下面,現(xiàn)在要想個(gè)法子證實(shí)我們的猜想,將onTransform(View page, float position)方法改成下面這樣:
@Override protected void onTransform(View page, float position) { Log.e("onTransform","position ==>"+position); //設(shè)置卡片透明度 page.setAlpha(0.5f); //設(shè)置縮放中點(diǎn) page.setPivotX(page.getWidth() / 2f); page.setPivotY(page.getHeight() / 2f); //設(shè)置縮放的比例 此處設(shè)置兩個(gè)相鄰的卡片的縮放比率為0.9f page.setScaleX((float) Math.pow(0.9f,position)); page.setScaleY((float) Math.pow(0.9f,position)); //設(shè)置每個(gè)卡片y方向偏移量,這樣可以使卡片都完全疊加起來 page.setTranslationY(-page.getHeight() * position); }
運(yùn)行起來之后,證實(shí)了我們的想法:
第二步,實(shí)現(xiàn)卡片疊加的最終效果
分析上面的圖片效果,可以發(fā)現(xiàn),把第二張卡片往下移動(dòng)一段距離之后,就可以形成一個(gè)卡片疊加的初步效果了,變成下面這樣:
其他的卡片,道理一樣,那么如何實(shí)現(xiàn)這個(gè)向下偏移的值呢,這個(gè)值如何以一個(gè)表達(dá)式表現(xiàn)出來呢,先看下面的A,B,C步驟的分析圖:
顯而易見,相隔兩張卡片的偏移量為:(H2-H1)+d1,我們稍微改變一下onTransform(View page, float position)方法如下:
@Override protected void onTransform(View page, float position) { Log.e("onTransform", "position ==>" + position); page.setAlpha(0.5f); page.setPivotX(page.getWidth() / 2f); page.setPivotY(page.getHeight() / 2f); page.setScaleX((float) Math.pow(0.9f, position)); page.setScaleY((float) Math.pow(0.9f, position)); //修改過的代碼 page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - (float) Math.pow(0.9f, position)) + ScreenUtils.dp2px(context, 10)); }
此時(shí)的效果圖如下:
卡片半透明的時(shí)候,效果還不是特別的明顯,把page.setAlpha(0.5f)改為page.setAlpha(1.0f)再試一次:
驚喜的發(fā)現(xiàn)這不就是卡片疊加效果嘛,雖然現(xiàn)在的效果細(xì)節(jié)還有點(diǎn)問題,我們不急,這個(gè)細(xì)節(jié)問題簡單分析一下就會(huì)想到,是我們的縮放比例問題導(dǎo)致的,繼續(xù)下一步的優(yōu)化,我們將會(huì)解決這個(gè)問題。
第三步,根據(jù)相鄰卡片的間距值動(dòng)態(tài)設(shè)置縮放值
上面的onTransform(View page, float position)方法中,我們的x,y縮放比例都是寫的一個(gè)固定值0.9f,這個(gè)顯然不能滿足日常需求,我這里是設(shè)置上下兩張卡片的寬度比來作為最終想要的縮放比例,修改onTransform(View page, float position)方法如下:
@Override protected void onTransform(View page, float position) { Log.e("onTransform", "position ==>" + position); float scale = (float) (page.getWidth() - ScreenUtils.dp2px(context, 20 * position)) / (float) (page.getWidth()); page.setAlpha(1.0f); page.setPivotX(page.getWidth() / 2f); page.setPivotY(page.getHeight() / 2f); page.setScaleX(scale); page.setScaleY(scale); //修改過的代碼 page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - scale) + ScreenUtils.dp2px(context, 10) * position); }
再跑一下程序,完美的卡片效果就出現(xiàn)了:
第四步,特殊到一般,實(shí)現(xiàn)最終的卡片滑動(dòng)效果
此時(shí),我們嘗試一下滑動(dòng)Viewpager,發(fā)現(xiàn)卡片的切換效果并沒有如期的出現(xiàn),通過多次嘗試和分析,我發(fā)現(xiàn),由于我們這里沒有對(duì)當(dāng)前滑動(dòng)過去的那張卡片做特殊處理,這里的特殊處理指的是:為了實(shí)現(xiàn)卡片抽動(dòng)的切換效果,當(dāng)前滑動(dòng)的卡片應(yīng)該不用執(zhí)行任何縮放和偏移的操作,修改為page.setTranslationY(0f);,具體代碼如下:
這里列出一篇博客: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/0814/1650.html,他主要講了對(duì)onTransform(View page, float position)中position的理解
@Override protected void onTransform(View page, float position) { Log.e("onTransform", "position ==>" + position); if (position <= 0.0f) { page.setAlpha(1.0f); //出現(xiàn)卡片抽動(dòng)效果的關(guān)鍵代碼 page.setTranslationY(0f); } else { float scale = (float) (page.getWidth() - ScreenUtils.dp2px(context, 20 * position)) / (float) (page.getWidth()); page.setAlpha(1.0f); page.setPivotX(page.getWidth() / 2f); page.setPivotY(page.getHeight() / 2f); page.setScaleX(scale); page.setScaleY(scale); //修改過的代碼 page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - scale) + ScreenUtils.dp2px(context, 10) * position); } }
至此,已經(jīng)可以實(shí)現(xiàn)文章開頭的動(dòng)畫效果了。回頭想一下,我們一直在基于特殊的情況寫代碼,最后發(fā)現(xiàn)其實(shí)他就是所有一般情況中的一種,只不過特殊情況由于他的特殊性最容易進(jìn)行分析總結(jié),更有利于我們編寫出易懂的代碼。
最后補(bǔ)充下,在實(shí)際項(xiàng)目中,在每張卡片上可能還有有點(diǎn)擊區(qū)域,更可能整張卡片都是一個(gè)點(diǎn)擊區(qū)域,這個(gè)時(shí)候就會(huì)發(fā)現(xiàn)一個(gè)問題,當(dāng)處于這種情況的時(shí)候:
我不但可以點(diǎn)到卡片1,也會(huì)點(diǎn)到卡片2,卡片3。。。這樣肯定不行的,所以我們再次回到onTransform(View page, float position)方法,在里面加一個(gè)控制:
@Override protected void onTransform(View page, float position) { Log.e("onTransform", "position ==>" + position); if (position <= 0.0f) { //最上面的卡片可以點(diǎn)擊 page.setClickable(true); ....... } else { //下面的卡片不可點(diǎn)擊 page.setClickable(false); ........ } }
另外我們可能只需要4張卡片重疊的效果就行,這個(gè)時(shí)候改變一下判斷條件即可:
@Override protected void onTransform(View page, float position) { Log.e("onTransform", "position ==>" + position); if (position <= 0.0f) { ...... //控制顯示幾張卡片 } else if(position <= 3.0f) { ...... } }
至此這邊文章就要結(jié)束了,這是我的總結(jié),希望能幫助大家對(duì)onTransform(View page, float position)方法有一個(gè)更深的理解。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 使用ViewPager實(shí)現(xiàn)左右循環(huán)滑動(dòng)及滑動(dòng)跳轉(zhuǎn)
- 自定義RadioButton和ViewPager實(shí)現(xiàn)TabHost帶滑動(dòng)的頁卡效果
- Android利用ViewPager實(shí)現(xiàn)滑動(dòng)廣告板實(shí)例源碼
- Android App中使用ViewPager+Fragment實(shí)現(xiàn)滑動(dòng)切換效果
- Android App中ViewPager所帶來的滑動(dòng)沖突問題解決方法
- Android中ViewPager帶來的滑動(dòng)卡頓問題解決要點(diǎn)解析
- android ViewPager實(shí)現(xiàn)滑動(dòng)翻頁效果實(shí)例代碼
- 巧用ViewPager實(shí)現(xiàn)駕考寶典做題翻頁效果
- Android?ViewPager實(shí)現(xiàn)左右滑動(dòng)翻頁效果
相關(guān)文章
Android Button點(diǎn)擊事件的四種實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了Android Button點(diǎn)擊事件的四種實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Android Data Binding數(shù)據(jù)綁定詳解
本文主要介紹Android Data Binding數(shù)據(jù)綁定的知識(shí),這里整理了詳細(xì)的資料及簡單示例代碼幫助大家學(xué)習(xí)理解此部分知識(shí),有需要的小伙伴可以參考下2016-09-09詳解Android中實(shí)現(xiàn)Redux方法
本篇文章給大家通過代碼實(shí)例教學(xué)Android中實(shí)現(xiàn)Redux的方法,有需要的朋友跟著參考下吧。2018-01-01Android 6.0 掃描不到 Ble 設(shè)備需開啟位置權(quán)限的方法
今天小編就為大家分享一篇Android 6.0 掃描不到 Ble 設(shè)備需開啟位置權(quán)限的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-07-07Android 序列化的存儲(chǔ)和讀取總結(jié)及簡單使用
這篇文章主要介紹了Android 序列化的存儲(chǔ)和讀取總結(jié)及簡單使用的相關(guān)資料,Serializable接口和Parcelable接口,本文對(duì)這兩種方式進(jìn)行簡單的總結(jié)和使用,需要的朋友可以參考下2016-12-12Android Studio gradle 編譯提示‘default not found’ 解決辦法
這篇文章主要介紹了Android Studio gradle 編譯提示‘default not found’ 解決辦法的相關(guān)資料,需要的朋友可以參考下2016-12-12詳解升級(jí)Android Studio3.0時(shí)遇到的幾個(gè)問題
本篇文章主要介紹了升級(jí)Android Studio3.0時(shí)遇到的幾個(gè)問題,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10Android設(shè)計(jì)登錄界面、找回密碼、注冊功能
這篇文章主要為大家詳細(xì)介紹了Android設(shè)計(jì)登錄界面的方法,Android實(shí)現(xiàn)找回密碼、注冊功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05