Android 自定义View实现圆形进度条 深入理解onDraw和onMeasure及自定义属性

Android的View类是用户接口的基础构件,表示屏幕上的一块矩形区域,负责这个区域的绘制和事件处理。自定义View的过程主要包括重写onDraw及onMeasure方法 , 其中onMeasure方法的作用就是计算出自定义View的宽度和高度。这个计算的过程会参照父布局给出的大小(widthMeasureSpec和heightMeasureSpec),以及自己特点算出结果 ;onDraw则根据onMeasure测量后的宽高进行界面的绘制。onDraw主要用到两个类,Canvas和Paint。Canvas画布,相当于现实中画图用的纸或布;Paint画笔,相当于现实中的笔,基本方法有 drawLine 绘制直线 ,drawRect绘制矩形 , drawCirlce绘制圆形。

自定义圆形进度条效果如下

这里写图片描述

完整代码:

package com.circleprogress;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by Administrator on 2017/6/22.
 */

public class CirlceView extends View {

    //定义第一支画笔,画背景的圆
    private Paint mPaintBackCircle;
    //定义第二支画笔,画前景色的圆(进度条的环)
    private Paint mPaintFrontCircle;

    //定义绘制文字的画笔
    private Paint mPaintText;

    //定义环的宽度
    private float mStrokeWidth = 50 ;
    private float mhalfStrokeWidth = mStrokeWidth/2;

    //定义圆心坐标和半径
    private float mX = 200 + mhalfStrokeWidth ;
    private float mY = 200 + mhalfStrokeWidth ;

    private float mRadius = 200 ;

    //定义矩形
    private RectF mRectF ;

    //开始进度
    private int mProgress = 0 ;
    //目标进度
    private int mTargetProgress = 70 ;
    //最大进度
    private int mMax = 100 ;

    //定义view的宽高
    private int mWidth ;
    private int mHeight ;

    //默认半径
    private static final int DEFAULT_RADIUS = 200 ;
    private static final int DEFAULT_STROKE_WIDTH = 50 ;

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

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

    public CirlceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        //获取自定义属性
        if( attrs != null ){
            TypedArray array  = context.obtainStyledAttributes(attrs , R.styleable.CirlceView);

            mRadius = array.getDimensionPixelSize(R.styleable.CirlceView_radius , DEFAULT_RADIUS);
            mStrokeWidth = array.getDimensionPixelSize(R.styleable.CirlceView_stroke_width,DEFAULT_STROKE_WIDTH);

            array.recycle();
        }
        init();
    }

    private void initRect(){

        if(mRectF == null ){
            mRectF = new RectF();
            int viewSize = (int) (mRadius*2);

            int left = (mWidth - viewSize)/2 ;
            int top = (mHeight - viewSize)/2 ;
            int right = left + viewSize ;
            int bottom = top + viewSize ;
            mRectF.set(left,top,right,bottom);
        }
    }

    //初始化画笔
    private void init(){
        mPaintBackCircle = new Paint();
        //设置颜色
        mPaintBackCircle.setColor(Color.WHITE);
        //设置防锯齿
        mPaintBackCircle.setAntiAlias(true);
        //设置为空心
        mPaintBackCircle.setStyle(Paint.Style.STROKE);
        mPaintBackCircle.setStrokeWidth(mStrokeWidth);

        mPaintFrontCircle = new Paint();
        mPaintFrontCircle.setColor(0xFF66C796);
        mPaintFrontCircle.setAntiAlias(true);
        mPaintFrontCircle.setStyle(Paint.Style.STROKE);
        mPaintFrontCircle.setStrokeWidth(mStrokeWidth);

        mPaintText = new Paint();
        mPaintText.setColor(0xFF66C796);
        mPaintText.setAntiAlias(true);
        mPaintText.setTextSize(50);
        //设置文字居中
        mPaintText.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        initRect();
        //获取进度百分比
        float angle = mProgress/(float)mMax * 360 ;

        canvas.drawCircle(mWidth/2 , mHeight/2 , mRadius , mPaintBackCircle );
        canvas.drawArc(mRectF , -90 , angle , false ,mPaintFrontCircle);
        canvas.drawText(mProgress+"%", mWidth/2  , mHeight/2 , mPaintText);

        if( mProgress < mTargetProgress) {
            //更新进度
            mProgress += 2;
            //通知重新绘制
            invalidate();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //测量宽高
        mWidth = getRealSize(widthMeasureSpec);
        mHeight = getRealSize(heightMeasureSpec);

        //保存测量的宽高
        setMeasuredDimension(mWidth,mHeight);
    }

    //测量View的真实尺寸
    public int getRealSize( int measureSpec ){

        int result = -1 ;

        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        if( mode == MeasureSpec.AT_MOST || mode == MeasureSpec.UNSPECIFIED){
            //自己计算
            result = (int) (mRadius * 2 + mStrokeWidth);
        }else {
            result = size ;
        }

        return result;
    }
}

在values目录下新建attrs.xml文件 , 增加自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CirlceView">
    <attr name="radius" format="dimension"></attr>
    <attr name="stroke_width" format="dimension"></attr>
    <attr name="back_circle_color" format="color"></attr>
    <attr name="front_arc_color" format="color"></attr>
    <attr name="text_visibility" format="boolean"></attr>
</declare-styleable>

</resources>

最后在布局文件中引入,设置自定义宽高和半径

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.circleprogress.MainActivity">

    <com.circleprogress.CirlceView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:radius="100dp"
        app:stroke_width="20dp"/>
</RelativeLayout>
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值