好久不見,這段時(shí)間忙著做自己的產(chǎn)品调炬,又有一段時(shí)間沒更博了语盈,今天帶來(lái)ViewPager的切換動(dòng)畫,先來(lái)看下效果圖:
這篇文章打算從3點(diǎn)切入:
1缰泡、關(guān)于clipChildren屬性的了解與使用(實(shí)現(xiàn)ViewPager單屏顯示多頁(yè)面效果)
2刀荒、關(guān)于ViewPager切換動(dòng)畫的基礎(chǔ)和示例講解
3、關(guān)于PageTransFormer的實(shí)例講解
1棘钞、關(guān)于clipChildren屬性的了解與使用(實(shí)現(xiàn)ViewPager一屏顯示多頁(yè)面效果)
關(guān)于clipChildren這個(gè)屬性缠借,因?yàn)椴⒉皇呛艹S茫赡苡行┡笥巡皇煜ど踔炼紱]見過宜猜,其實(shí)它非常強(qiáng)大泼返,可以幫你完成一些特殊的UI效果。
舉個(gè)栗子:
對(duì)于這種底部Tab欄姨拥,大家應(yīng)該再熟悉不過吧绅喉?那么中間這個(gè)突出來(lái)的播放按鈕應(yīng)該怎么實(shí)現(xiàn)呢?有的朋友會(huì)說(shuō)用一層幀布局FrameLayout或者相對(duì)布局RelativeLayout作為外層叫乌,然后里面再嵌套一層線性布局LinearLayout柴罐,把其它部分的背景顏色設(shè)置成透明不就可以了。這樣做其實(shí)是可以的憨奸,只不過這邊我想帶給你一種更加省時(shí)省力的方式丽蝎,就是clipChildren這個(gè)屬性的應(yīng)用。
clipChildren這個(gè)屬性是用來(lái)修飾ViewGroup容器的膀藐,它可以限制處于當(dāng)前這個(gè)容器里的子控件是否可以越界繪制屠阻,默認(rèn)為true,也就是限制子控件不允許越界额各,然后再來(lái)看下上面的底部Tab欄国觉,我們不需要嵌套布局,只需要在根節(jié)點(diǎn)添加
android:clipChildren="false"
這個(gè)屬性即可虾啦,來(lái)看一下示例代碼:
<?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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
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="80dp"
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>
上面是效果圖麻诀,我們?cè)试S相對(duì)布局RelativeLayout的子控件越界繪制痕寓,所以中間的icon圖片可以突破LinearLayout布局,這里還需要注意的一點(diǎn)是蝇闭,我設(shè)置中間的ImageView的對(duì)齊方式為android:layout_gravity="bottom"
呻率,不然圖標(biāo)是會(huì)往下沉屏幕之外去的。
有了這個(gè)基礎(chǔ)后呻引,我們來(lái)實(shí)現(xiàn)下ViewPager單屏顯示多頁(yè)面的效果
<?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:clipChildren="false">
<android.support.v4.view.ViewPager
android:id="@+id/vp_main_pager"
android:layout_width="280dp"
android:layout_height="200dp"
android:layout_centerInParent="true"
android:clipChildren="false" />
</RelativeLayout>
這是一個(gè)在根節(jié)點(diǎn)和ViewPager節(jié)點(diǎn)里添加了android:clipChildren="false"
屬性的布局礼仗,這里可能有人會(huì)問為什么2個(gè)節(jié)點(diǎn)都需要設(shè)置android:clipChildren呢?因?yàn)閂iewPager默認(rèn)只顯示一屏逻悠,我們添加上這個(gè)屬性后元践,ViewPager里面承載的ImageView就可以突破限制,在ViewPager之外繪制了童谒,然后又因?yàn)閂iewPager是在最外層布局RelativeLayout容器內(nèi)的单旁,所以也需要設(shè)置上這個(gè)屬性。
然后我們補(bǔ)一下代碼饥伊,就可以看到下面的效果了象浑。
mViewPager= (ViewPager) findViewById(R.id.vp_main_pager);
mViewPagerAdapter=new ViewPagerAdapter(this,mImages);
mViewPager.setOffscreenPageLimit(3);
mViewPager.setAdapter(mViewPagerAdapter);
我們?cè)賮?lái)設(shè)置下頁(yè)面與頁(yè)面之間的外邊距
mViewPager.setPageMargin(40);
,就可以看到這樣的效果:2琅豆、關(guān)于ViewPager切換動(dòng)畫的基礎(chǔ)(PageTransFormer)和示例講解
首先我們先來(lái)了解下關(guān)于ViewPager里的setPageTransformer方法愉豺,這里是源碼:
/**
* Sets a {@link PageTransformer} that will be called for each attached page whenever
* the scroll position is changed. This allows the application to apply custom property
* transformations to each page, overriding the default sliding behavior.
*
* <p><em>Note:</em> Prior to Android 3.0 ({@link Build.VERSION_CODES#HONEYCOMB API 11}),
* the property animation APIs did not exist. As a result, setting a PageTransformer prior
* to API 11 will have no effect.</p>
*
* @param reverseDrawingOrder true if the supplied PageTransformer requires page views
* to be drawn from last to first instead of first to last.
* @param transformer PageTransformer that will modify each page's animation properties
* @param pageLayerType View layer type that should be used for ViewPager pages. It should be
* either {@link ViewCompat#LAYER_TYPE_HARDWARE},
* {@link ViewCompat#LAYER_TYPE_SOFTWARE}, or
* {@link ViewCompat#LAYER_TYPE_NONE}.
*/
public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer,
int pageLayerType) {
if (Build.VERSION.SDK_INT >= 11) {
final boolean hasTransformer = transformer != null;
final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
mPageTransformer = transformer;
setChildrenDrawingOrderEnabledCompat(hasTransformer);
if (hasTransformer) {
mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
mPageTransformerLayerType = pageLayerType;
} else {
mDrawingOrder = DRAW_ORDER_DEFAULT;
}
if (needsPopulate) populate();
}
}
這里我們提取一下重點(diǎn):
關(guān)于方法的參數(shù):
參數(shù)1表示加載到ViewPager的Pager頁(yè)面是按正序還是逆序添加,這里我們一般設(shè)置成true就行趋距,這個(gè)一般只有在幀布局的時(shí)候才有視覺效果區(qū)別。
參數(shù)2是一個(gè)PageTransformer接口越除,接口里帶有一個(gè)回調(diào)方法节腐,這個(gè)是重點(diǎn),下面會(huì)單獨(dú)提到摘盆。
關(guān)于兼容Andorid API11(3.0版本):
這個(gè)東西對(duì)于現(xiàn)在來(lái)說(shuō)意義不是很大了翼雀,現(xiàn)在開發(fā)APP基本上都是兼容到4.1以上了,如果非要兼容3.0以下孩擂,拷貝下ViewPager的源碼狼渊,把這個(gè)判斷判斷注釋掉即可。
Prior to Android 3.0 ({@link Build.VERSION_CODES#HONEYCOMB API 11}),the property animation APIs did not exist. As a result, setting a PageTransformer prior to API 11 will have no effect.
上面這句話是講解為什么官方不支持3.0以下的原因类垦,因?yàn)樵贏ndroid3.0之前是沒有屬性動(dòng)畫的狈邑,而官方的示例代碼使用的卻是屬性動(dòng)畫,我們?nèi)绻蛳录嫒菰槿希褂闷渌姆绞綄?shí)現(xiàn)動(dòng)畫即可米苹。
public interface PageTransformer {
/**
* Apply a property transformation to the given page.
*
* @param page Apply the transformation to this page
* @param position Position of page relative to the current front-and-center
* position of the pager. 0 is front and center. 1 is one full
* page position to the right, and -1 is one page position to the left.
*/
void transformPage(View page, float position);
}
重點(diǎn)來(lái)了,這里的transformPage是PageTransformer接口里未實(shí)現(xiàn)的方法砰琢,有兩個(gè)回調(diào)參數(shù)蘸嘶,它們分別表示:
參數(shù)page:當(dāng)前頁(yè)的View
參數(shù)position:這里需要特別注意良瞧,它不代表當(dāng)前第幾頁(yè),而是代表當(dāng)前頁(yè)面值和一個(gè)滑動(dòng)距離的數(shù)值训唱,在當(dāng)前手機(jī)屏幕能看到的頁(yè)面永遠(yuǎn)為0褥蚯,往左遞減,往右遞增况增,也就是如下圖:
記自奘:(重要的話說(shuō)三遍)
當(dāng)前手機(jī)屏幕顯示的頁(yè)面的position值永遠(yuǎn)為0
當(dāng)前手機(jī)屏幕顯示的頁(yè)面的position值永遠(yuǎn)為0
當(dāng)前手機(jī)屏幕顯示的頁(yè)面的position值永遠(yuǎn)為0
接下來(lái)我們來(lái)分析一種情況:當(dāng)我們的手指往左滑的時(shí)候,此時(shí)發(fā)生的事情:
頁(yè)面:位置0會(huì)走到原-1的位置巡通,位置1會(huì)走到原0的位置尘执,以此類推。
position值:position的值逐漸從0開始下降到-1宴凉,從1開始下降到0誊锭,這里我們只考慮[-1,1]區(qū)間,之外的區(qū)間我們是看不到的弥锄,因?yàn)槲覀兠看沃荒芑黄痢?/p>
具體大家打印下Log日志就會(huì)很清晰了:
mViewPager.setPageTransformer(true, new ViewPager.PageTransformer() {
@Override
public void transformPage(View page, float position) {
Log.i("TAG","【page】:"+page+"丧靡,【position】:"+position);
}
});
日志顯示,反方向滑動(dòng)頁(yè)面正好是相反的籽暇,這里就不貼出來(lái)了温治。
【頁(yè)面從1到0】
【position】:0.95357144
【position】:0.94166666
【position】:0.572619
【position】:0.086904764
【position】:0.0035714286
【position】:0.0
========================
【頁(yè)面從0到-1】
【position】:-0.09404762
【position】:-0.10595238
【position】:-0.475
【position】:-0.79761904
【position】:-0.9607143
【position】:-1.00
我們所有的動(dòng)畫操作就可以根據(jù)這里的page和position來(lái)完成了,當(dāng)然官方也給了我們小例子示范戒悠,一起來(lái)看下:
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(0);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} 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(0);
}
}
}
這里簡(jiǎn)單講一下(當(dāng)前屏幕顯示的position值為0)熬荆,假設(shè)頁(yè)面從1到2,也就是手指向左滑绸狐,頁(yè)面往左走:
1卤恳、position<-1的時(shí)候剛好是當(dāng)前屏幕的左數(shù)第二頁(yè),是看不到的寒矿,position>1剛好是當(dāng)前屏幕的右數(shù)第二頁(yè)突琳,也是看不到的,所以這邊設(shè)置了透明度為0符相。
2拆融、position <= 0,position從0到-1(動(dòng)圖頁(yè)面1到2)啊终,此時(shí)的頁(yè)面1的效果是不透明镜豹,不縮放,向左走蓝牲。
3逛艰、position <= 1,position從1到0(動(dòng)圖頁(yè)面2到1)搞旭,此時(shí)的效果是頁(yè)面隨著position值比例的減小散怖,頁(yè)面從透明到不透明菇绵,從小到大,頁(yè)面不滑動(dòng)(抵消)镇眷。
3咬最、關(guān)于PageTransFormer的實(shí)例講解
說(shuō)了這么多,來(lái)實(shí)戰(zhàn)一下吧欠动,先從上面我們實(shí)現(xiàn)的單屏顯示多頁(yè)面的ViewPager開始永乌,加一些效果,比如我們想讓邊上的兩頁(yè)面顯示半透明并伴隨著左右滑動(dòng)有放大縮小的效果具伍,直接來(lái)看下PageTransformer的代碼:
package com.lcw.dynamicviewpager.transformer;
import android.support.v4.view.ViewPager;
import android.view.View;
/**
* 畫廊效果Transformer(半透明+縮放)
* Create by: chenwei.li
* Date: 2017/12/8
* time: 14:55
* Email: lichenwei.me@foxmail.com
*/
public class GalleryTransformer implements ViewPager.PageTransformer {
private static final float MAX_ALPHA=0.5f;
private static final float MAX_SCALE=0.9f;
@Override
public void transformPage(View page, float position) {
if(position<-1||position>1){
//不可見區(qū)域
page.setAlpha(MAX_ALPHA);
page.setScaleX(MAX_SCALE);
page.setScaleY(MAX_SCALE);
}else {
//可見區(qū)域翅雏,透明度效果
if(position<=0){
//pos區(qū)域[-1,0)
page.setAlpha(MAX_ALPHA+MAX_ALPHA*(1+position));
}else{
//pos區(qū)域[0,1]
page.setAlpha(MAX_ALPHA+MAX_ALPHA*(1-position));
}
//可見區(qū)域,縮放效果
float scale=Math.max(MAX_SCALE,1-Math.abs(position));
page.setScaleX(scale);
page.setScaleY(scale);
}
}
}
我們讓可見區(qū)域[-1,1]根據(jù)position值的變化做了一些動(dòng)作人芽,假設(shè)頁(yè)面往左滑:
1望几、在區(qū)間[-1,0)里是從0到-1,也就是透明度隨著position的減小而減小萤厅,最終為0.5f(半透明)橄抹。
2、在區(qū)間(0,1)里是從1到0惕味,也就是透明度隨著position的減小而增大楼誓,最終為1f(不透明)。
3名挥、它們的縮放規(guī)則是隨著position減小疟羹,先小后大,因?yàn)檫@邊有個(gè)絕對(duì)值abs方法和最大數(shù)max方法作用著禀倔,當(dāng)position的絕對(duì)值大于0.9f的時(shí)候榄融,頁(yè)面才開始放大。
是不是很簡(jiǎn)單疤R铡剃袍?哈哈黄刚,再來(lái)寫個(gè)效果吧捎谨,橫向的ViewPager我們看多了,這里我們來(lái)實(shí)現(xiàn)一個(gè)縱向排列的ViewPager憔维,并實(shí)現(xiàn)它的滑動(dòng)效果涛救。
首先我們來(lái)思考下如何實(shí)現(xiàn)縱向排列,我們知道ViewPager默認(rèn)是水平排開的业扒,每個(gè)頁(yè)面之間的距離是頁(yè)面的寬度检吆,那么是不是讓第二個(gè)頁(yè)面的x軸往左移動(dòng)一個(gè)頁(yè)面的距離,第三個(gè)頁(yè)面的x軸往左移動(dòng)2個(gè)頁(yè)面的距離程储。蹭沛。臂寝。以此類推
//移動(dòng)X軸左邊,使得卡片在同一坐標(biāo)
page.setTranslationX(-position * page.getWidth());
然后我們需要把每個(gè)頁(yè)面往下拉一點(diǎn)摊灭,也就是把y軸坐標(biāo)往下移動(dòng)一些咆贬,這里為了使頁(yè)面好看一些,我做了一個(gè)按比例縮放效果帚呼,mOffset是一個(gè)偏移值
//縮放卡片并調(diào)整位置
float scale = (page.getWidth() - mOffset * position) / page.getWidth();
page.setScaleX(scale);
page.setScaleY(scale);
//移動(dòng)Y軸坐標(biāo)
page.setTranslationY(position * mOffset);
此時(shí)運(yùn)行代碼我們就可以看到這樣的效果了:
不過此時(shí)會(huì)發(fā)現(xiàn)掏缎,頁(yè)面滑動(dòng)不了了,這是為什么呢煤杀,還記得我們之前設(shè)置的x軸偏移嗎眷蜈?
//移動(dòng)X軸左邊,使得卡片在同一坐標(biāo)
page.setTranslationX(-position * page.getWidth());
正常情況下沈自,手指往左滑酌儒,當(dāng)頁(yè)面從0到-1的時(shí)候是向左走的,但是我們這邊設(shè)置了x軸的移動(dòng)酥泛,隨著position的減小今豆,頁(yè)面是往右走的(正數(shù)為x軸右邊),剛好互相抵消了柔袁,所以這里我們需要對(duì)滑動(dòng)進(jìn)行特殊處理(順帶加了一個(gè)45°的旋轉(zhuǎn)動(dòng)畫):
if (position <= 0) {
//頁(yè)面滑動(dòng)的時(shí)候
page.setTranslationX(0f);
page.setRotation(45 * position);
page.setTranslationX((page.getWidth() / 2 * position));
} else {
//頁(yè)面不滑動(dòng)的時(shí)候
//移動(dòng)X軸坐標(biāo)呆躲,使得卡片在同一坐標(biāo)
page.setTranslationX(-position * page.getWidth());
//縮放卡片并調(diào)整位置
float scale = (page.getWidth() - mOffset * position) / page.getWidth();
page.setScaleX(scale);
page.setScaleY(scale);
//移動(dòng)Y軸坐標(biāo)
page.setTranslationY(position * mOffset);
}
到這里,以上的效果就都實(shí)現(xiàn)了捶索,有了這些基礎(chǔ)插掂,剩下的就靠你的想象力了,貧窮會(huì)限制你的想象力腥例,但代碼不會(huì)辅甥,快去實(shí)現(xiàn)自己的狂拽酷炫吊炸天的動(dòng)畫效果吧~
最后補(bǔ)充:
其實(shí)以上說(shuō)的這些效果還有一種更加優(yōu)雅的實(shí)現(xiàn)方法,就是利用RecyclerView實(shí)現(xiàn)燎竖,哈哈哈璃弄,先賣個(gè)關(guān)子,有時(shí)間再來(lái)寫吧~
源碼下載:
這里附上源碼地址(歡迎Star构回,歡迎Fork):源碼下載