Android自定义View实现圆弧进度效果

前言:Android开发中,自定义View实现自己想要的效果已成为一项必备的技能,当然自定义View也是Android开发中比较难的部分,涉及到的知识有Canvas(画布),Paint(画笔)等,自定义控件分为三种:一是直接继承自View,完全的自定义;二是在原有控件的基础上进行改造,达到自己想要的效果;还有一种就是自定义组合控件,将已有的控件根据自己的需要进行组合实现的效果。本人对自定义View也是一知半解,简单记录下自己学习自定义View(继承自View)的过程,方便日后翻阅。

技术实现

1.ArcView继承自View

2.Canvas(画布)

3.Paint(画笔)

效果图:类似于QQ的计步效果

效果图

1.继承自View

(1)重写3个构造方法(新的API中的构造方法是4个)

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

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

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

(2)重写View的OnDraw方法

@SuppressLint("DrawAllocation")
@Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    centerX=getWidth()/2;
    centerY=getHeight()/2;
    //初始化paint
    initPaint();
    //绘制弧度
    drawArc(canvas);
    //绘制文本
    drawText(canvas); 
}

注:这里的paint初始化我放在了onDraw方法中进行的,当然你也可以放在有三个参数的构造方法中初始化。

2.Paint初始化

(1)圆弧的画笔mArcPaint

//圆弧的paint 
mArcPaint=new Paint(Paint.*ANTI_ALIAS_FLAG*); 
//抗锯齿 
mArcPaint.setAntiAlias(true); 
mArcPaint.setColor(Color.parseColor("#666666")); 
//设置透明度(数值为0-255) 
mArcPaint.setAlpha(100); 
//设置画笔的画出的形状 
mArcPaint.setStrokeJoin(Paint.Join.*ROUND*); 
mArcPaint.setStrokeCap(Paint.Cap.*ROUND*); 
//设置画笔类型 
mArcPaint.setStyle(Paint.Style.*STROKE*); 
mArcPaint.setStrokeWidth(dp2px(mStrokeWith));

(2)文字的画笔mTextPaint

//中心文字的paint 
mTextPaint=new Paint(); 
mTextPaint.setAntiAlias(true); 
mTextPaint.setColor(Color.parseColor("#FF4A40")); 
//设置文本的对齐方式 
mTextPaint.setTextAlign(Paint.Align.*CENTER*); 
//mTextPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.dp_12)); 
mTextPaint.setTextSize(dp2px(25));

3.Canvas绘制

(1)圆弧的绘制

/**
*绘制圆弧
*@param canvas
*/
 private void drawArc(Canvas canvas) {
    //绘制圆弧背景
   RectF mRectF=new RectF(mStrokeWith+dp2px(5),mStrokeWith+dp2px(5),getWidth()-mStrokeWith-dp2px(5),getHeight()-mStrokeWith);
   canvas.drawArc(mRectF,startAngle,mAngle,false,mArcPaint);
   //绘制当前数值对应的圆弧
   mArcPaint.setColor(Color.parseColor("#FF4A40"));
   //根据当前数据绘制对应的圆弧
   canvas.drawArc(mRectF,startAngle,mIncludedAngle,false,mArcPaint); 
}

(2)文本的绘制

/**
* 绘制文本
* @param canvas
*/
private void drawText(Canvas canvas) {
    Rect mRect=new Rect();
   String mValue=String.valueOf(mAnimatorValue);
   //绘制中心的数值
   mTextPaint.getTextBounds(mValue,0,mValue.length(),mRect);
   canvas.drawText(String.valueOf(mAnimatorValue),centerX,centerY+mRect.height(),mTextPaint);    
   //绘制中心文字描述
   mTextPaint.setColor(Color.parseColor("#999999"));
   mTextPaint.setTextSize(dp2px(12));
   mTextPaint.getTextBounds(mDes,0,mDes.length(),mRect);
   canvas.drawText(mDes,centerX,centerY+2*mRect.height()+dp2px(10),mTextPaint);    //绘制最小值
   String minValue=String.valueOf(mMinValue);
   String maxValue=String.valueOf(mMaxValue);
   mTextPaint.setTextSize(dp2px(18));
   mTextPaint.getTextBounds(minValue,0,minValue.length(),mRect);
   canvas.drawText(minValue, (float) (centerX-0.6*centerX-dp2px(5)), (float) (centerY+0.75*centerY+mRect.height()+dp2px(5)),mTextPaint);
   //绘制最大值
   mTextPaint.getTextBounds(maxValue,0,maxValue.length(),mRect);
   canvas.drawText(maxValue, (float) (centerX+0.6*centerX+dp2px(5)), (float) (centerY+0.75*centerY+mRect.height()+dp2px(5)),mTextPaint); 
}

4.添加动画效果及数据

(1)动画效果

/** 
*  为绘制弧度及数据设置动画
*  @param startAngle 开始的弧度
*  @param currentAngle 需要绘制的弧度
*  @param currentValue 需要绘制的数据
*  @param time 动画执行的时长
*/
private void setAnimation(float startAngle, float currentAngle,int currentValue, int time) {
    //绘制当前数据对应的圆弧的动画效果
    ValueAnimator progressAnimator = ValueAnimator.ofFloat(startAngle, currentAngle);
    progressAnimator.setDuration(time);
    progressAnimator.setTarget(mIncludedAngle);
    progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mIncludedAngle = (float) animation.getAnimatedValue();
            //重新绘制,不然不会出现效果
            postInvalidate();
        }
    });
    //开始执行动画
    progressAnimator.start();    //中心数据的动画效果
    ValueAnimator valueAnimator = ValueAnimator.ofInt(mAnimatorValue, currentValue);
    valueAnimator.setDuration(2500);
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {

            mAnimatorValue = (int) valueAnimator.getAnimatedValue();
            postInvalidate();
        }
    });
    valueAnimator.start(); 
}

(2)数据添加

/** 
* 设置数据
* @param minValue 最小值
* @param maxValue 最大值
* @param currentValue 当前绘制的值
* @param des 描述信息
*/
public void setValues(int minValue,int maxValue, int currentValue,String des) {
    mDes=des;
    mMaxValue=maxValue;
    mMinValue=minValue;
    //完全覆盖背景弧度
    if (currentValue > maxValue) {
        currentValue = maxValue;
    }
    //计算弧度比重
    float scale = (float) currentValue / maxValue;
    //计算弧度
    float currentAngle = scale * mAngle;
    //开始执行动画
    setAnimation(0, currentAngle, currentValue,2500);
}

完整代码:

/** 
*Created by ruancw on 2018/6/13.
*自定义的圆弧形view
*/
public class ArcView extends View {

    //根据数据显示的圆弧Paint
    private Paint mArcPaint;
    //文字描述的paint
    private Paint mTextPaint;
    //圆弧开始的角度
    private float startAngle=135;
    //圆弧结束的角度
    private float endAngle=45;
    //圆弧背景的开始和结束间的夹角大小
    private float mAngle=270;
    //当前进度夹角大小
    private float mIncludedAngle=0;
    //圆弧的画笔的宽度
    private float mStrokeWith=10;
    //中心的文字描述
    private String mDes="";
    //动画效果的数据及最大/小值
    private int mAnimatorValue,mMinValue,mMaxValue;
    //中心点的XY坐标
    private float centerX,centerY;   
    public ArcView(Context context) {
        this(context,null);
    }

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

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

    private void initPaint() {
        //圆弧的paint
        mArcPaint=new Paint(Paint.*ANTI_ALIAS_FLAG*);
        //抗锯齿
        mArcPaint.setAntiAlias(true);
        mArcPaint.setColor(Color.parseColor("#666666"));
        //设置透明度(数值为0-255)
        mArcPaint.setAlpha(100);
        //设置画笔的画出的形状
        mArcPaint.setStrokeJoin(Paint.Join.*ROUND*);
        mArcPaint.setStrokeCap(Paint.Cap.*ROUND*);
        //设置画笔类型
        mArcPaint.setStyle(Paint.Style.*STROKE*);
        mArcPaint.setStrokeWidth(dp2px(mStrokeWith));    
        //中心文字的paint
        mTextPaint=new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(Color.parseColor("#FF4A40"));
        //设置文本的对齐方式
        mTextPaint.setTextAlign(Paint.Align.*CENTER*);
        //mTextPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.dp_12));
        mTextPaint.setTextSize(dp2px(25));    
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        centerX=getWidth()/2;
        centerY=getHeight()/2;
        //初始化paint
        initPaint();
        //绘制弧度
        drawArc(canvas);
        //绘制文本
        drawText(canvas);
    }

    /**
    * 绘制文本
    * @param canvas
    */
    private void drawText(Canvas canvas) {
        Rect mRect=new Rect();
        String mValue=String.valueOf(mAnimatorValue);
        //绘制中心的数值
        mTextPaint.getTextBounds(mValue,0,mValue.length(),mRect);
        canvas.drawText(String.valueOf(mAnimatorValue),centerX,centerY+mRect.height(),mTextPaint);    
        //绘制中心文字描述
        mTextPaint.setColor(Color.parseColor("#999999"));
        mTextPaint.setTextSize(dp2px(12));
        mTextPaint.getTextBounds(mDes,0,mDes.length(),mRect);
        canvas.drawText(mDes,centerX,centerY+2*mRect.height()+dp2px(10),mTextPaint);    //绘制最小值
        String minValue=String.valueOf(mMinValue);
        String maxValue=String.valueOf(mMaxValue);
        mTextPaint.setTextSize(dp2px(18));
        mTextPaint.getTextBounds(minValue,0,minValue.length(),mRect);
        canvas.drawText(minValue, (float) (centerX-0.6*centerX-dp2px(5)), (float) (centerY+0.75*centerY+mRect.height()+dp2px(5)),mTextPaint);
        //绘制最大指
        mTextPaint.getTextBounds(maxValue,0,maxValue.length(),mRect);
        canvas.drawText(maxValue, (float) (centerX+0.6*centerX+dp2px(5)), (float) (centerY+0.75*centerY+mRect.height()+dp2px(5)),mTextPaint);
    }

    /**
    *绘制圆弧
    *@param canvas
    */
    private void drawArc(Canvas canvas) {
        //绘制圆弧背景
        RectF mRectF=new RectF(mStrokeWith+dp2px(5),mStrokeWith+dp2px(5),getWidth()-mStrokeWith-dp2px(5),getHeight()-mStrokeWith);
        canvas.drawArc(mRectF,startAngle,mAngle,false,mArcPaint);
        //绘制当前数值对应的圆弧
        mArcPaint.setColor(Color.parseColor("#FF4A40"));
        //根据当前数据绘制对应的圆弧
        canvas.drawArc(mRectF,startAngle,mIncludedAngle,false,mArcPaint);
    }

    /** 
    *  为绘制弧度及数据设置动画
    *  @param startAngle 开始的弧度
    *  @param currentAngle 需要绘制的弧度
    *  @param currentValue 需要绘制的数据
    *  @param time 动画执行的时长
    */
    private void setAnimation(float startAngle, float currentAngle,int currentValue, int time) {
        //绘制当前数据对应的圆弧的动画效果
        ValueAnimator progressAnimator = ValueAnimator.ofFloat(startAngle, currentAngle);
        progressAnimator.setDuration(time);
        progressAnimator.setTarget(mIncludedAngle);
        progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mIncludedAngle = (float) animation.getAnimatedValue();
                //重新绘制,不然不会出现效果
                postInvalidate();
            }
        });
        //开始执行动画
        progressAnimator.start();    //中心数据的动画效果
        ValueAnimator valueAnimator = ValueAnimator.ofInt(mAnimatorValue, currentValue);
        valueAnimator.setDuration(2500);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mAnimatorValue = (int) valueAnimator.getAnimatedValue();
                postInvalidate();
            }
        });
        valueAnimator.start();
    }

    /** 
    * 设置数据
    * @param minValue 最小值
    * @param maxValue 最大值
    * @param currentValue 当前绘制的值
    * @param des 描述信息
    */
    public void setValues(int minValue,int maxValue, int currentValue,String des) {
         mDes=des;
         mMaxValue=maxValue;
         mMinValue=minValue;
         //完全覆盖背景弧度
         if (currentValue > maxValue) {
               currentValue = maxValue;
        }
        //计算弧度比重
        float scale = (float) currentValue / maxValue;
        //计算弧度
        float currentAngle = scale * mAngle;
        //开始执行动画
        setAnimation(0, currentAngle, currentValue,2500);
    }


    public float dp2px(float dp) {
        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
        return dp * metrics.density;
    }
}

总结:设置Paint的画笔形状(Cap和Join设置为弧形);使用Canvas的drawArc方法绘制圆弧及drawText绘制文本信息等;ValueAnimator设置数据及当前圆弧进度的动画效果。

欢迎评论与留言,不足之处,欢迎指正!!!

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

推荐阅读更多精彩内容