《自定義控件:xfermode+貝塞爾曲線實(shí)現(xiàn)動(dòng)態(tài)水波紋》

1.效果

換了大王之后审胸,順帶就下拉聯(lián)通手機(jī)營(yíng)業(yè)廳App,剛開(kāi)始用卸勺,出于好奇每天都會(huì)看看今天自己通過(guò)免流用了多少流量砂沛。
查看流量的入口長(zhǎng)這樣如下圖


由于流量為0,不顯示水波紋效果曙求,下面是我實(shí)現(xiàn)的水波紋效果碍庵,這樣的效果在很多App中都用到。


效果圖

預(yù)告:

實(shí)現(xiàn)上面的效果將使用到以下知識(shí)點(diǎn)

  • 1 xfermode(用的比較少)
  • 2 貝塞爾曲線(挺常用的悟狱,Path中提供了相關(guān)Api)
  • 3 屬性動(dòng)畫(huà)(太常用了静浴,這里不多說(shuō)了)

2.分步實(shí)現(xiàn)

2.1分析

2.1.1 定義屬性

如果從自定義的view的角度來(lái)時(shí)實(shí)現(xiàn),那么首先考慮的是這個(gè)view可改變的屬性是什么挤渐,比如邊框的顏色苹享、水波峰高度值、移動(dòng)的快慢等浴麻。在布局文件中通過(guò)配置這些屬性得问,讓view呈現(xiàn)不同的效果,如下圖白胀。

  <declare-styleable name="XFPView">
        <!--水波紋顏色-->
        <attr name="backgroundColorWave" format="color"/>
        <!--圓心填充顏色-->
        <attr name="backgroundColorRound" format="color"/>
        <!--圓的邊框顏色-->
        <attr name="backgroundColorRoundBoder" format="color"/>
        <!--波峰突出值-->
        <attr name="waveDy" format="integer"/>
        <!--最大完成值-->
        <attr name="percentMax" format="float"/>
        <!--動(dòng)畫(huà)移動(dòng)時(shí)長(zhǎng)(毫秒椭赋,建議大于1000,小于5000)-->
        <attr name="durationAnim" format="integer"/>
    </declare-styleable>

定義好了屬性或杠,這樣用起來(lái)方便哪怔,不用修改代碼,只通過(guò)改變xml中的屬性值來(lái)就可以改變view的相關(guān)狀態(tài)(讓你同事去修改你的代碼他會(huì)生氣的)向抢,接下按自定義view的步驟應(yīng)該要繼承View认境,重寫(xiě)onMeasure方法、最后是onDraw方法(套路)挟鸠。這個(gè)過(guò)程看似簡(jiǎn)單叉信,其實(shí)考慮的細(xì)節(jié)很多(紙上得來(lái)終覺(jué)淺)。

那么就來(lái)吧K蚁!E鹕怼硅急!

2.1.2 自定義XfPathView

先寫(xiě)一個(gè)類(lèi)繼承view,直接上代碼佳遂。

/**
* Created by Zhoudesen
* Created time 2018/1/29 16:20
* Description: Xfermode + Path之貝塞爾曲線 應(yīng)用
* Version: V 1.0
*/
public class XfPathView extends View {
    /**
    * 圓形畫(huà)筆
    */
    private Paint mPaintRound;
    /**
    * 水波紋畫(huà)筆
    */
    private Paint mPaintWave;
    /**
    * 最小寬度
    */
    private final static int MIN_WIDTH = 300;
    /**
    * 最小高度
    */
    private final static int MIN_HEIGHT = 300;
    /**
    * 水波紋 背景顏色
    */
    private int mColorBgWave;
    /**
    * 水波紋 默認(rèn)顏色
    */
    private final static int COLOR_BG_WAVE_DEFUALT = 0xaa00ff00;
    /**
    * 圓形boder顏色
    */
    private int mColorBgRoundBoder;
    /**
    * 圓形背景顏色
    */
    private int mColorBgRound;
    /**
    * 圓形 默認(rèn)顏色 完全透明色
    */
    private final static int COLOR_BG_ROUND_DEFUALT = 0x00000000;
    /**
    * y 軸百分比
    */
    private float mPercentY = 0f;
    /**
    * 最大完成值
    */
    private float mPercentMax;
    /**
    * 1/2 波峰x軸長(zhǎng)度(每個(gè)波峰/波谷均為貝塞爾曲線的控制點(diǎn))
    */
    private float mRadius;
    /**
    * x 軸偏移量(讓水波紋動(dòng)起來(lái))
    */
    private float mDx = 0;
    /**
    * y 軸波峰突出值(波谷凹陷值)
    */
    private int mDy = 0;
    /**
    * view高寬值(高=寬)
    */
    private int mViewWidthHeight;
    private Path mPath;
    private Bitmap mBitmap;
    private ValueAnimator mAnimator;
    private PorterDuffXfermode mPorterDuffXfermode;
    private RectF mRectf;
    public XfPathView(Context context) {
        this(context, null);
    }
    public XfPathView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public XfPathView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }
    /**
    * 初始化參數(shù)
    *
    * @param context
    * @param attrs
    */
    private void init(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.XFPView);
            mColorBgRound = typedArray.getColor(R.styleable.XFPView_backgroundColorRound, COLOR_BG_ROUND_DEFUALT);
            mColorBgRoundBoder = typedArray.getColor(R.styleable.XFPView_backgroundColorRoundBoder, COLOR_BG_WAVE_DEFUALT);
            mColorBgWave = typedArray.getColor(R.styleable.XFPView_backgroundColorWave, COLOR_BG_WAVE_DEFUALT);
            mPercentMax = typedArray.getFloat(R.styleable.XFPView_percentMax, 100);
            mDy = typedArray.getInteger(R.styleable.XFPView_waveDy, 20);
            typedArray.recycle();
        }
        mPath = new Path();
        //畫(huà)筆初始化
        mPaintRound = new Paint();
        mPaintRound.setAntiAlias(true);
        mPaintRound.setStyle(Paint.Style.STROKE);
        mPaintRound.setStrokeWidth(3);
        mPaintRound.setColor(mColorBgRoundBoder);
        mPaintWave = new Paint();
        mPaintWave.setAntiAlias(true);
        mPaintWave.setColor(mColorBgWave);
        mPaintWave.setStyle(Paint.Style.FILL);
        mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
        mRectf = new RectF();
    }

以上代碼初始化了畫(huà)筆营袜,顏色等相關(guān)基礎(chǔ)工作,其中倒數(shù)第二行PorterDuffXfermode丑罪,才是主角荚板,把主角放到最后登場(chǎng)(這里有坑)。

接下來(lái)開(kāi)始對(duì)view進(jìn)行測(cè)量,這個(gè)view高度和寬度是相等的吩屹,如果高度和寬度不相等跪另,那么取小的值設(shè)置為它的高寬。視乎也很簡(jiǎn)單煤搜,就直接看下面代碼吧免绿。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = 0;
        int width = 0;
        if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
            width = MeasureSpec.getSize(widthMeasureSpec);
        }
        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
            height = MeasureSpec.getSize(heightMeasureSpec);
        }
        //取最小值為view的高寬,讓“高”=“寬”
        width = Math.min(width, height);
        if ( width > MIN_WIDTH) {      
            setMeasuredDimension(width, width);
        } else {
            setMeasuredDimension(MIN_WIDTH, MIN_HEIGHT);
        }
    }

到這里宅楞,已經(jīng)完成了自定義相關(guān)屬性针姿,獲取了屬性值袱吆,并且完成初始化準(zhǔn)備厌衙,還重寫(xiě)了onMeasure測(cè)量了高寬。接下來(lái)就是onDraw绞绒,在onDraw之前要分析先畫(huà)什么婶希,后畫(huà)什么,還要計(jì)算相關(guān)的坐標(biāo)蓬衡,所以先分析吧喻杈。

先從水波紋的實(shí)現(xiàn)說(shuō)起(盡管大家都知道,但還是要啰嗦一下 >溫故而知新嘛)

2.1.3水波紋的實(shí)現(xiàn)

畫(huà)一條線或多邊形(或閉合曲線)總得有一個(gè)起點(diǎn)吧狰晚。

  • 【起點(diǎn)】
    Path.moveTo(x,y)
    有了點(diǎn)就可以畫(huà)線了筒饰,就先看下面點(diǎn)曲線是怎么實(shí)現(xiàn)的

  • 【畫(huà)曲線】(就是貝塞爾曲線)
    Path.quadTo(x1,y1,x2,y2);

quadTo(x1,y1,x2,y2)這個(gè)函數(shù)傳入兩個(gè)坐標(biāo),第一個(gè)是控制點(diǎn)壁晒,第二個(gè)是這段曲線的終點(diǎn)瓷们。

這個(gè)實(shí)際上就是二階貝塞爾曲線(還有三階、四階秒咐、用的不多) 谬晕。

quadTo是核心方法,各種曲線效果都是由第一個(gè)控制點(diǎn)的變化而變化的携取。當(dāng)然它是有一個(gè)公式的攒钳,但是Path Api中封裝好了,只管用就好(你所關(guān)注的就是調(diào)方法雷滋,傳坐標(biāo)參數(shù))不撑。

  • 【鏈接到某一個(gè)點(diǎn)】(直線)
    Path.lineTo(x,y) ;

  • 【首尾相連】構(gòu)成一個(gè)閉合的路徑(直線)
    Path.close();

下圖是一個(gè)帶有水波紋的閉合圖形文兢,看看它是怎么實(shí)現(xiàn)的

畫(huà)出水波過(guò)程圖

解釋一下上圖吧

moveTo(點(diǎn)0) 起始點(diǎn)
quadTo(點(diǎn)1,點(diǎn)2)焕檬;
quadTo(點(diǎn)3禽作,點(diǎn)4);
quadTo(點(diǎn)5揩页,點(diǎn)6)旷偿;
quadTo(點(diǎn)7,點(diǎn)8)爆侣;
沒(méi)錯(cuò)萍程,水波紋就是有一段段貝塞爾曲線拼接而來(lái)的。
lineTo(點(diǎn)9)
lineTo(點(diǎn)10)
close()將首尾相連(圖中紅色線部分)

通過(guò)Path的四個(gè)方法:moveTo兔仰、quadTo茫负、lineTo和close。已經(jīng)能滿足畫(huà)任意閉合的圖形了乎赴,當(dāng)然Path遠(yuǎn)不止這些忍法,下回分解。

下圖:實(shí)現(xiàn)了略帶水波紋的閉合圖形


顯示了一半榕吼、通過(guò)移動(dòng)饿序、不斷循環(huán)形成水波效果

2.1.4 onDraw

我們最終要實(shí)現(xiàn)的效果是這樣的


最終的效果

可以拆分成兩部分來(lái)畫(huà)
1、在onDraw中可以先畫(huà)一個(gè)外邊圓的白色邊框boder羹蚣。
2原探、在畫(huà)里面的動(dòng)態(tài)水波紋。

水波紋的實(shí)現(xiàn)上述已經(jīng)介紹過(guò)了顽素,畫(huà)一個(gè)圓邊框視乎也毫不費(fèi)勁咽弦,上代碼。


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //--畫(huà)圓boder
        drawBoder(canvas);

        //--畫(huà)水波紋
        drawWave(canvas);
   }

 private void drawBoder(Canvas canvas){
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mViewWidthHeight / 2 - 1, mPaintRound);
    }

drawWave(canvas)的代碼也很簡(jiǎn)單胁出,

private void drawWave(Canvas canvas){
        mPath.reset();
        //起點(diǎn)
        mPath.moveTo(0 - mDx, mPercentY * mViewWidthHeight);
        //四段曲線
        for (int i = 0; i < 4; i++) {
            mDy = -mDy;
            mPath.quadTo((1 + 2 * i) * mRadius - mDx, mPercentY * mViewWidthHeight + mDy,
                    (1 + i) * 2 * mRadius - mDx, mPercentY * mViewWidthHeight);
        }
        //連接到view的右邊底部
        mPath.lineTo(2 * mViewWidthHeight - mDx, mViewWidthHeight);
        //連接到view的左邊底部
        mPath.lineTo(0 - mDx, mViewWidthHeight);
        //閉合
        mPath.close();
        canvas.drawPath(mPath, mPaintWave);
    }

mDx是X軸上的偏移量型型,通過(guò)改變mDx,就可以讓水波紋動(dòng)起來(lái)全蝶。
mDy是施加給控制點(diǎn)的Y軸坐標(biāo)的闹蒜,其中mDy = - mDy (取反)是為了實(shí)現(xiàn)每段曲線上下波動(dòng)效果的。

為了突出效果裸诽,畫(huà)的是一個(gè)填充的圓
實(shí)際效果如下圖


需要裁剪的部分

你發(fā)現(xiàn)了什么嫂用?
箭頭所指的兩塊白色區(qū)域并不是我們想要的,隨著水波紋的上升丈冬,圓的上部分也將會(huì)突出,這么解決這個(gè)問(wèn)題嘱函,話不多說(shuō),讓主角xfermode登場(chǎng)埂蕊。

2.1.5 Xfermode

  • 它是干嘛用的往弓?
    答:通過(guò)使用Xfermode將繪制的圖形的像素和Canvas上對(duì)應(yīng)位置的像素按照一定的規(guī)則進(jìn)行混合疏唾,形成新的像素,再更新到Canvas中形成最終的圖形

  • 它怎么用函似?
    答:通過(guò)Paint.setXfermode

  • 代碼片段

image.png

關(guān)于這段代碼片段有幾點(diǎn)記錄一下:

  • saveLayer 會(huì)產(chǎn)生一個(gè)新的圖層槐脏,之后的畫(huà)操作都是在這個(gè)圖層上進(jìn)行
  • restoreToCount(int layer),將指定的圖層繪制到cavas.

更進(jìn)一步
實(shí)際上Xfermode有三個(gè)子類(lèi):AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,前兩個(gè)類(lèi)在API 16被遺棄了撇寞,PorterDuffXfermode才是我們需要關(guān)注的顿天。在初始化的時(shí)候用的就是這個(gè)子類(lèi)。

看看它是真么實(shí)例化的:
mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);

PorterDuff.Mode蔑担,這個(gè)是什么牌废?SRC_IN代表什么?

看源碼:

// these value must match their native equivalents. See SkXfermode.h
    public enum Mode {
        /** [0, 0] */
        CLEAR       (0),
        /** [Sa, Sc] */
        SRC         (1),
        /** [Da, Dc] */
        DST         (2),
        /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
        SRC_OVER    (3),
        /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
        DST_OVER    (4),
        /** [Sa * Da, Sc * Da] */
        SRC_IN      (5),
        /** [Sa * Da, Sa * Dc] */
        DST_IN      (6),
        /** [Sa * (1 - Da), Sc * (1 - Da)] */
        SRC_OUT     (7),
        /** [Da * (1 - Sa), Dc * (1 - Sa)] */
        DST_OUT     (8),
        /** [Da, Sc * Da + (1 - Sa) * Dc] */
        SRC_ATOP    (9),
        /** [Sa, Sa * Dc + Sc * (1 - Da)] */
        DST_ATOP    (10),
        /** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
        XOR         (11),
        /** [Sa + Da - Sa*Da,
             Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
        DARKEN      (16),
        /** [Sa + Da - Sa*Da,
             Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
        LIGHTEN     (17),
        /** [Sa * Da, Sc * Dc] */
        MULTIPLY    (13),
        /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
        SCREEN      (14),
        /** Saturate(S + D) */
        ADD         (12),
        OVERLAY     (15);

        Mode(int nativeInt) {
            this.nativeInt = nativeInt;
        }

        /**
         * @hide
         */
        public final int nativeInt;
    }

PorterDuff.Mode它是一個(gè)枚舉類(lèi)型啤握,而SRC_IN只是其中一個(gè)模式鸟缕。

看一張圖官方給的參考圖


各種模式的混合結(jié)果
image.png

圖中已經(jīng)列出了兩張圖(Src和Dst)對(duì)應(yīng)不同Mode所呈現(xiàn)的效果,參考這些模式對(duì)應(yīng)的混合效果排抬,實(shí)現(xiàn)的效果也太豐富了懂从。是時(shí)候開(kāi)始改寫(xiě)onDraw方法了。

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //--畫(huà)圓boder
        drawBoder(canvas);

        //--saveLayer
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);

        //--畫(huà)水波紋
        drawWave(canvas);

        canvas.restoreToCount(layerId);
    }

根據(jù)之前的分析混合繪制要在一張新的圖層進(jìn)行所以執(zhí)行saveLayer方法蹲蒲,繪制完成后restoreToCount方法將混合后的圖層繪制到canvas上碉钠。

看看drawWave(canvas)中是如何繪制的

 private void drawWave(Canvas canvas){
        //--畫(huà)圓心
        mPaintWave.setColor(mColorBgRound);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, (mViewWidthHeight / 2) - 3, mPaintWave);

        //--畫(huà)水波紋
        mPath.reset();
        mPath.moveTo(0 - mDx, mPercentY * mViewWidthHeight);
        //四個(gè)點(diǎn)
        for (int i = 0; i < 4; i++) {
            mDy = -mDy;
            mPath.quadTo((1 + 2 * i) * mRadius - mDx, mPercentY * mViewWidthHeight + mDy,
                    (1 + i) * 2 * mRadius - mDx, mPercentY * mViewWidthHeight);
        }
        //連接到右邊view底部
        mPath.lineTo(2 * mViewWidthHeight - mDx, mViewWidthHeight);
        //連接到左邊底部
        mPath.lineTo(0 - mDx, mViewWidthHeight);
        //閉合
        mPath.close();

        //--圓與水波紋fix
        mPaintWave.setColor(mColorBgWave);
        mPaintWave.setXfermode(mPorterDuffXfermode);
        canvas.drawPath(mPath, mPaintWave);

        mPaintWave.setXfermode(null);
    }

畫(huà)一個(gè)圓與水波紋進(jìn)行混合忿檩,去除超出圓的部分沮焕。
混合前


image.png

Srcin這個(gè)模式符合我們的需求


image.png

看效果圖


image.png

哇参滴,水波紋底部已經(jīng)實(shí)現(xiàn)了我們的效果,but,上部分是什么鬼(這是混合圓的顏色)咖祭。需求可是要透明的啊,好那么就把這個(gè)圓改成透明色把蔫骂,我們已經(jīng)在xml中定義了顏色屬性么翰,很容易改,然后運(yùn)行得到如下結(jié)果辽旋。


image.png

圓是透明了浩嫌,水波紋也不見(jiàn)了〔古撸看來(lái)是想的太簡(jiǎn)單了

因?yàn)橥该鞫葧?huì)影響混合效果码耐,完全透明那么混合夠也透明了

那么圓不能完全透明,如果這個(gè)需求不要背景完全透明溶其,那么也就完事了骚腥,下圖Srctop模式的效果


image.png

好吧,接著又找了很幾種相像的模式試了一遍瓶逃,就是不行束铭。

機(jī)智時(shí)刻:既然圓不能完全透明廓块,和水波紋混合后上部的底色又得透明,那么可以通過(guò)一個(gè)完全透明矩形和上部分底色混合契沫,就可以消除了带猴。如下圖


image.png

看最終效果


image.png
//--用完全透明的矩形消除圓的上部分
        mPaintWave.setColor(COLOR_BG_ROUND_DEFUALT);
        mRectf.bottom = mViewWidthHeight * mPercentY - mDy;

完整代碼:


import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;

import itsen.com.bduidemo.R;
import itsen.com.bduidemo.lib.tool.LogTool;

/**
 * Created by Zhoudesen
 * Created time 2018/1/29 16:20
 * Description: Xfermode Path之貝塞爾曲線 應(yīng)用
 * Version: V 1.0
 */

public class XfPathView extends View {
    /**
     * 圓形畫(huà)筆
     */
    private Paint mPaintRound;
    /**
     * 水波紋畫(huà)筆
     */
    private Paint mPaintWave;
    /**
     * 最小寬度
     */
    private final static int MIN_WIDTH = 300;
    /**
     * 最小高度
     */
    private final static int MIN_HEIGHT = 300;
    /**
     * 水波紋 背景顏色
     */
    private int mColorBgWave;
    /**
     * 水波紋 默認(rèn)顏色
     */
    private final static int COLOR_BG_WAVE_DEFUALT = 0xaa00ff00;
    /**
     * 圓形boder顏色
     */
    private int mColorBgRoundBoder;
    /**
     * 圓形背景顏色
     */
    private int mColorBgRound;
    /**
     * 圓形 默認(rèn)顏色 完全透明色
     */
    private final static int COLOR_BG_ROUND_DEFUALT = 0x00000000;
    /**
     * y 軸百分比
     */
    private float mPercentY = 0f;
    /**
     * 最大完成值
     */
    private float mPercentMax;
    /**
     * 1/2 波峰x軸長(zhǎng)度(每個(gè)波峰/波谷均為貝塞爾曲線的控制點(diǎn))
     */
    private float mRadius;
    /**
     * x 軸偏移量(讓水波紋動(dòng)起來(lái))
     */
    private float mDx = 0;
    /**
     * y 軸波峰突出值(波谷凹陷值)
     */
    private int mDy = 0;
    /**
     * view高寬值(高=寬)
     */
    private int mViewWidthHeight;

    private Path mPath;

    private Bitmap mBitmap;

    private ValueAnimator mAnimator;

    private PorterDuffXfermode mPorterDuffXfermode;

    private RectF mRectf;

    public XfPathView(Context context) {
        this(context, null);
    }

    public XfPathView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public XfPathView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    /**
     * 初始化參數(shù)
     *
     * @param context
     * @param attrs
     */
    private void init(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.XFPView);
            mColorBgRound = typedArray.getColor(R.styleable.XFPView_backgroundColorRound, COLOR_BG_ROUND_DEFUALT);
            mColorBgRoundBoder = typedArray.getColor(R.styleable.XFPView_backgroundColorRoundBoder, COLOR_BG_WAVE_DEFUALT);
            mColorBgWave = typedArray.getColor(R.styleable.XFPView_backgroundColorWave, COLOR_BG_WAVE_DEFUALT);
            mPercentMax = typedArray.getFloat(R.styleable.XFPView_percentMax, 100);
            mDy = typedArray.getInteger(R.styleable.XFPView_waveDy, 20);
            typedArray.recycle();
        }

        mPath = new Path();
        //畫(huà)筆初始化
        mPaintRound = new Paint();
        mPaintRound.setAntiAlias(true);
        mPaintRound.setStyle(Paint.Style.STROKE);
        mPaintRound.setStrokeWidth(3);
        mPaintRound.setColor(mColorBgRoundBoder);

        mPaintWave = new Paint();
        mPaintWave.setAntiAlias(true);
        mPaintWave.setColor(mColorBgWave);
        mPaintWave.setStyle(Paint.Style.FILL);

        mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);

        mRectf = new RectF(0,0,mViewWidthHeight,0);

        setmPercentY(10);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = 0;
        int width = 0;
        if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
            width = MeasureSpec.getSize(widthMeasureSpec);
        }
        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
            height = MeasureSpec.getSize(heightMeasureSpec);
        }
        //取最小值為view的高寬,讓“高”=“寬”
        width = Math.min(width, height);

        if (width > 0 && width > MIN_WIDTH) {
            setMeasuredDimension(width, width);
        } else {
            setMeasuredDimension(MIN_WIDTH, MIN_HEIGHT);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mViewWidthHeight = w;
        //二分之一的波峰波谷
        mRadius = mViewWidthHeight / 4;
        if (mBitmap == null) {
            mBitmap = Bitmap.createBitmap(mViewWidthHeight, mViewWidthHeight, Bitmap.Config.ARGB_8888);
        }
        startAinm();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //--畫(huà)圓boder
        drawBoder(canvas);

        //--saveLayer
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);

        //--畫(huà)水波紋
        drawWave(canvas);

        canvas.restoreToCount(layerId);
    }

    private void drawBoder(Canvas canvas){
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mViewWidthHeight / 2 - 1, mPaintRound);
    }

    private void drawWave(Canvas canvas){
        //--畫(huà)圓心
        mPaintWave.setColor(mColorBgRound);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, (mViewWidthHeight / 2) - 3, mPaintWave);

        //--畫(huà)水波紋
        mPath.reset();
        mPath.moveTo(0 - mDx, mPercentY * mViewWidthHeight);
        //四個(gè)點(diǎn)
        for (int i = 0; i < 4; i++) {
            mDy = -mDy;
            mPath.quadTo((1 + 2 * i) * mRadius - mDx, mPercentY * mViewWidthHeight + mDy,
                    (1 + i) * 2 * mRadius - mDx, mPercentY * mViewWidthHeight);
        }
        //連接到右邊view底部
        mPath.lineTo(2 * mViewWidthHeight - mDx, mViewWidthHeight);
        //連接到左邊底部
        mPath.lineTo(0 - mDx, mViewWidthHeight);
        //閉合
        mPath.close();

        //--圓與水波紋fix
        mPaintWave.setColor(mColorBgWave);
        mPaintWave.setXfermode(mPorterDuffXfermode);
        canvas.drawPath(mPath, mPaintWave);

        //--用完全透明的矩形消除圓的上部分
        mPaintWave.setColor(COLOR_BG_ROUND_DEFUALT);
        mRectf.right = mViewWidthHeight;
        mRectf.bottom = mViewWidthHeight * mPercentY - mDy;
        canvas.drawRect(mRectf, mPaintWave);

        mPaintWave.setXfermode(null);
    }
    /**
     * 初始化并開(kāi)始動(dòng)畫(huà)
     */
    public void startAinm() {
        mAnimator = ValueAnimator.ofFloat(0, mViewWidthHeight);
        mAnimator.setDuration(2000);
        mAnimator.setInterpolator(new LinearInterpolator());
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mDx = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        mAnimator.start();
    }


    /**
     * 暫停
     */
    public void stopAnim() {
        if (mAnimator.isStarted()) {
            mAnimator.pause();
        }
    }

    /**
     * 重啟
     */
    public void reStartAinm() {
        if (mAnimator != null && mAnimator.isPaused()) {
            mAnimator.start();
        }
    }

    /**
     * 設(shè)置完成度
     *
     * @param value
     */
    public void setmPercentY(float value) {
        this.mPercentY = 1 - (value / mPercentMax < 1 ? value / mPercentMax : 1);
    }
}

水平有限懈万,疏漏之處請(qǐng)批評(píng)指正

寫(xiě)作不易拴清、喜歡的給個(gè)星,謝謝会通!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贷掖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子渴语,更是在濱河造成了極大的恐慌苹威,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驾凶,死亡現(xiàn)場(chǎng)離奇詭異牙甫,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)调违,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)窟哺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人技肩,你說(shuō)我怎么就攤上這事且轨。” “怎么了虚婿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵旋奢,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我然痊,道長(zhǎng)至朗,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任剧浸,我火速辦了婚禮锹引,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唆香。我一直安慰自己嫌变,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布躬它。 她就那樣靜靜地躺著腾啥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碑宴,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天软啼,我揣著相機(jī)與錄音,去河邊找鬼延柠。 笑死祸挪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贞间。 我是一名探鬼主播贿条,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼增热!你這毒婦竟也來(lái)了整以?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤峻仇,失蹤者是張志新(化名)和其女友劉穎公黑,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體摄咆,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凡蚜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吭从。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朝蜘。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涩金,靈堂內(nèi)的尸體忽然破棺而出谱醇,到底是詐尸還是另有隱情,我是刑警寧澤步做,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布副渴,位于F島的核電站,受9級(jí)特大地震影響辆床,放射性物質(zhì)發(fā)生泄漏佳晶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一讼载、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧中跌,春花似錦咨堤、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春凸克,著一層夾襖步出監(jiān)牢的瞬間议蟆,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工萎战, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咐容,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓蚂维,卻偏偏與公主長(zhǎng)得像戳粒,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子虫啥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,747評(píng)論 25 707
  • 上一篇我們說(shuō)了 Path 的基本操作蔚约,這一篇讓我們來(lái)說(shuō)一下 Path 的進(jìn)階用法——貝塞爾曲線。 那什么是貝塞爾曲...
    一團(tuán)撈面閱讀 5,739評(píng)論 5 36
  • 九月一號(hào)是女兒上學(xué)第二天涂籽,本以為做好飯?jiān)俳兴鸫财凰睿伤缭缇推鸫擦耍f(shuō)媽媽?zhuān)何乙ド蠈W(xué)评雌,可不要遲到了树枫。看她興致這...
    孫佳婧媽媽閱讀 140評(píng)論 0 0
  • 初讀《逍遙游》舔清,其詞瑰麗,其想奇特曲初,不禁感嘆体谒,莊子真牛×也臼婆。時(shí)隔多年抒痒,秋水又至,夜風(fēng)微涼颁褂,與隱士熊逸再品逍遙故响,留在...
    張永勝_永往直前閱讀 448評(píng)論 1 1
  • 最近在學(xué)習(xí)設(shè)計(jì)模式,本人現(xiàn)在工作與程序員無(wú)關(guān)颁独,在一個(gè)普通的工廠工作彩届,所以首先從簡(jiǎn)單工廠模式開(kāi)始學(xué)習(xí),畢竟本人就在工...
    靜守歲月中閱讀 223評(píng)論 0 1