十分钟搞定酷炫动画,定制仪表盘

往常惯例,先上图, 不会的童鞋真的只需要十分钟就可以学会

仪表盘.gif

效果比较简单,就是绘制6段圆弧、然后指针的旋转、弧线型的下标文字,中心的数字随进度条变化。
这里我直接贴 onDraw 里面的方法了。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    drawInstrument(canvas);//进度底色
    drawText(canvas);//进度条下标文字
    drawPointer(canvas);//指针
    drawSecond(canvas);//第二层
    drawCalorie(canvas);//绘制中间的卡路里
}

接下来就一个一个方法看吧:

1、drawInstrument():

/*
* 绘制6段圆弧
* */
private void drawInstrument(Canvas canvas) {
    //70度一个弧形,中间隔5度
    canvas.drawArc(mBigRectF, offset[0], 70, false, mPaintBig);//绘制圆弧,不含圆心
    canvas.drawArc(mBigRectF, offset[1], 70, false, mPaintBig);//绘制圆弧,不含圆心
    canvas.drawArc(mBigRectF, offset[2], 70, false, mPaintBig);//绘制圆弧,不含圆心
    canvas.drawArc(mSmallRectF, offset[0], 70, false, mPaintSmall);//绘制圆弧,不含圆心
    canvas.drawArc(mSmallRectF, offset[1], 70, false, mPaintSmall);//绘制圆弧,不含圆心
    canvas.drawArc(mSmallRectF, offset[2], 70, false, mPaintSmall);//绘制圆弧,不含圆心
}

就是 canvas 类里面一个基本方法 canvas.drawArc()绘制圆弧,第一个参数是矩阵(控制圆弧的区域),第二个参数是开始时候得角度(UI给的),第三个参数70是圆弧的度数,第四个参数是是否绘制圆心,第五个参数是画笔。
其中画笔是在构造方法里面初始化,代码我就不在这贴了。矩阵是在 onMeasure 里面初始化,代码如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    mWidth = MeasureSpec.getSize(widthMeasureSpec);
    mHeight = MeasureSpec.getSize(heightMeasureSpec);
    int i = (mWidth - mHeight) / 2;
    mBigRectF = new RectF(10 + i, 10, mWidth - 10 - i, mHeight - 10);//外圆弧
    mTextRectF = new RectF(40 + i, 40, mWidth - 40 - i, mHeight - 40);//文字弧线
    mSmallRectF = new RectF(70 + i, 70, mWidth - 70 - i, mHeight - 70);//内圆弧
    mBigRectF.offset(0, 80);
    mSmallRectF.offset(0, 80);
    mTextRectF.offset(0, 80);

    mMatrix = new Matrix();
    mMatrix.postTranslate(mWidth / 2 - mGreenBitmap.getWidth() / 2, mHeight / 4);
}

在这里 初始化了三个 RectF,用户绘制弧相关,Matrix是用与指针旋转的,后面我会说明。
噢,对了,这里有个疑问,在 onMeasure 方法里面初始化的这三个变量,在 studio 里面报黄色警告,说避免在 draw/layout 期间初始化变量,但是要获取到控件的宽高之后再初始化,onDraw里面肯定更加不合适。有木有大牛知道的请告知,onSizeChanged?

2、drawText():

/**
 * 绘制圆弧下标 文字
 */
private void drawText(Canvas canvas) {
    mTextPaint.setTextSize(28);
    Path path = new Path();
    path.addArc(mTextRectF, offset[0], 20);
    canvas.drawTextOnPath("0", path, 10f, 10f, mTextPaint);
    path.reset();
    path.addArc(mTextRectF, offset[1] - 15, 20);
    canvas.drawTextOnPath(“1000”, path, 10f, 10f, mTextPaint);
    path.reset();
    path.addArc(mTextRectF, offset[2] - 15, 20);
    canvas.drawTextOnPath(“2000”, path, 10f, 10f, mTextPaint);
    path.reset();
    path.addArc(mTextRectF, offset[3] - 20, 20);
    canvas.drawTextOnPath(“3000”, path, 10f, 10f, mTextPaint);
}

由于这里的下标文字是弧线走势,所以这里用了 canvas 的另一个方法drawTextOnPath(),也是基本方法,我就不多赘述了。

3、drawPointer():

/*
* 绘制指针
* */
private void drawPointer(Canvas canvas) {
    mMatrix.reset();
    mMatrix.postTranslate(mWidth / 2 - mGreenBitmap.getWidth() / 2, mHeight / 4);
    if (progress > 666) {
        //红色
        mMatrix.postRotate(38, mWidth / 2f, mHeight / 2f + 80);
        mMatrix.postRotate(70 * (progress - 666) / 333f, mWidth / 2f, mHeight / 2f + 80);
        canvas.drawBitmap(mRedBitmap, mMatrix, null);
    } else if (progress > 333) {
        //黄色
        mMatrix.postRotate(-35, mWidth / 2f, mHeight / 2f + 80);
        mMatrix.postRotate(70 * (progress - 333) / 333f, mWidth / 2f, mHeight / 2f + 80);
        canvas.drawBitmap(mYellowBitmap, mMatrix, null);
    } else {
        //绿色
        mMatrix.postRotate(-108, mWidth / 2f, mHeight / 2f + 80);
        mMatrix.postRotate(70 * progress / 333f, mWidth / 2f, mHeight / 2f + 80);
        canvas.drawBitmap(mGreenBitmap, mMatrix, null);
    }
}

progress就是用来控制进

这里我们使用了三个不同颜色的 icon 作为指针,在不同的进度显示不同的颜色。然后由于指针不是纯色的,所以不能用 drawable.setTint()方法来控制指针的颜色,要不然用一个指针就行了。Matrix控制 bitmap的位置的偏移旋转,记得使用Matrix的时候要用Matrix.postXXX方法, 使用 setXXX 系列的方法会覆盖上次的设置。

Matrix:The Matrix class holds a 3x3 matrix for transforming coordinates. 这里给Matrix做个简单的说明,就是用来控制矩阵变化的,感兴趣的小伙伴请自行 google。

4、drawSecond():

/*
*
*动态进度条
**/
private void drawSecond(Canvas canvas) {
    //不能修改 mPaint 画笔的颜色复用 mPaint,初步估计 canvas.drawArc 方法为异步
    if (progress > 666) {
        //红色
        canvas.drawArc(mBigRectF, offset[2], 70 * (progress - 666) * 3 / 1000, false, mPaintRed);
    } else if (progress > 333) {
        //黄色
        canvas.drawArc(mBigRectF, offset[1], 70 * (progress - 333) * 3 / 1000, false, mPaintYellow);
    } else {
        //绿色
        canvas.drawArc(mBigRectF, offset[0], 70 * progress * 3 / 1000, false, mPaintGreen);
    }
}

这里踩了一个坑,一开始我的想法是所有的圆弧都用同一支画笔 mPaint,因为画笔的参数都是一样的,就只有颜色不同,在需要绘制不同颜色的时候调用 mPaint.setColor()方法即可。比如说我先用 mPaint 画笔画了一段灰色的圆弧,然后调用 mPaint.setColor(Color.BLUE)方法将画笔设置为蓝色,然后再绘制一段蓝色弧线,但是最终的结果是两段圆弧的颜色都是蓝色的。由于 canvas.drawArc()方法是native方法,所以我没有找到原因,初步估计 canvas.drawArc()方法是异步原因引起的,有木有大牛知道原因~~~

5、drawCalorie():

/*
*
* 圆弧中间文字
* */
private void drawCalorie(Canvas canvas) {
    mTextPaint.setTextSize(56);
    String num = String.valueOf((int) (mFullEat * progress / 1000f));

    int numberWidth = (int) mTextPaint.measureText(num);
    mTextPaint.setTextSize(28);
    int textWidth = (int) mTextPaint.measureText("千卡");

    mTextPaint.setTextSize(56);
    canvas.drawText(num, (mWidth - numberWidth - textWidth) / 2, mHeight / 2 + 80 - 20, mTextPaint);
    mTextPaint.setTextSize(28);
    canvas.drawText("千卡", (mWidth - numberWidth - textWidth) / 2 + numberWidth + 5, mHeight / 2 + 80 - 20, mTextPaint);

    String state = progress > 333 ? (progress > 666 ? "吃多了" : "正合适") : "还需吃";
    mTextPaint.setTextSize(32);
    canvas.drawText(state, (mWidth - mTextPaint.measureText(state)) / 2, mHeight / 2 + 80 + 30, mTextPaint);
}

这里没什么好说的,代码简洁明了。就是计算了几个宽高,让文字居中而已。


好,控件画完了。

接下来,只需要添加一个方法,修改progress的值,然后调用invalidate()重新走 ondraw()方法即可。

private void setProgress(int progress) {
    this.progress = progress;
    invalidate();
}

嗯~然后还要动起来,所以我把这个方法的权限设置为 pravite。

public void setData(int hasEat, int fullEat, boolean needAnimator) {
    progress = hasEat * 1000 / fullEat;
    mFullEat = fullEat;
    if (progress > 1000)
        progress = 1000;
    if (progress < 0)
        progress = 0;
    if (needAnimator) {
        ObjectAnimator objectAnimator = ObjectAnimator.ofInt(this, "progress", progress);
        objectAnimator.setDuration(2000);
        objectAnimator.setInterpolator(new DecelerateInterpolator());
        objectAnimator.start();
    } else {
        setProgress(progress);
    }
}

就酱紫,很简单的代码逻辑。哦,对了,ObjectAnimator动画用到了反射,代码混淆的时候记得keep 这个类不被混淆哦。

~~Over

对了,代码比较简单,我就不传 github 的,直接贴代码吧,两百行不到

/**
* Author:    Diamond_Lin
* Version    V1.0
* Date:      2017/6/26 上午10:06
* Description:
* Modification  History:
* Date          Author              Version         Description
* -----------------------------------------------------------------------------------
* 2017/6/26      Diamond_Lin            1.0                    1.0
* Why & What is modified:
*/

public class InstrumentView extends View {

private Paint mPaintBig, mPaintSmall, mPaintYellow, mPaintGreen, mPaintRed;
private int mWidth;
private int mHeight;
private RectF mBigRectF, mSmallRectF, mTextRectF;
private TextPaint mTextPaint;
private int[] offset = {-198, -125, -52, 18};//分别是第一段起点,第二段起点,第三段起点,第三段终点
private Bitmap mGreenBitmap;
private Bitmap mYellowBitmap;
private Bitmap mRedBitmap;

private int progress = 0;
private int mFullEat;
private Matrix mMatrix;

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

public InstrumentView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public InstrumentView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init() {
    mPaintBig = new Paint();//外环画笔
    mPaintBig.setAntiAlias(true);//使用抗锯齿功能
    mPaintBig.setColor(Color.argb(128, 0, 0, 0));    //设置画笔的颜色
    mPaintBig.setStyle(Paint.Style.STROKE);//设置画笔类型为描边
    mPaintBig.setStrokeWidth(16);

    mPaintYellow = new Paint();//黄色
    mPaintYellow.setAntiAlias(true);//使用抗锯齿功能
    mPaintYellow.setColor(Color.rgb(0xf7, 0xb5, 0x00));    //设置画笔的颜色
    mPaintYellow.setStyle(Paint.Style.STROKE);//设置画笔类型为描边
    mPaintYellow.setStrokeWidth(16);

    mPaintGreen = new Paint();//绿色
    mPaintGreen.setAntiAlias(true);//使用抗锯齿功能
    mPaintGreen.setColor(Color.rgb(0x5c, 0xd1, 0xb4));    //设置画笔的颜色
    mPaintGreen.setStyle(Paint.Style.STROKE);//设置画笔类型为描边
    mPaintGreen.setStrokeWidth(16);

    mPaintRed = new Paint();//红色
    mPaintRed.setAntiAlias(true);//使用抗锯齿功能
    mPaintRed.setColor(Color.rgb(0xff, 0x64, 0x6f));    //设置画笔的颜色
    mPaintRed.setStyle(Paint.Style.STROKE);//设置画笔类型为描边
    mPaintRed.setStrokeWidth(16);

    mPaintSmall = new Paint();//内环画笔
    mPaintSmall.setAntiAlias(true);//使用抗锯齿功能
    mPaintSmall.setColor(Color.argb(128, 0, 0, 0));    //设置画笔的颜色
    mPaintSmall.setStyle(Paint.Style.STROKE);//设置画笔类型为描边
    mPaintSmall.setStrokeWidth(8);

    mTextPaint = new TextPaint();
    mTextPaint.setAntiAlias(true);
    mTextPaint.setColor(Color.WHITE);
    mTextPaint.setTextSize(28);

    mGreenBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_pointer_green);//绿色指针
    mYellowBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_pointer_yellow);//黄色指针
    mRedBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_pointer_red);//红色指针

}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    mWidth = MeasureSpec.getSize(widthMeasureSpec);
    mHeight = MeasureSpec.getSize(heightMeasureSpec);
    int i = (mWidth - mHeight) / 2;
    mBigRectF = new RectF(10 + i, 10, mWidth - 10 - i, mHeight - 10);
    mTextRectF = new RectF(40 + i, 40, mWidth - 40 - i, mHeight - 40);
    mSmallRectF = new RectF(70 + i, 70, mWidth - 70 - i, mHeight - 70);
    mBigRectF.offset(0, 80);
    mSmallRectF.offset(0, 80);
    mTextRectF.offset(0, 80);
    mMatrix = new Matrix();
    mMatrix.postTranslate(mWidth / 2 - mGreenBitmap.getWidth() / 2, mHeight / 4);
}


@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Log.e("__-progress", progress + "");
    drawInstrument(canvas);//进度底色
    drawText(canvas);//文字
    drawPointer(canvas);//指针
    drawSecond(canvas);//第二层
    drawCalorie(canvas);//绘制中间的卡路里
}

/*
*
* 圆弧中间文字
* */
private void drawCalorie(Canvas canvas) {
    mTextPaint.setTextSize(56);
    String num = String.valueOf((int) (mFullEat * progress / 1000f));

    int numberWidth = (int) mTextPaint.measureText(num);
    mTextPaint.setTextSize(28);
    int textWidth = (int) mTextPaint.measureText("千卡");

    mTextPaint.setTextSize(56);
    canvas.drawText(num, (mWidth - numberWidth - textWidth) / 2, mHeight / 2 + 80 - 20, mTextPaint);
    mTextPaint.setTextSize(28);
    canvas.drawText("千卡", (mWidth - numberWidth - textWidth) / 2 + numberWidth + 5, mHeight / 2 + 80 - 20, mTextPaint);

    String state = progress > 333 ? (progress > 666 ? "吃多了" : "正合适") : "还需吃";
    mTextPaint.setTextSize(32);
    canvas.drawText(state, (mWidth - mTextPaint.measureText(state)) / 2, mHeight / 2 + 80 + 30, mTextPaint);
}

/*
*
*动态进度条
**/
private void drawSecond(Canvas canvas) {
    //不能修改 mPaint 画笔的颜色复用 mPaint,初步估计 canvas.drawArc 方法为异步
    if (progress > 666) {
        //红色
        canvas.drawArc(mBigRectF, offset[2], 70 * (progress - 666) * 3 / 1000, false, mPaintRed);
    } else if (progress > 333) {
        //黄色
        canvas.drawArc(mBigRectF, offset[1], 70 * (progress - 333) * 3 / 1000, false, mPaintYellow);
    } else {
        //绿色
        canvas.drawArc(mBigRectF, offset[0], 70 * progress * 3 / 1000, false, mPaintGreen);
    }
}

/*
* 绘制指针
* */
private void drawPointer(Canvas canvas) {
    mMatrix.reset();
    mMatrix.postTranslate(mWidth / 2 - mGreenBitmap.getWidth() / 2, mHeight / 4);
    if (progress > 666) {
        //红色
        mMatrix.postRotate(38, mWidth / 2f, mHeight / 2f + 80);
        mMatrix.postRotate(70 * (progress - 666) / 333f, mWidth / 2f, mHeight / 2f + 80);
        canvas.drawBitmap(mRedBitmap, mMatrix, null);
    } else if (progress > 333) {
        //黄色
        mMatrix.postRotate(-35, mWidth / 2f, mHeight / 2f + 80);
        mMatrix.postRotate(70 * (progress - 333) / 333f, mWidth / 2f, mHeight / 2f + 80);
        canvas.drawBitmap(mYellowBitmap, mMatrix, null);
    } else {
        //绿色
        mMatrix.postRotate(-108, mWidth / 2f, mHeight / 2f + 80);
        mMatrix.postRotate(70 * progress / 333f, mWidth / 2f, mHeight / 2f + 80);
        canvas.drawBitmap(mGreenBitmap, mMatrix, null);
    }
}

/**
 * 绘制圆弧标记 文字
 */
private void drawText(Canvas canvas) {
    mTextPaint.setTextSize(28);
    Path path = new Path();
    path.addArc(mTextRectF, offset[0], 20);
    canvas.drawTextOnPath("0", path, 10f, 10f, mTextPaint);
    path.reset();
    path.addArc(mTextRectF, offset[1] - 15, 20);
    canvas.drawTextOnPath(String.valueOf(mFullEat / 3), path, 10f, 10f, mTextPaint);
    path.reset();
    path.addArc(mTextRectF, offset[2] - 15, 20);
    canvas.drawTextOnPath(String.valueOf(mFullEat / 3 * 2), path, 10f, 10f, mTextPaint);
    path.reset();
    path.addArc(mTextRectF, offset[3] - 20, 20);
    canvas.drawTextOnPath(String.valueOf(mFullEat), path, 10f, 10f, mTextPaint);
}

/*
*
* 绘制6段圆弧
* */
private void drawInstrument(Canvas canvas) {
    //70度一个弧形,中间隔5度
    canvas.drawArc(mBigRectF, offset[0], 70, false, mPaintBig);//绘制圆弧,不含圆心
    canvas.drawArc(mBigRectF, offset[1], 70, false, mPaintBig);//绘制圆弧,不含圆心
    canvas.drawArc(mBigRectF, offset[2], 70, false, mPaintBig);//绘制圆弧,不含圆心
    canvas.drawArc(mSmallRectF, offset[0], 70, false, mPaintSmall);//绘制圆弧,不含圆心
    canvas.drawArc(mSmallRectF, offset[1], 70, false, mPaintSmall);//绘制圆弧,不含圆心
    canvas.drawArc(mSmallRectF, offset[2], 70, false, mPaintSmall);//绘制圆弧,不含圆心
}

public void setData(int hasEat, int fullEat, boolean needAnimator) {
    progress = hasEat * 1000 / fullEat;
    mFullEat = fullEat;
    if (progress > 1000)
        progress = 1000;
    if (progress < 0)
        progress = 0;
    if (needAnimator) {
        ObjectAnimator objectAnimator = ObjectAnimator.ofInt(this, "progress", progress);
        objectAnimator.setDuration(2000);
        objectAnimator.setInterpolator(new DecelerateInterpolator());
        objectAnimator.start();
    } else {
        setProgress(progress);
    }
}

private void setProgress(int progress) {
    this.progress = progress;
    invalidate();
}

}

你可能感兴趣的:(十分钟搞定酷炫动画,定制仪表盘)