手把手带你画一个 时尚仪表盘 Android 自定义View

拿到美工效果图,咱们程序员就得画得一模一样。 为了不被老板喷,只能多练啊。

听说你觉得前面几篇都so easy,那今天就带你做个相对比较复杂的。

今天的效果图如下(左边是ui图 右边是实现图):

\

自我感觉总体效果还不错,至少大概画得一样了。上一个动态图:

\

其实这个效果实现起来也不是很难,就是计算坐标,弧度之类的可能会比较麻烦,这里分享写这个其中一张手稿,请无视掉很丑的字,其实做自定义view 还是要在纸上多画。所以希望大家也能这么画画,思路会很顺。

\

好的了,废话不多说,快开始。

首先自定义属性 构造函数,测量什么的 你肯定已经很熟练 直接贴代码了,注释写的很清楚

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class PanelView extends View {
     private int mWidth;
     private int mHeight;
 
     private int mPercent;
 
     //刻度宽度
     private float mTikeWidth;
 
     //第二个弧的宽度
     private int mScendArcWidth;
 
     //最小圆的半径
     private int mMinCircleRadius;
 
     //文字矩形的宽
     private int mRectWidth;
 
     //文字矩形的高
     private int mRectHeight;
 
 
     //文字内容
     private String mText = "" ;
 
     //文字的大小
     private int mTextSize;
 
     //设置文字颜色
     private int mTextColor;
     private int mArcColor;
 
     //小圆和指针颜色
     private int mMinCircleColor;
 
     //刻度的个数
     private int mTikeCount;
 
     private Context mContext;
 
     public PanelView(Context context) {
         this (context, null );
     }
 
     public PanelView(Context context, AttributeSet attrs) {
         this (context, attrs, 0 );
     }
 
     public PanelView(Context context, AttributeSet attrs, int defStyleAttr) {
         super (context, attrs, defStyleAttr);
         mContext = context;
         TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.PanelView,defStyleAttr, 0 );
         mArcColor = a.getColor(R.styleable.PanelView_arcColor, Color.parseColor( "#5FB1ED" ));
         mMinCircleColor = a.getColor(R.styleable.PanelView_pointerColor,Color.parseColor( "#C9DEEE" ));
         mTikeCount = a.getInt(R.styleable.PanelView_tikeCount, 12 );
         mTextSize = a.getDimensionPixelSize(PxUtils.spToPx(R.styleable.PanelView_android_textSize,mContext), 24 );
         mText = a.getString(R.styleable.PanelView_android_text);
         mScendArcWidth = 50 ;
     }
 
 
     @Override
     protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
         if (widthMode == MeasureSpec.EXACTLY) {
             mWidth = widthSize;
         } else {
             mWidth = PxUtils.dpToPx( 200 ,mContext);
         }
 
 
         if (heightMode == MeasureSpec.EXACTLY) {
             mHeight = heightSize;
         } else {
             mHeight = PxUtils.dpToPx( 200 ,mContext);
         }
         Log.e( "wing" ,mWidth+ "" );
         setMeasuredDimension(mWidth, mHeight);
     }
自定义属性attr.xml
?
1
2
3
4
5
6
7
8
9
10
11
<!--{cke_protected}{C}%3C!%2D%2D%3Fxml%20version%3D% 221.0 % 22 %20encoding%3D%22utf- 8 % 22 %3F%2D%2D%3E-->
<resources>
     <declare-styleable name= "PanelView" >
         <attr name= "arcColor" format= "color" >
         <attr name= "arcWidth" format= "dimension" >
         <attr name= "android:text" >
         <attr name= "tikeCount" format= "integer" >
         <attr name= "pointerColor" format= "color" >
         <attr name= "android:textSize" >
     </attr></attr></attr></attr></attr></attr></declare-styleable>
</resources>

之后来重头戏,也就是绘制。就像画画一样,再复杂的view也是一笔一笔画出来的。所以我们把这个view分解。

大概分解成如下:1.最外面的弧 2.里面的粗弧 3.中间小圆 4.最小的圆 5.刻度 6.指针 7.矩形 8.文字

相信让你分开画一定难不倒你。那组合在一起 就是这个view啦。下面开始我们的ondraw()

按照这个分解来:

1.绘制最外面的弧 这里需要注意的一点是,如果想让这个圆在view里 记得减去画笔宽度的一半 因为半径是从圆心到画笔宽度的中间算的,所以这里画弧的矩形是 new RectF(strokeWidth, strokeWidth, mWidth - strokeWidth, mHeight - strokeWidth)

?
1
2
3
4
5
6
7
8
Paint p = new Paint();
    int strokeWidth = 3 ;
    p.setStrokeWidth(strokeWidth);
    p.setAntiAlias( true );
    p.setStyle(Paint.Style.STROKE);
    p.setColor(mArcColor);
    //最外面线条
    canvas.drawArc( new RectF(strokeWidth, strokeWidth, mWidth - strokeWidth, mHeight - strokeWidth), 145 , 250 , false , p);

画出来是这样的效果。

\

2.绘制里面的粗弧,这里比较麻烦的就是需要分为四段,看图:

\

因为大圆和里面粗弧的长短不一致,这里使用百分比来计算 所以会造成指针偏差,那么这里把 1、2两个部分固定来画,然后是3 充满的部分,用百分比来计算需要画多少度,最后是4 空白的部分。

首先把粗弧的矩形画出来,这里固定了比大弧半径少50(这里其实可以改进,你可以改成动态的让他更灵活),然后计算出百分比。

?
1
2
3
4
RectF secondRectF = new RectF(strokeWidth + 50 , strokeWidth + 50 , mWidth - strokeWidth - 50 , mHeight - strokeWidth - 50 );
         float secondRectWidth = mWidth - strokeWidth - 50 - (strokeWidth + 50 );
         float secondRectHeight = mHeight - strokeWidth - 50 - (strokeWidth + 50 );
         float percent = mPercent / 100f;

接下来绘制1弧,先算出fill充满部分的度数,因为是突出的,所以如果百分比为0,突出左端为白色 如果不为零,则和充满颜色统一。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
         //充满的圆弧的度数    -5是大小弧的偏差
         float fill = 250 * percent ;
 
         //空的圆弧的度数
         float empty = 250 - fill;
//        Log.e("wing", fill + "");
 
         if (percent== 0 ){
             p.setColor(Color.WHITE);
         }
         //画粗弧突出部分左端
 
         canvas.drawArc(secondRectF, 135 , 11 , false ,p);

然后绘制2弧 也就是fill充满的弧,
?
1
canvas.drawArc(secondRectF, 145 , fill, false , p);

接下来是3弧,也就是empty未充满的弧,是白色的
?
1
2
3
p.setColor(Color.WHITE);
        //画弧胡的未充满部分
        canvas.drawArc(secondRectF, 145 + fill, empty, false , p);

最后,画出右边突出的4弧, 如果百分比为100 那么和充满的颜色一致,否则为白色
?
1
2
3
4
5
//画粗弧突出部分右端
        if (percent == 1 ){
            p.setColor(mArcColor);
        }
        canvas.drawArc(secondRectF, 144 +fill+empty, 10 , false ,p);

这样粗弧也就画完了 来看看效果,就画了两条弧线(实际是5条),就成型了。

\

3.中间的小圆外圈,他的圆心不用多说 是整个view的中心

?
1
2
3
4
5
6
p.setColor(mArcColor);
 
 
//绘制小圆外圈
p.setStrokeWidth( 3 );
canvas.drawCircle(mWidth / 2 , mHeight / 2 , 30 , p);

4.绘制内圆,圆心一样的,半径和画笔粗度改变一下
?
1
2
3
4
5
6
//绘制小圆内圈
 
p.setColor(mMinCircleColor);
p.setStrokeWidth( 8 );
mMinCircleRadius = 15 ;
canvas.drawCircle(mWidth / 2 , mHeight / 2 , mMinCircleRadius, p);

\

5.刻度 刻度处理起来可能比较麻烦,用三角函数算坐标啊 循环画出来。。 这里提供一种比较简单的方法:旋转画布。

首先引入一个概念,什么叫旋转画布呢,就是把你的画布旋转。。经过测试,旋转以后,整个坐标轴都会对应旋转,一张图举例说明下。

\

大概就是这个意思,画布旋转之后 坐标系也就旋转了,但是原来的图像还在,所以说你比如这个点 x,y旋转前在这个位置, 那么旋转后就是另外一个位置了,但是他们的坐标是相同的。 所以刻度也可以考这种方法画。我们只要画出最顶端的刻度 然后旋转就可以了。

\

绘制第一段刻度, 然后总共是250的弧度 计算出每个刻度的度数 用250除以刻度数mTikeCount,就是每次旋转的度数。接下来把画布逐步旋转,按照原坐标绘制,即可绘制出右半部分刻度。 注意:为了让之后的绘制正常,务必把画布转回原来的位置

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//绘制刻度!
 
p.setColor(mArcColor);
//绘制第一条最上面的刻度
mTikeWidth = 20 ;
p.setStrokeWidth( 3 );
 
canvas.drawLine(mWidth / 2 , 0 , mWidth / 2 , mTikeWidth, p);
//旋转的角度
float rAngle = 250f / mTikeCount;
//通过旋转画布 绘制右面的刻度
for ( int i = 0 ; i < mTikeCount / 2 ; i++) {
     canvas.rotate(rAngle, mWidth / 2 , mHeight / 2 );
     canvas.drawLine(mWidth / 2 , 0 , mWidth / 2 , mTikeWidth, p);
}
 
//现在需要将将画布旋转回来
canvas.rotate(-rAngle * mTikeCount / 2 , mWidth / 2 , mHeight / 2 );

\

左半部分同理,需要改变的度数为负 就好了

?
1
2
3
4
5
6
7
8
9
//通过旋转画布 绘制左面的刻度
for ( int i = 0 ; i < mTikeCount / 2 ; i++) {
     canvas.rotate(-rAngle, mWidth / 2 , mHeight / 2 );
     canvas.drawLine(mWidth / 2 , 0 , mWidth / 2 , mTikeWidth, p);
}
 
 
//现在需要将将画布旋转回来
canvas.rotate(rAngle * mTikeCount / 2 , mWidth / 2 , mHeight / 2 );
\

6.指针 指针的绘制和刻度相似,先算出来百分比所占的度数 然后根据 是否大于50%来旋转画布。

指针的起终点是 总view高度的一半 粗弧矩形的一半 加上小圆,前面坐标讲解了那么,这个也一样,自己拿起笔算一算。

注意这里画布旋转我通过计算得出一个公式 250 * percent - 250/2。

如果小于50% 则为负 如果大于50%则为正,然后进行旋转。

切忌最后一定要将画布转回来。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//绘制指针
 
p.setColor(mMinCircleColor);
p.setStrokeWidth( 4 );
 
 
 
//按照百分比绘制刻度
canvas.rotate(( 250 * percent - 250 / 2 ), mWidth / 2 , mHeight / 2 );
 
canvas.drawLine(mWidth / 2 , (mHeight / 2 - secondRectHeight / 2 ) + mScendArcWidth / 2 + 2 , mWidth / 2 , mHeight / 2 - mMinCircleRadius, p);
 
//将画布旋转回来
canvas.rotate(-( 250 * percent - 250 / 2 ), mWidth / 2 , mHeight / 2 );
\

接下来就是画矩形和文字。没什么好说的了,坐标也是X周围mWidth/2 y轴自己根据圆心微调一个距离

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//绘制矩形
     p.setStyle(Paint.Style.FILL);
     p.setColor(mArcColor);
     mRectWidth = 60 ;
     mRectHeight = 25 ;
 
     //文字矩形的最底部坐标
     float rectBottomY = mHeight/ 2 + secondRectHeight/ 3 +mRectHeight;
     canvas.drawRect(mWidth/ 2 -mRectWidth/ 2 ,mHeight/ 2 + secondRectHeight/ 3 ,mWidth/ 2 +mRectWidth/ 2 ,rectBottomY,p);
 
 
     p.setTextSize(mTextSize);
     mTextColor = Color.WHITE;
     p.setColor(mTextColor);
     float txtLength = p.measureText(mText);
     canvas.drawText(mText,(mWidth-txtLength)/ 2 ,rectBottomY + 40 ,p);
 
     super .onDraw(canvas);

这样完成了整个view的绘制。

\
下面要做的就是为了方便使用者,提供一些设置属性的方法。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
     * 设置百分比
     * @param percent
     */
    public void setPercent( int percent) {
        mPercent = percent;
        invalidate();
    }
 
    /**
     * 设置文字
     * @param text
     */
    public void setText(String text){
        mText = text;
        invalidate();
    }
 
    /**
     * 设置圆弧颜色
     * @param color
     */
 
    public void setArcColor( int color){
        mArcColor = color;
 
        invalidate();
    }
 
 
    /**
     * 设置指针颜色
     * @param color
     */
    public void setPointerColor( int color){
        mMinCircleColor = color;
 
        invalidate();
    }
 
    /**
     * 设置文字大小
     * @param size
     */
    public void setTextSize( int size){
        mTextSize = size;
 
        invalidate();
    }
 
    /**
     * 设置粗弧的宽度
     * @param width
     */
    public void setArcWidth( int width){
        mScendArcWidth = width;
 
        invalidate();
    }

大功告成!!!一个看似复杂的view 经过我们一步一步绘制遍完成了。

其实技术的养成也是这样,只要一步一步脚踏实地的去练习,我相信总有一天我能成为大神,并且找到心仪的工作。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值