Android自定义进度条

效果图

这里写图片描述

代码实现

首先讲一下自定义View的一个大致的步骤有哪些:

  1. 自定义属性的声明与获取
  2. 测量onMeasure
  3. 布局onLayout(ViewGroup)
  4. 绘制onDraw
  5. onTouchEvent
  6. onInterceptTouchEvent(ViewGroup)

    实现上图所示的效果,我们只需要用到1,2,4这三个步骤,接下来就带大家一步步实现。

自定义属性的声明与获取

首先,在当前项目的values文件夹下新建attrs.xml文件,里面编写我们的自定义属性,文件内容如下,我们总结了如下的属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleProgressView">
        <!--圆的半径-->
        <attr name="circleRadius" format="dimension"/>
        <!--圆的颜色-->
        <attr name="circleColor" format="color"/>
        <!--文字颜色-->
        <attr name="textColor" format="color"/>
        <!--弧线颜色-->
        <attr name="arcColor" format="color"/>
        <!--弧线宽度-->
        <attr name="arcWidth" format="dimension"/>
        <!--文字大小-->
        <attr name="textSize" format="dimension"/>
        <!--弧线与圆直间的间距-->
        <attr name="arcPadding" format="dimension"/>
    </declare-styleable>
</resources>

自定义属性声明好之后,我们在代码中来进行获取,写一个类CircleProgressView继承自ProgressBar,在构造方法中我们进行自定义属性的获取,

public class CircleProgressView extends ProgressBar {
    /*默认圆形颜色*/
    public static final int DEFAULT_CIRCLE_COLOR = 0XFF0000;
    /*弧线默认颜色*/
    public static final int DEFAULT_ARC_COLOR = 0X00FF00;
    /*文字默认颜色*/
    public static final int DEFAULT_TEXT_COLOR = 0X000000;
    /*圆形默认半径*/
    public static final int DEFAULT_CIRCLE_RADIUS = 100;
    /*弧线默认宽度*/
    public static final int DEFAULT_ARC_WIDTH = 40;
    /*默认文字大小*/
    public static final int DEFAULT_TEXT_SIZE = 60;
    /*弧线和圆之间默认的间距*/
    public static final int DEFAULT_ARC_PADDING = 30;

    private int circleColor;
    private float circleRadius;
    private int arcColor;
    private int textColor;

    private float arcWidth;
    private float textSize;
    private float arcPadding;
    private int mRealWidth;


    public CircleProgressView(Context context) {
        this(context,null);//一个参数的构造方法调用两个参数的构造方法
    }

    public CircleProgressView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView);
        circleColor = typedArray.getColor(R.styleable.CircleProgressView_circleColor, DEFAULT_CIRCLE_COLOR);
        circleRadius = typedArray.getDimension(R.styleable.CircleProgressView_circleRadius,DEFAULT_CIRCLE_RADIUS);
        arcColor = typedArray.getColor(R.styleable.CircleProgressView_arcColor, DEFAULT_ARC_COLOR);
        textColor = typedArray.getColor(R.styleable.CircleProgressView_textColor, DEFAULT_TEXT_COLOR);
        arcWidth = typedArray.getDimension(R.styleable.CircleProgressView_arcWidth, DEFAULT_ARC_WIDTH);
        textSize = typedArray.getDimension(R.styleable.CircleProgressView_textSize, DEFAULT_TEXT_SIZE);
        arcPadding = typedArray.getDimension(R.styleable.CircleProgressView_arcPadding, DEFAULT_ARC_PADDING);
    }

}

测量onMeasure

接下来是要重写onMeasure方法来对自定义的进度条进行测量,为何要重写onMeasure方法呢?直接开始绘制不就完了吗?这里我说一下重写onMeasure方法的意义何在:

简单一句话,重写onMeasure就是为了给View一个wrap_content时的默认大小,如果我们在自定义View时不重写onMeasure,那么当你指定View的宽高为wrap_content时,此时的效果是充满整个父控件,所以如果需要使用wrap_content属性(根据内容确定控件大小),必须重写该方法。

先看一张图

这里写图片描述

很明显,这个进度条的宽度等于三部分之和:

(圆半径+圆与弧线之间的间距+弧宽度)*2

然后我们需要设置View最终的尺寸,下面给出onMeasure方法的实现

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //期望的宽度
        int expectSize = (int) ((circleRadius+arcPadding+arcWidth)*2+getPaddingLeft()+getPaddingRight());
        int width = resolveSize(expectSize,widthMeasureSpec);
        int height = resolveSize(expectSize,heightMeasureSpec);
        mRealWidth = Math.min(width,height);//取宽和高中较小的一个值作为View的尺寸
        setMeasuredDimension(mRealWidth, mRealWidth);
    }

其中用到了一个比较有用的方法resolveSize,这是系统已经为我们封装好的,简单说一下,这个方法的主要作用就是根据你提供的大小和传过来的MeasureSpec,返回最终的大小值,这个里面根据传入模式的不同来做相应的处理。最后调不要忘了调用setMeasuredDimension方法保存设置的值。

绘制onDraw

这里我们分三部分进行绘制,圆形,弧线以及圆形中间的文字

画圆
画圆的重点在于圆心位置和半径的确定,很明显,圆心位置在当前View的中心,半径由我们的自定义属性进行设置,废话不多说,直接上代码

//创建画笔
        Paint circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //设置画笔颜色
        circlePaint.setColor(circleColor);  
//画圆        canvas.drawCircle(mRealWidth/2,mRealWidth/2,circleRadius,circlePaint);

画弧线

画弧线我们首先要确定弧线所在矩形的位置,因为在Android中,弧线是由一个矩形来规定的,如图所示

这里写图片描述

 //画弧线
        Paint arcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        arcPaint.setColor(arcColor);
        arcPaint.setStrokeWidth(arcWidth);
        arcPaint.setStyle(Paint.Style.STROKE);
        RectF rectF = new RectF(arcWidth/2,arcWidth/2,mRealWidth-arcWidth/2,mRealWidth-arcWidth/2);
        Log.d(TAG, "onDraw: "+rectF.toString());
        float sweepValue = getProgress()*1.0f/getMax()*360;
        canvas.drawArc(rectF,-90,sweepValue,true,arcPaint);

 //画文字
        Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setTextSize(textSize);
        textPaint.setColor(textColor);
        String tvContent = getProgress()+"%";
        float height = getTextHeight(tvContent,textPaint);
        canvas.drawText(tvContent,mRealWidth/2,mRealWidth/2+height/2,textPaint);

这里写图片描述

这里写图片描述

获取文字高度的方法如下


    public float getTextHeight(String text,Paint paint){
        Rect bounds = new Rect();
        paint.getTextBounds(text,0,text.length(),bounds);
        return bounds.height();
    }

布局文件如下

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    >
    <com.example.personcenter.CircleProgressView
        android:id="@+id/circleView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:circleColor="@android:color/holo_green_light"
        app:circleRadius="150px"
        app:textColor="@android:color/holo_red_light"
        app:arcColor="@android:color/holo_blue_light"
        app:arcWidth="50px"
        app:textSize="16sp"
        app:arcPadding="50px"
        android:max="100"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="update"
        android:text="特么的动起来"
        android:layout_marginTop="20dp"
        />
</LinearLayout>

最后我们在代码中控制进入条的变化。

public class MainActivity extends AppCompatActivity {
    public static final int MSG_UPDATE = 0X11;

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
                int currentProgress = circleProgressView.getProgress();
                circleProgressView.setProgress(++currentProgress);
                if(currentProgress >= circleProgressView.getMax()){
                    handler.removeMessages(MSG_UPDATE);
                }else{
                    handler.sendEmptyMessageDelayed(MSG_UPDATE,200);
            }
        }
    };
    private CircleProgressView circleProgressView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        circleProgressView = (CircleProgressView) findViewById(R.id.circleView);
    }

    public void update(View view){
        handler.sendEmptyMessage(MSG_UPDATE);
    }
}

效果二

这里写图片描述

public class CircleProgressView extends ProgressBar {
    /*默认圆形颜色*/
    public static final int DEFAULT_CIRCLE_COLOR = 0XFF0000;
    /*弧线默认颜色*/
    public static final int DEFAULT_ARC_COLOR = 0X00FF00;
    /*文字默认颜色*/
    public static final int DEFAULT_TEXT_COLOR = 0X000000;
    /*圆形默认半径*/
    public static final int DEFAULT_CIRCLE_RADIUS = 100;
    /*弧线默认宽度*/
    public static final int DEFAULT_ARC_WIDTH = 40;
    /*默认文字大小*/
    public static final int DEFAULT_TEXT_SIZE = 60;
    /*弧线和圆之间默认的间距*/
    public static final int DEFAULT_ARC_PADDING = 30;

    private float circleRadius;
    private int arcColor;
    private int textColor;

    private float arcWidth;
    private float textSize;
    private float arcPadding;
    private int mRealSize;

    private Paint mPaintCircle;

    private Path mPath;
    private Paint mPathPaint;
    private int width;
    private int height;
    private int count = 0;
    private Canvas bitmapCanvas;//定义Bitmap的画布
    private Bitmap bitmap;//定义Bitmap的画布

    //设置进度
    private int maxProgress = 100;
    private int currentProgress = 0;
    private static final int NEED_INVALIDATE = 0X6666;
    //操作UI主线程
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case NEED_INVALIDATE:
                    //更新时间
                    count += 5;
                    if (count > 80) {
                        count = 0;
                    }
                    invalidate();
                    sendEmptyMessageDelayed(NEED_INVALIDATE, 50);
                    break;
            }

        }
    };

    public CircleProgressView(Context context) {
        this(context, null);//一个参数的构造方法调用两个参数的构造方法
    }

    public CircleProgressView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView);
        circleRadius = typedArray.getDimension(R.styleable.CircleProgressView_circleRadius, DEFAULT_CIRCLE_RADIUS);
        arcColor = typedArray.getColor(R.styleable.CircleProgressView_arcColor, DEFAULT_ARC_COLOR);
        textColor = typedArray.getColor(R.styleable.CircleProgressView_textColor, DEFAULT_TEXT_COLOR);
        arcWidth = typedArray.getDimension(R.styleable.CircleProgressView_arcWidth, DEFAULT_ARC_WIDTH);
        textSize = typedArray.getDimension(R.styleable.CircleProgressView_textSize, DEFAULT_TEXT_SIZE);
        arcPadding = typedArray.getDimension(R.styleable.CircleProgressView_arcPadding, DEFAULT_ARC_PADDING);
        //初始化绘制路径的画笔
        mPath = new Path();
        mPathPaint = new Paint();
        mPathPaint.setAntiAlias(true);
        mPathPaint.setColor(Color.argb(0xff, 0xff, 0x69, 0x5a));
        mPathPaint.setStyle(Paint.Style.FILL);//设置为填充,默认为填充,这里我们还是定义下
        mPathPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

        mPaintCircle = new Paint();
        mPaintCircle.setAntiAlias(true);
        mPaintCircle.setColor(Color.argb(0xff, 0xf8, 0x8e, 0x8b));

        handler.sendEmptyMessageDelayed(NEED_INVALIDATE, 50);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //期望的宽度
        int expectSize = (int) ((circleRadius+arcPadding+arcWidth)*2+getPaddingLeft()+getPaddingRight());
        width = resolveSize(expectSize,widthMeasureSpec);
        height = resolveSize(expectSize,heightMeasureSpec);
        mRealSize = Math.min(width, height);//取宽和高中较小的一个值作为View的尺寸
        setMeasuredDimension(width, mRealSize);
        bitmap = Bitmap.createBitmap(width, mRealSize, Bitmap.Config.ARGB_8888);
        bitmapCanvas = new Canvas(bitmap);
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制Bitmap上的圆形
        bitmapCanvas.drawCircle(mRealSize / 2, height / 2, 150, mPaintCircle);
        //通过Path绘制贝塞尔曲线
        mPath.reset();
        mPath.moveTo(mRealSize, (height / 2 + 150) - (currentProgress * 300f / maxProgress));
        mPath.lineTo(mRealSize, height / 2 + 200);
        mPath.lineTo(count, height / 2 + 200);
        mPath.lineTo(count, (height / 2 + 150) - (currentProgress * 300f / maxProgress));
        for (int i = 0; i < 10; i++) {
            mPath.rQuadTo(20, 5, 40, 0);
            mPath.rQuadTo(20, -5, 40, 0);
        }
        mPath.close();
        //将贝塞尔曲线绘制到Bitmap的Canvas上
        bitmapCanvas.drawPath(mPath, mPathPaint);
//
        //画弧线
        Paint arcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        arcPaint.setColor(arcColor);
        arcPaint.setStrokeWidth(arcWidth);
        arcPaint.setStyle(Paint.Style.STROKE);
        RectF rectF = new RectF(arcWidth/2,arcWidth/2, mRealSize -arcWidth/2, mRealSize -arcWidth/2);
        float sweepValue = currentProgress*1.0f/maxProgress*360;
        bitmapCanvas.drawArc(rectF,-90,sweepValue,false,arcPaint);
        //画文字
        Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setTextSize(textSize);
        textPaint.setColor(textColor);
        String tvContent = currentProgress+"%";
        float height = getTextHeight(tvContent,textPaint);
        bitmapCanvas.drawText(tvContent, mRealSize /2, mRealSize /2+height/2,textPaint);
        canvas.drawBitmap(bitmap, 0, 0, null);
    }
    public float getTextHeight(String text,Paint paint){
        Rect bounds = new Rect();
        paint.getTextBounds(text,0,text.length(),bounds);
        return bounds.height();
    }
    public void setCurrentProgress(int currentProgress) {
        this.currentProgress = currentProgress;
        invalidate();//实时更新进度
    }
    public int getMaxProgress() {
        return maxProgress;
    }

    public void setMaxProgress(int maxProgress) {
        this.maxProgress = maxProgress;
    }

    public int getCurrentProgress() {
        return currentProgress;
    }
}
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private Button mButton;
    private CircleProgressView circleProgressView;
    public static final int MSG_UPDATE = 0X11;
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int currentProgress = circleProgressView.getCurrentProgress();
            circleProgressView.setCurrentProgress(++currentProgress);
            if (currentProgress >= circleProgressView.getMaxProgress()) {
                handler.removeMessages(MSG_UPDATE);
            } else {
                sendEmptyMessageDelayed(MSG_UPDATE,100);
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.btn);
        circleProgressView = (CircleProgressView) findViewById(R.id.circleView);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (circleProgressView.getCurrentProgress() < circleProgressView.getMaxProgress()) {
                    handler.sendEmptyMessage(MSG_UPDATE);
                }
            }
        });
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值