敲酷炫的 ViewPager 切換效果和彈性指示器。

凶殘的程式設計師發表於2018-02-06

前言

前些天看到這個效果圖
GIF




效果真是酷炫極了,感覺很biu踢,我們們說做就做。

[改裝加強版,改進了圓入框的甩尾效果,最重要的一點是
增強ViewPager切換效果和卡片陰影]

效果圖

整合方式【伸手黨福利】

github地址 : github.com/qdxxxx/Bezi…
多謝老鐵隨手就是一個star,抱拳。
[標題黨一般是: 轉瘋了,專案整合此酷炫動畫只要3步!]

  • 注入依賴
    Step 1. Add the JitPack repository to your build file
    Step 2. Add the dependency
    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }複製程式碼
dependencies {
    compile 'com.github.qdxxxx:BezierViewPager:v1.0.5'
}複製程式碼


  • xml佈局程式碼
    <qdx.bezierviewpager_compile.vPage.BezierViewPager
        android:id="@+id/view_page"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <qdx.bezierviewpager_compile.BezierRoundView
        android:id="@+id/bezRound"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
     />複製程式碼


  • Activity裡面整合程式碼
 CardPagerAdapter cardAdapter = new CardPagerAdapter(getApplicationContext());
 cardAdapter.addImgUrlList(imgList);  //放置圖片url的list

BezierViewPager viewPager = (BezierViewPager) findViewById(R.id.view_page);
viewPager.setAdapter(cardAdapter);

BezierRoundView bezRound = (BezierRoundView) findViewById(R.id.bezRound);
bezRound.attach2ViewPage(viewPager);複製程式碼



方法及屬性介紹

  • BezierRoundView
nameformat中文解釋
color_bezcolor貝塞爾圓球顏色
color_touchcolor觸控反饋
color_strokecolor圓框的顏色
time_animatorinteger動畫時間
round_countinteger圓框數量,即Adapter.getCount
radiusdimension貝塞爾圓球半徑,圓框半徑為(radius-2)
attach2ViewPageBezierViewPager繫結指定的ViewPager(處理滑動時觸控事件)
並自動設定round_count


  • BezierViewPager[extends ViewPager]
nameformat中文解釋
showTransformerfloatViewPager滑動到當前顯示頁的放大比例


  • CardPagerAdapter[extends PagerAdapter]
nameformat中文解釋
addImgUrlListList包含圖片地址的list
setOnCardItemClickListenerOnCardItemClickListener當前ViewPager點選事件
返回CurPosition
setMaxElevationFactorintegerAdapter裡CardView最大的Elevation




實現解剖

1.從實現貝塞爾圓開始

[建議先看這篇文章]
貝塞爾圓



首先,我們需要繪製P0,然後 cubicTo p1,p2,p3,再cubicTo p4,p5.p6……
原諒我用這麼簡單粗暴的方式畫圓…

    private PointF p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11;
複製程式碼
        p0 = new PointF(0, -mRadius);//mRadius圓的半徑
        p6 = new PointF(0, mRadius);

        p1 = new PointF(mRadius * bezFactor, -mRadius);//bezFactor即0.5519...
        p5 = new PointF(mRadius * bezFactor, mRadius);

        p2 = new PointF(mRadius, -mRadius * bezFactor);
        p4 = new PointF(mRadius, mRadius * bezFactor);

        p3 = new PointF(mRadius, 0);
        p9 = new PointF(-mRadius, 0);

        p11 = new PointF(-mRadius * bezFactor, -mRadius);
        p7 = new PointF(-mRadius * bezFactor, mRadius);

        p10 = new PointF(-mRadius, -mRadius * bezFactor);
        p8 = new PointF(-mRadius, mRadius * bezFactor);複製程式碼

再繪製path

        mPath.moveTo(p0.x, p0.y);
        mPath.cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
        mPath.cubicTo(p4.x, p4.y, p5.x, p5.y, p6.x, p6.y);
        mPath.cubicTo(p7.x, p7.y, p8.x, p8.y, p9.x, p9.y);
        mPath.cubicTo(p10.x, p10.y, p11.x, p11.y, p0.x, p0.y);
        mPath.close();複製程式碼

圓
一個貝(ri)塞(ben)爾(guo)圓(qi)栩栩如生。


我們嘗試通過手指滑動改變,p2,p3,p4的x軸座標來觀察圓的變化

@Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_DOWN:
                p2 = new PointF(event.getX() - mWidth / 2, -mRadius * bezFactor);
                p3 = new PointF(event.getX() - mWidth / 2, 0);
                p4 = new PointF(event.getX() - mWidth / 2, mRadius * bezFactor);

                invalidate();
                break;
        }

        return true;
    }複製程式碼

這裡寫圖片描述這裡寫圖片描述這裡寫圖片描述


2.解剖效果圖

貝塞爾圓

這裡寫圖片描述 這裡寫圖片描述 這裡寫圖片描述


首先我們不考慮反彈效果,圓的變化有3種狀態

  1. bezier圓還沒離開圓框,p2,3,4 x軸座標由 , 變化至 2r
  2. bezier圓離開圓框,至到達中心位置
    [p2,3,4 x軸座標由 2r 變化至 1.5r ],[p8,9,10 x軸座標由 變化至 1.5r ]
  3. bezier圓由中心位置,至到達下一個圓框。
    [p2,3,4, 8,9,10 x軸座標由 1.5r 變化至 ]

老樣子,我們用ValueAnimator來模擬一下[0,1]變化的值。【因為ViewPager的onPageScrolled監聽中positionOffset是[0,1)變化的,類似。】

驚!下面幾段程式碼居然!男的看了沉默,女的看了流淚。

    //展示動畫
    private ValueAnimator animatorStart;
    private TimeInterpolator timeInterpolator = new DecelerateInterpolator();
    private float animatedValue; //[0,1]的值
    public void startAnimator() {
        if (animatorStart != null) {
            if (animatorStart.isRunning()) {
                return;
            }
            animatorStart.start();
        } else {
            animatorStart = ValueAnimator.ofFloat(0, 1f).setDuration(1500);
            animatorStart.setInterpolator(timeInterpolator);
            animatorStart.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    animatedValue = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            animatorStart.start();
        }
    }複製程式碼
    private float rRadio=1;  //P2,3,4 x軸倍數 
    private float lRadio=1;  //P8,9,10倍數
    private float tbRadio=1;  //y軸縮放倍數

    private float disL = 0.5f;   //離開圓的閾值
    private float disM = 0.8f;  //最大值的閾值
    private float disA = 0.9f;  //到達下個圓框的閾值複製程式碼
        if (0 < animatedValue && animatedValue <= disL) { //還沒離開圓框的時候
            rRadio = 1f + animatedValue * 2;              //[1,2]
        }
        if (disL < animatedValue && animatedValue <= disM) {//離開圓框,至最大值區域
            rRadio = 2 - range0Until1(disL, disM) * 0.5f;          //  [2,1.5]
            lRadio = 1 + range0Until1(disL, disM) * 0.5f;          // [1,1.5]

        }
        if (disM < animatedValue && animatedValue <= disA) {    //從最大值,至到達下一個圓框
            rRadio = 1.5f - range0Until1(disM, disA) * 0.5f;     //  [1.5,1]
            lRadio = 1.5f - range0Until1(disM, disA) * 0.5f;     //  [1.5,1]

        }複製程式碼
    /**
     * 將值域轉化為[0,1]
     *
     * @param minValue 大於等於
     * @param maxValue 小於等於
     * @return 根據當前 animatedValue,返回 [0,1] 對應的數值
     */
    private float range0Until1(float minValue, float maxValue) {
        return (animatedValue - minValue) / (maxValue - minValue);
    }複製程式碼

請再次原諒我用這麼簡單粗暴的方式畫圓…

        mPath.moveTo(p0.x, p0.y * tbRadio);
        mPath.cubicTo(p1.x, p1.y * tbRadio, p2.x * rRadio, p2.y, p3.x * rRadio, p3.y);
        mPath.cubicTo(p4.x * rRadio, p4.y, p5.x, p5.y * tbRadio, p6.x, p6.y * tbRadio);
        mPath.cubicTo(p7.x, p7.y * tbRadio, p8.x * lRadio, p8.y, p9.x * lRadio, p9.y);
        mPath.cubicTo(p10.x * lRadio, p10.y, p11.x, p11.y * tbRadio, p0.x, p0.y * tbRadio);
        mPath.close();
複製程式碼

理清了上面這些程式碼,一個有靈性的貝塞爾圓就即將繪製成功。我們再加上離開圓至到達下一個圓框這個區域y軸變化,[p,5,6,7, 1,0,11],效果就如下所示。
這裡寫圖片描述

3.模擬效果

這時候我們已經將貝塞爾圓的運動方式給表達出來了,再加上一些效果[位移/反彈/翻轉],我們就能模擬出貝塞爾圓從一個圓框進入下一個圓框的動畫了。
在上面的基礎上,我們加上反彈效果

        if (0 < animatedValue && animatedValue <= disL) { //還沒離開圓框的時候
            rRadio = 1f + animatedValue * 2;              //[1,2]
        }
        if (disL < animatedValue && animatedValue <= disM) {//離開圓框,至最大值區域
            rRadio = 2 - range0Until1(disL, disM) * 0.5f;          //  [2,1.5]
            lRadio = 1 + range0Until1(disL, disM) * 0.5f;          // [1,1.5]
            tbRadio = 1 - range0Until1(disL, disM) / 3;           // [1 , 2/3]
        }
        if (disM < animatedValue && animatedValue <= disA) {    //從最大值,至到達下一個圓框
            rRadio = 1.5f - range0Until1(disM, disA) * 0.5f;     //  [1.5,1]
            lRadio = 1.5f - range0Until1(disM, disA) * (1.5f - boundRadio);      //反彈效果,進場 內彈boundRadio  lRadio =[1.5,boundRadio]
            tbRadio = (range0Until1(disM, disA) + 2) / 3;        // [ 2/3,1]   
        }
         if (disA < animatedValue && animatedValue <= 1f) {//到達圓框,lRadio=[boundRadio,1]
            rRadio = 1;
            tbRadio = 1;
            lRadio = boundRadio + range0Until1(disA, 1) * (1 - boundRadio);     //反彈效果,飽和
        }複製程式碼

再加上位移效果。一開始我在想,貝塞爾圓要不斷的變化形態,還要移動位置。豈不相當的麻煩。後來把它分解成變化狀態+不斷位移效果。

        boolean isTrans = false;
        float transX = 1f;
        if (disL <= animatedValue && animatedValue <= disA) { //離開圓框,至到達下一個圓框
            isTrans = true;
            //我們設定2個圓框距離為mWidth / 2f
            transX = mWidth / 2f * range0Until1(disL, disA);  //[0,mWidth / 2f]
        }
        if (disA < animatedValue && animatedValue <= 1) {//到達下一個圓
            isTrans = true;
            transX = mWidth / 2;
        }

        if (isTrans) {
            canvas.translate(transX, 0);
        }複製程式碼

這裡寫圖片描述


至此貝塞爾圓球進入右側圓框的效果已經實現,那麼如果圓球要從右側圓框進入左側圓框呢?
【題外話:寫完上面這個效果已經是月黑風高的時候了,腦神經即將進入假死狀態,我心想,雖然複雜了點,但是應該還是可以做的出來的,腦袋執行的速度根本跟不上敲程式碼的速度。根據位移方向的判斷從而設定lRadio和rRadio。有點自信回頭的趕腳。。。休息了一覺第二天醒來天啊嚕,為什麼不用Matrix,只要用path.transform(matrix),就可以做到映象path,所以適當的休息有助於提升效率。】
這裡寫圖片描述

        matrix_bounceL = new Matrix();
        matrix_bounceL.preScale(-1, 1);

        mPath.transform(matrix_bounceL);
複製程式碼




4.Attach2ViewPager

關聯ViewPager總共有2個要點

  • ViewPager的滑動監聽,onPageScrolled。
    根據positionOffset和position,獲取我們所要的當前位置/下一個位置/移動方向。
  • 手動選擇ViewPager,即手指點選非當前圓框。

4.1 onPageScrolled

首先我們來了解一下onPageScrolled這個方法中2個我們要用到的引數

  • position : 當前cur位置,如果當前是1,手指按住右滑(vPage向左滑動)那就立馬變為0。但如果當前是1,手指按住要左滑至下一個位置才為2
  • positionOffset : [0,1) ,到達下一個pos就置為0

這裡寫圖片描述這裡寫圖片描述

我們功能需求分析一下:

  1. 獲取正確的當前位置curPos
  2. 獲取正確的貝塞爾球進入的下一個位置nextPos
  3. 獲取正確的貝塞爾球運動方向
  4. 配置正確的animatedValue

之前我們用ValueAnimator來模擬運動狀態,現在我們可以使用positionOffset關聯到ViewPager

        animatedValue = positionOffset;

        direction = ((position + positionOffset) - curPos > 0);  //運動方向。 true為右邊(手往左滑動)
        nextPos = direction ? curPos + 1 : curPos - 1;  //右 +1   左 -1

        if (!direction)   //如果是向左
            animatedValue = 1 - animatedValue;  //讓 animatedValue 不管是左滑還是右滑,都從[0,1)開始計算

        if (positionOffset == 0) { 
            curPos = position;
            nextPos = position;
        }
複製程式碼

以上程式碼還需動手除錯,看看log才能更明白的領悟。

這裡寫圖片描述
從上面的gif可以發現如果緩慢的滑動,pos的位置正確的,但是如果快速滑動,就會發現問題 : [例如0快速滑動到2,貝塞爾圓球會從0滑動到1,再從0滑動到2],打了Log之後我們才發現原來快速滑動的時候,positionOffset到達下一個pos不會置為0!!發現問題後就好解決了。我們加上這一段程式碼就可以解決該問題。(快速滑動可能存在或多或少的問題,我也是花了些時間去測試的。)
這裡寫圖片描述

        //快速滑動的時候,positionOffset有可能不會置於0
        if (direction && position + positionOffset > nextPos) {  //向右,而且
            curPos = position;
            nextPos = position + 1;
        } else if (!direction && position + positionOffset < nextPos) {
            curPos = position;
            nextPos = position - 1;
        }複製程式碼



onDraw
我們先要獲得每個圓框的圓心x軸座標

    private float[] bezPos; //記錄每一個圓心x軸的位置

    bezPos = new float[default_round_count];  //根據圓框個數
    for (int i = 0; i < default_round_count; i++) {
    bezPos[i] = mWidth / (default_round_count + 1) * (i + 1);
    }
複製程式碼

假設我們的default_round_count 即圓框個數為4,那麼我們就要分成 4+1 份,再綜合上述的求圓心程式碼,應該會更清晰一點。
這裡寫圖片描述


根據curPos和nextPos繪製貝塞爾圓球,po出onDraw程式碼

        canvas.translate(0, mHeight / 2);

        mBezPath.reset();
        for (int i = 0; i < default_round_count; i++) {
            canvas.drawCircle(bezPos[i], 0, mRadius - 2, mRoundStrokePaint);   //繪製圓框
        }
        if (animatedValue == 1) {
            canvas.drawCircle(bezPos[nextPos], 0, mRadius, mBezPaint);
            return;
        }

        canvas.translate(bezPos[curPos], 0); //根據curPos,移動到當前圓框位置

        if (0 < animatedValue && animatedValue <= disL) {
            rRadio = 1f + animatedValue * 2;                         //  [1,2]
            lRadio = 1f;
            tbRadio = 1f;
        }
        if (disL < animatedValue && animatedValue <= disM) {
            rRadio = 2 - range0Until1(disL, disM) * 0.5f;          //  [2,1.5]
            lRadio = 1 + range0Until1(disL, disM) * 0.5f;          // [1,1.5]
            tbRadio = 1 - range0Until1(disL, disM) / 3;           // [1 , 2/3]
        }
        if (disM < animatedValue && animatedValue <= disA) {
            rRadio = 1.5f - range0Until1(disM, disA) * 0.5f;     //  [1.5,1]
            lRadio = 1.5f - range0Until1(disM, disA) * (1.5f - boundRadio);      //反彈效果,進場 內彈boundRadio
            tbRadio = (range0Until1(disM, disA) + 2) / 3;        // [ 2/3,1]
        }
        if (disA < animatedValue && animatedValue <= 1f) {
            rRadio = 1;
            tbRadio = 1;
            lRadio = boundRadio + range0Until1(disA, 1) * (1 - boundRadio);     //反彈效果,飽和
        }
        if (animatedValue == 1 || animatedValue == 0) {  //防止極其粗暴的滑動
            rRadio = 1f;
            lRadio = 1f;
            tbRadio = 1f;
        }

        boolean isTrans = false;  //根據nextPos和curPos求出位移距離
        float transX = (nextPos - curPos) * (mWidth / (default_round_count + 1));
        if (disL <= animatedValue && animatedValue <= disA) {
            isTrans = true;
            transX = transX * (animatedValue - disL) / (disA - disL);
        }
        if (disA < animatedValue && animatedValue <= 1) {
            isTrans = true;
        }
        if (isTrans) {
            canvas.translate(transX, 0);
        }

        mBezPath.moveTo(p0.x, p0.y * tbRadio);
        mBezPath.cubicTo(p1.x, p1.y * tbRadio, p2.x * rRadio, p2.y, p3.x * rRadio, p3.y);
        mBezPath.cubicTo(p4.x * rRadio, p4.y, p5.x, p5.y * tbRadio, p6.x, p6.y * tbRadio);
        mBezPath.cubicTo(p7.x, p7.y * tbRadio, p8.x * lRadio, p8.y, p9.x * lRadio, p9.y);
        mBezPath.cubicTo(p10.x * lRadio, p10.y, p11.x, p11.y * tbRadio, p0.x, p0.y * tbRadio);
        mBezPath.close();

        if (!direction) {
            mBezPath.transform(matrix_bounceL);
        }
        canvas.drawPath(mBezPath, mBezPaint);

        if (isTrans) {
            canvas.save();
        }

複製程式碼


4.2 點選圓框,設定ViewPager的curItem

我們需要判斷是否點選到了圓框上,和點選了具體哪個圓框。
onPageScrolled方法的時候不進行處理,而是通過ValueAnimator來模擬數值。從而繪製貝塞爾圓球效果。

        private float[] xPivotPos;  //根據圓心x軸+mRadius,劃分成不同的區域 ,主要為了判斷觸控x軸的位置
        xPivotPos = new float[default_round_count];
        for (int i = 0; i < default_round_count; i++) {
            xPivotPos[i] = mWidth / (default_round_count + 1) * (i + 1) + mRadius;
        }複製程式碼

這裡寫圖片描述
針對x軸 : 我的做法是用一個陣列xPivotPos 儲存每個圓框最邊緣的位置,即圓心+mRadius,然後我們觸控的時候,就可以找到當前觸控touchPos是屬於哪個(圓框+mRadius)範圍內。只要x >=bezPos[touchPos]-mRadius,就可以清楚的知道是否觸控到了該區域的圓框範圍。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                float x = event.getX();
                float y = event.getY();

                if (y <= mHeight / 2 + mRadius && y >= mHeight / 2 - mRadius && !isAniming) {  //先判斷y,如果y點選是在圓y軸的範圍
                    int pos = -Arrays.binarySearch(xPivotPos, x) - 1;
                    if (pos >= 0 && pos < default_round_count && x + mRadius >= bezPos[pos]) {
                        nextPos = pos;
                        if (mViewPage != null && curPos != nextPos) {
                            mViewPage.setCurrentItem(pos);
                            isAniming = true;
                            direction = (curPos < pos);
                            startAnimator();  //我們通過ValueAnimator來模擬具體的值,不使用ViewPager的onPageScrolled方法。
                        }
                    }
                    return true;
                }
                break;
        }
        return super.onTouchEvent(event);
    }複製程式碼

至此我們BezierRoundView的用法和繪製方法已經講解完了,下面來看一下ViewPager是怎麼實現切換效果的。

實現ViewPager切換效果

參考【github.com/rubensousa/…

setClipToPadding

這裡寫圖片描述這裡寫圖片描述
【靈魂畫家】
上圖針對的是ViewPager設定Padding之後,

setClipToPadding 設定true,false不同的區別。 
左圖是正常情況下預設 setClipToPadding(true) 的顯示情況,設定Padding之後,手機螢幕上只顯示width-PaddingLeft - PaddingRight。 


而如果設定 setClipToPadding(false) 情況,表示不裁剪Padding,這時候我們就可以看到左右的ViewPager,相當於原本兩邊的Padding透明度為1,而設定false之後透明度為0。


setMaxCardElevation

CardPagerAdapter是我們繼承PagerAdapter的類,adapter裡的佈局是cardView

<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/cardView"
    app:cardCornerRadius="10dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:cardPreventCornerOverlap="true"
    app:cardUseCompatPadding="true">
    <!--cardUseCompatPadding 設定陰影之後自動縮小布局大小-->
    <ImageView
        android:id="@+id/item_iv"
        android:scaleType="fitXY"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</android.support.v7.widget.CardView>複製程式碼

先來了解一下cardView setCardElevation(float)方法。【針對CardViewApi21】

        if (!cardView.getUseCompatPadding()) {
            cardView.setShadowPadding(0, 0, 0, 0);
            return;
        }
        float elevation = getMaxElevation(cardView);
        final float radius = getRadius(cardView);
        int hPadding = (int) Math.ceil(RoundRectDrawableWithShadow
                .calculateHorizontalPadding(elevation, radius, cardView.getPreventCornerOverlap()));
        int vPadding = (int) Math.ceil(RoundRectDrawableWithShadow
                .calculateVerticalPadding(elevation, radius, cardView.getPreventCornerOverlap()));
        cardView.setShadowPadding(hPadding, vPadding, hPadding, vPadding);複製程式碼
    static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
            boolean addPaddingForCorners) {
        if (addPaddingForCorners) {
            return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
        } else {
            return maxShadowSize * SHADOW_MULTIPLIER;
        }
    }

    static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
            boolean addPaddingForCorners) {
        if (addPaddingForCorners) {
            return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
        } else {
            return maxShadowSize;
        }
    }複製程式碼

下面看一下效果測試。

ViewPager效果測試

我們來看一下ViewPager左右設定Padding為mWidth / 10的效果

        viewPager.setPadding(mWidth / 10, 0, mWidth / 10, 0);
        viewPager.setClipToPadding(false);複製程式碼

這裡寫圖片描述

再來看一下CardPagerAdapter設定MaxElevationFactor為mWidth / 10的效果【adapter.xml的cardCornerRadius不設值,cardUseCompatPadding一定要設定true!!】

        int maxFactor = mWidth / 10;
        cardAdapter.setMaxElevationFactor(maxFactor);複製程式碼

這裡寫圖片描述

具體我也不贅述了,看圖應該能分析出兩者的不同。
所以現在綜上所述,制定一個需求

  • 不管是設定padding還是Elevation都要保持圖片的寬高比例。

也就是說當我們知道圖片的寬高比例之後,程式碼裡面我們要動態的去調整和設定並保持這個寬高比例。 


【這邊有個坑就是設定setMaxElevation它的寬高比是不可抗的,所以我們只能在setPadding的時候,去調節這個比例】 


【setMaxElevation
寬的Padding為maxFactor + 0.3*CornerRadius 【0.3≈≈ (1 - COS_45)】
高的Padding為maxFactor*1.5f + 0.3*CornerRadius】

這裡寫圖片描述

但是!

(雞生的)
如果我們在setMaxElevation的情況下,在去設定padding,那麼如何保證我們的寬高比?具體請看如下程式碼分析。【可以通過去掉adapter.xml 裡ImagerView 的android:scaleType=”fitXY”屬性測試一下寬高比例是否除錯正確

        //已知圖片的寬為1920,高1080.
        int mWidth = getWindowManager().getDefaultDisplay().getWidth();
        float heightRatio = 0.565f;  //高是寬的 0.565 ,根據圖片比例

        CardPagerAdapter cardAdapter = new CardPagerAdapter(getApplicationContext());
        cardAdapter.addImgUrlList(imgList);//新增載入的圖片集合


        //設定陰影大小,即vPage  左右兩個圖片相距邊框  maxFactor + 0.3*CornerRadius   *2
        //設定陰影大小,即vPage 上下圖片相距邊框  maxFactor*1.5f + 0.3*CornerRadius
        int maxFactor = mWidth / 25;
        cardAdapter.setMaxElevationFactor(maxFactor);

        int mWidthPading = mWidth / 8;
        //因為我們adapter裡的cardView CornerRadius已經寫死為10dp,所以0.3*CornerRadius=3
        //設定Elevation之後,控制元件寬度要減去 (maxFactor + dp2px(3)) * heightRatio
        //heightMore 設定Elevation之後,控制元件高度 比  控制元件寬度* heightRatio  多出的部分
        float heightMore = (1.5f * maxFactor + dp2px(3)) - (maxFactor + dp2px(3)) * heightRatio;
        int mHeightPading = (int) (mWidthPading * heightRatio - heightMore);

        BezierViewPager viewPager = (BezierViewPager) findViewById(R.id.view_page);
        viewPager.setLayoutParams(new RelativeLayout.LayoutParams(mWidth, (int) (mWidth * heightRatio)));
        viewPager.setPadding(mWidthPading, mHeightPading, mWidthPading, mHeightPading);
        viewPager.setClipToPadding(false);
        viewPager.setAdapter(cardAdapter);複製程式碼


showTransformer

改方法是設定ViewPager移動的時候,cardView放大效果和Elevation陰影效果,具體過程可以自行在ShadowTransformer檢視,實現過程上文基本也有覆蓋。

總結

零零碎碎也搗鼓了一陣子的自定義View,我在想既然邁出這一步了,就得做好它。

人生總是要有信仰,有夢想才能一直前行,哪怕走的再慢,也是在前行。

如果這篇文章寫的還湊合或者勾引起了你的鬥志的話,歡迎點個star
github.com/qdxxxx/Bezi…


相關文章