《自定义控件:xfermode+贝塞尔曲线实现动态水波纹》

1.效果

换了大王之后,顺带就下拉联通手机营业厅App,刚开始用,出于好奇每天都会看看今天自己通过免流用了多少流量。
查看流量的入口长这样如下图


由于流量为0,不显示水波纹效果,下面是我实现的水波纹效果,这样的效果在很多App中都用到。


效果图

预告:

实现上面的效果将使用到以下知识点

  • 1 xfermode(用的比较少)
  • 2 贝塞尔曲线(挺常用的,Path中提供了相关Api)
  • 3 属性动画(太常用了,这里不多说了)

2.分步实现

2.1分析

2.1.1 定义属性

如果从自定义的view的角度来时实现,那么首先考虑的是这个view可改变的属性是什么,比如边框的颜色、水波峰高度值、移动的快慢等。在布局文件中通过配置这些属性,让view呈现不同的效果,如下图。

  <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"/>
        <!--动画移动时长(毫秒,建议大于1000,小于5000)-->
        <attr name="durationAnim" format="integer"/>
    </declare-styleable>

定义好了属性,这样用起来方便,不用修改代码,只通过改变xml中的属性值来就可以改变view的相关状态(让你同事去修改你的代码他会生气的),接下按自定义view的步骤应该要继承View,重写onMeasure方法、最后是onDraw方法(套路)。这个过程看似简单,其实考虑的细节很多(纸上得来终觉浅)。

那么就来吧!!!

2.1.2 自定义XfPathView

先写一个类继承view,直接上代码。

/**
* Created by Zhoudesen
* Created time 2018/1/29 16:20
* Description: Xfermode + Path之贝塞尔曲线 应用
* Version: V 1.0
*/
public class XfPathView extends View {
    /**
    * 圆形画笔
    */
    private Paint mPaintRound;
    /**
    * 水波纹画笔
    */
    private Paint mPaintWave;
    /**
    * 最小宽度
    */
    private final static int MIN_WIDTH = 300;
    /**
    * 最小高度
    */
    private final static int MIN_HEIGHT = 300;
    /**
    * 水波纹 背景颜色
    */
    private int mColorBgWave;
    /**
    * 水波纹 默认颜色
    */
    private final static int COLOR_BG_WAVE_DEFUALT = 0xaa00ff00;
    /**
    * 圆形boder颜色
    */
    private int mColorBgRoundBoder;
    /**
    * 圆形背景颜色
    */
    private int mColorBgRound;
    /**
    * 圆形 默认颜色 完全透明色
    */
    private final static int COLOR_BG_ROUND_DEFUALT = 0x00000000;
    /**
    * y 轴百分比
    */
    private float mPercentY = 0f;
    /**
    * 最大完成值
    */
    private float mPercentMax;
    /**
    * 1/2 波峰x轴长度(每个波峰/波谷均为贝塞尔曲线的控制点)
    */
    private float mRadius;
    /**
    * x 轴偏移量(让水波纹动起来)
    */
    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);
    }
    /**
    * 初始化参数
    *
    * @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();
        //画笔初始化
        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();
    }

以上代码初始化了画笔,颜色等相关基础工作,其中倒数第二行PorterDuffXfermode,才是主角,把主角放到最后登场(这里有坑)。

接下来开始对view进行测量,这个view高度和宽度是相等的,如果高度和宽度不相等,那么取小的值设置为它的高宽。视乎也很简单,就直接看下面代码吧。

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

到这里,已经完成了自定义相关属性,获取了属性值,并且完成初始化准备,还重写了onMeasure测量了高宽。接下来就是onDraw,在onDraw之前要分析先画什么,后画什么,还要计算相关的坐标,所以先分析吧。

先从水波纹的实现说起(尽管大家都知道,但还是要啰嗦一下 >温故而知新嘛)

2.1.3水波纹的实现

画一条线或多边形(或闭合曲线)总得有一个起点吧。

  • 【起点】
    Path.moveTo(x,y)
    有了点就可以画线了,就先看下面点曲线是怎么实现的

  • 【画曲线】(就是贝塞尔曲线)
    Path.quadTo(x1,y1,x2,y2);

quadTo(x1,y1,x2,y2)这个函数传入两个坐标,第一个是控制点,第二个是这段曲线的终点。

这个实际上就是二阶贝塞尔曲线(还有三阶、四阶、用的不多) 。

quadTo是核心方法,各种曲线效果都是由第一个控制点的变化而变化的。当然它是有一个公式的,但是Path Api中封装好了,只管用就好(你所关注的就是调方法,传坐标参数)。

  • 【链接到某一个点】(直线)
    Path.lineTo(x,y) ;

  • 【首尾相连】构成一个闭合的路径(直线)
    Path.close();

下图是一个带有水波纹的闭合图形,看看它是怎么实现的

画出水波过程图

解释一下上图吧

moveTo(点0) 起始点
quadTo(点1,点2);
quadTo(点3,点4);
quadTo(点5,点6);
quadTo(点7,点8);
没错,水波纹就是有一段段贝塞尔曲线拼接而来的。
lineTo(点9)
lineTo(点10)
close()将首尾相连(图中红色线部分)

通过Path的四个方法:moveTo、quadTo、lineTo和close。已经能满足画任意闭合的图形了,当然Path远不止这些,下回分解。

下图:实现了略带水波纹的闭合图形


显示了一半、通过移动、不断循环形成水波效果

2.1.4 onDraw

我们最终要实现的效果是这样的


最终的效果

可以拆分成两部分来画
1、在onDraw中可以先画一个外边圆的白色边框boder。
2、在画里面的动态水波纹。

水波纹的实现上述已经介绍过了,画一个圆边框视乎也毫不费劲,上代码。


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

        //--画圆boder
        drawBoder(canvas);

        //--画水波纹
        drawWave(canvas);
   }

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

drawWave(canvas)的代码也很简单,

private void drawWave(Canvas canvas){
        mPath.reset();
        //起点
        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轴上的偏移量,通过改变mDx,就可以让水波纹动起来。
mDy是施加给控制点的Y轴坐标的,其中mDy = - mDy (取反)是为了实现每段曲线上下波动效果的。

为了突出效果,画的是一个填充的圆
实际效果如下图


需要裁剪的部分

你发现了什么?
箭头所指的两块白色区域并不是我们想要的,随着水波纹的上升,圆的上部分也将会突出,这么解决这个问题,话不多说,让主角xfermode登场。

2.1.5 Xfermode

  • 它是干嘛用的?
    答:通过使用Xfermode将绘制的图形的像素和Canvas上对应位置的像素按照一定的规则进行混合,形成新的像素,再更新到Canvas中形成最终的图形

  • 它怎么用?
    答:通过Paint.setXfermode

  • 代码片段

image.png

关于这段代码片段有几点记录一下:

  • saveLayer 会产生一个新的图层,之后的画操作都是在这个图层上进行
  • restoreToCount(int layer),将指定的图层绘制到cavas.

更进一步
实际上Xfermode有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,前两个类在API 16被遗弃了,PorterDuffXfermode才是我们需要关注的。在初始化的时候用的就是这个子类。

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

PorterDuff.Mode,这个是什么?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它是一个枚举类型,而SRC_IN只是其中一个模式。

看一张图官方给的参考图


各种模式的混合结果
image.png

图中已经列出了两张图(Src和Dst)对应不同Mode所呈现的效果,参考这些模式对应的混合效果,实现的效果也太丰富了。是时候开始改写onDraw方法了。

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

        //--画圆boder
        drawBoder(canvas);

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

        //--画水波纹
        drawWave(canvas);

        canvas.restoreToCount(layerId);
    }

根据之前的分析混合绘制要在一张新的图层进行所以执行saveLayer方法,绘制完成后restoreToCount方法将混合后的图层绘制到canvas上。

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

 private void drawWave(Canvas canvas){
        //--画圆心
        mPaintWave.setColor(mColorBgRound);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, (mViewWidthHeight / 2) - 3, mPaintWave);

        //--画水波纹
        mPath.reset();
        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);
        //连接到左边底部
        mPath.lineTo(0 - mDx, mViewWidthHeight);
        //闭合
        mPath.close();

        //--圆与水波纹fix
        mPaintWave.setColor(mColorBgWave);
        mPaintWave.setXfermode(mPorterDuffXfermode);
        canvas.drawPath(mPath, mPaintWave);

        mPaintWave.setXfermode(null);
    }

画一个圆与水波纹进行混合,去除超出圆的部分。
混合前


image.png

Srcin这个模式符合我们的需求


image.png

看效果图


image.png

哇,水波纹底部已经实现了我们的效果,but,上部分是什么鬼(这是混合圆的颜色)。需求可是要透明的啊,好那么就把这个圆改成透明色把,我们已经在xml中定义了颜色属性,很容易改,然后运行得到如下结果。


image.png

圆是透明了,水波纹也不见了。看来是想的太简单了

因为透明度会影响混合效果,完全透明那么混合够也透明了

那么圆不能完全透明,如果这个需求不要背景完全透明,那么也就完事了,下图Srctop模式的效果


image.png

好吧,接着又找了很几种相像的模式试了一遍,就是不行。

机智时刻:既然圆不能完全透明,和水波纹混合后上部的底色又得透明,那么可以通过一个完全透明矩形和上部分底色混合,就可以消除了。如下图


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之贝塞尔曲线 应用
 * Version: V 1.0
 */

public class XfPathView extends View {
    /**
     * 圆形画笔
     */
    private Paint mPaintRound;
    /**
     * 水波纹画笔
     */
    private Paint mPaintWave;
    /**
     * 最小宽度
     */
    private final static int MIN_WIDTH = 300;
    /**
     * 最小高度
     */
    private final static int MIN_HEIGHT = 300;
    /**
     * 水波纹 背景颜色
     */
    private int mColorBgWave;
    /**
     * 水波纹 默认颜色
     */
    private final static int COLOR_BG_WAVE_DEFUALT = 0xaa00ff00;
    /**
     * 圆形boder颜色
     */
    private int mColorBgRoundBoder;
    /**
     * 圆形背景颜色
     */
    private int mColorBgRound;
    /**
     * 圆形 默认颜色 完全透明色
     */
    private final static int COLOR_BG_ROUND_DEFUALT = 0x00000000;
    /**
     * y 轴百分比
     */
    private float mPercentY = 0f;
    /**
     * 最大完成值
     */
    private float mPercentMax;
    /**
     * 1/2 波峰x轴长度(每个波峰/波谷均为贝塞尔曲线的控制点)
     */
    private float mRadius;
    /**
     * x 轴偏移量(让水波纹动起来)
     */
    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);
    }

    /**
     * 初始化参数
     *
     * @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();
        //画笔初始化
        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);

        //--画圆boder
        drawBoder(canvas);

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

        //--画水波纹
        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){
        //--画圆心
        mPaintWave.setColor(mColorBgRound);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, (mViewWidthHeight / 2) - 3, mPaintWave);

        //--画水波纹
        mPath.reset();
        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);
        //连接到左边底部
        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);
    }
    /**
     * 初始化并开始动画
     */
    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();
        }
    }

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

水平有限,疏漏之处请批评指正

写作不易、喜欢的给个星,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,451评论 25 707
  • 上一篇我们说了 Path 的基本操作,这一篇让我们来说一下 Path 的进阶用法——贝塞尔曲线。 那什么是贝塞尔曲...
    一团捞面阅读 5,692评论 5 36
  • 九月一号是女儿上学第二天,本以为做好饭再叫她起床,可她早早就起床了,她说妈妈:我要去上学,可不要迟到了。看她兴致这...
    孙佳婧妈妈阅读 139评论 0 0
  • 初读《逍遥游》,其词瑰丽,其想奇特,不禁感叹,庄子真牛×也。时隔多年,秋水又至,夜风微凉,与隐士熊逸再品逍遥,留在...
    张永胜_永往直前阅读 447评论 1 1
  • 最近在学习设计模式,本人现在工作与程序员无关,在一个普通的工厂工作,所以首先从简单工厂模式开始学习,毕竟本人就在工...
    静守岁月中阅读 221评论 0 1