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

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

仪表盘.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();
}

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

推荐阅读更多精彩内容