我的自定义控件之旅——自定义进度条

上次做一个项目看到一个很简约的进度条,然后就做了一个,结果发现这个进度条网上的做法有好多,不过也不管了,先来看看样子
这里写图片描述

一、首先讲下要用到的两个函数

    在自定义中最重要的就是onMeasure、onDraw以及onLayout了,此次我们用到的是前两个
    onMeasur():这个方法是用来测量View的大小的(宽高)
               用户在这个函数里先计算好宽和高然后调用setMeasuredDimension(width, height)方法就可以完成绘制
               setMeasuredDimension(width, height)方法可以理解为通知view我计算好了并把计算好的宽高传给view
    onDraw():这个方法是用来绘画的(例如要显示文字就利用画笔画一个文字出来)

二、编写自定义view代码

   通过上面的图片可以看到我们需要进度条的高度、字体的大小、进度条的已完成的颜色、未完成的颜色、字体颜色以及起始进度值着六个自定义属性。
   1、首先在values目录下新建attrs.xml文件,然后定义上面所讲的六个属性
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="progress_lineheight" format="dimension"></attr>
    <attr name="progress_textsize" format="dimension"></attr>
    <attr name="progress_finishlinecolor" format="color"></attr>
    <attr name="progress_unfinishlinecolor" format="color"></attr>
    <attr name="progress_textcolort" format="color"></attr>
    <attr name="progress" format="float"></attr>

    <declare-styleable name="MyProgress">
        <attr name="progress_lineheight" ></attr>
        <attr name="progress_textsize" ></attr>
        <attr name="progress_finishlinecolor" ></attr>
        <attr name="progress_unfinishlinecolor" ></attr>
        <attr name="progress_textcolort"></attr>
        <attr name="progress"></attr>
    </declare-styleable>
</resources>
   2、接下来创建我们自定义控件类MyProgress继承自View(当然也可以继承ProgressBar这样还不用自己考虑进度方面的问题),然后声明6个属性变量,以及6个静态变量(对应6个属性变量的默认值),当然不能忘记将dp、sp等单位转化为px单位了
    private static final int LINE_HEIGHT = 2;//进度条高度;dp
    private static final int TEXT_SIZE = 13;//显示进度的字体的大小;sp
    private static final int FINISHLINE_COLOR = 0xFFFF0000;//完成部分进度的颜色
    private static final int UNFINISHLINE_COLOR = 0xFF00FFFF;//未完成部分进度的颜色
    private static final int TEXT_COLOR = FINISHLINE_COLOR;//字体的颜色
    private static final int PROGRESS = 0;//进度;

    private int mLineHeight = dptopx(LINE_HEIGHT);
    private int mTextSize = sptopx(TEXT_SIZE);
    private int mFinishLineColor = FINISHLINE_COLOR;
    private int mUnFinishLineColor = UNFINISHLINE_COLOR;
    private int mTextColor = TEXT_COLOR;
    private float mProgress =  PROGRESS;

    /**
     * dp转px
     * @param dp
     * @return
     */
    private int dptopx(int dp) {
        return (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

    /**
     * sp转px
     * @param sp
     * @return
     */
    private int sptopx(int sp) {
        return (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
 }
   然后定义画笔变量,以及构造函数,在构造函数中,我们初始化画笔,获取自定义属性,然后设置画笔的字体大小以便以后计算字体打宽高使用
private Paint mPaint ;

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

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

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

        inint(attrs);

    }

    private void inint(AttributeSet attrs) {

        mPaint = new Paint();

        obtainStyledAttr(attrs);

        mPaint.setTextSize(mTextSize);
    }

    /**
     * 获取自定义属性
     * @param attrs
     */
    private void obtainStyledAttr(AttributeSet attrs) {
        TypedArray t = getContext()
                .obtainStyledAttributes(attrs,R.styleable.MyProgress);

        mLineHeight= (int) t.getDimension(
                R.styleable.MyProgress_progress_lineheight,mLineHeight);

         mTextSize= (int) t.getDimension(
                 R.styleable.MyProgress_progress_textsize,mTextSize);

        mFinishLineColor = t.getColor(
                R.styleable.MyProgress_progress_finishlinecolor,mFinishLineColor);

        mUnFinishLineColor = t.getColor(
                R.styleable.MyProgress_progress_unfinishlinecolor,mUnFinishLineColor);

        mTextColor = t.getColor(
                R.styleable.MyProgress_progress_textcolort,mTextColor);

        mProgress = t.getFloat(
                R.styleable.MyProgress_progress,mProgress);

        t.recycle();
    }
    接着给进度度添加get和set,get我们*100再输出,set的是后要通知view重绘
public int getprogress() {
        return (int) (mProgress*100);
    }

    public void setprogress(float progress) {
        this.mProgress = progress;
        //通知view重绘
        invalidate();
    }
    接下来在onMeasure方法里面计算view的宽和高,widthMeasureSpec和heightMeasureSpec传入的不是一个值,而是两个,我们可以通过MeasureSpec.getMode()和MeasureSpec.getSize()获取类型和大小,另外我们的是条形横向进度条所以宽度一定是给定的不能包裹内容的所以我们只需要获取size就可以了,计算完之后调用setMeasuredDimension(width,heitht)方法通知view就可以了
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int width = MeasureSpec.getSize(widthMeasureSpec);
        int heitht = getmeasure(heightMeasureSpec);
        //通知view测量完毕,并将数据传给它
        setMeasuredDimension(width,heitht);
    }
    /**
     * 计算view的高度
     * @param heightMeasureSpec
     * @return view的高度
     */
    private int getmeasure(int heightMeasureSpec) {

        int result = 0;

        int Mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);

        switch (Mode){
            case MeasureSpec.EXACTLY://用户给定精确值
                result = size;
                break;
            case MeasureSpec.UNSPECIFIED://父容器对于子容器没有任何限制,子容器想要多大就多大
                //测量文字的高度
                int textHeight = (int) (mPaint.descent()+mPaint.ascent());
                //获取文字和进度条两者高度的最大值
                result = getPaddingBottom()+getPaddingTop()+Math.max(textHeight,mLineHeight);
                break;
            case MeasureSpec.AT_MOST://子容器可以是声明大小内的任意大小
                int textheight = (int) (mPaint.descent()+mPaint.ascent());
                int maxHeight = getPaddingBottom()+getPaddingTop()+Math.max(textheight,mLineHeight);
                //获取计算出来的高度和实际可用最大高度的最小值
                result = Math.min(maxHeight,size);
                break;
        }
        return result;
    }
   接着是绘画了,绘画分为三个部分,第一部分是画以及完成的进度条部分(首先判断进度是否为0,如果为0就说名没有已经完成的部分,第一部分就不需要画了),首先计算起点,起点X应该是从paddingleft开始,Y嘛就是一半的高度了,然后将画布移过去就好了,然后计算第一部分的长度,第一部分的长度等于(view的宽度-左右padding-字体的长度)*进度/100。
        canvas.translate(getPaddingLeft(),getHeight()/2);
        //两边添加空格使三个部分有分开的感觉
        String str = " "+getprogress()+"% ";
        int strWidth = (int) mPaint.measureText(str);
        int width = getWidth()-getPaddingLeft()-getPaddingRight()-strWidth;
        int drawToX = width*getprogress()/100;
        //已经完成的部分
        if (getprogress()!=0){
            mPaint.setColor(mFinishLineColor);
            mPaint.setStrokeWidth(mLineHeight);
            canvas.drawLine(0,0,drawToX,0,mPaint);
        }
   第二部分是文字部分,文字部分起点是第一部分的结束,就是代码中的drawToX,高度要向下移动字体一半的高度
        //进度文字
        mPaint.setColor(mTextColor);
        int y = (int) ((mPaint.descent()+mPaint.ascent())/2);
        canvas.drawText(str,drawToX,-y,mPaint);
   第三部分是未完成部分,和第一部分类似,判断进度是否为100,如果是一百就不需要画了,第三部分的起始点是第一部分的长度+第二部份的长度,结束点是view的宽度-左右padding
        //未完成部分
        if (getprogress()!=100){
            mPaint.setColor(mUnFinishLineColor);
            mPaint.setStrokeWidth(mLineHeight);
            canvas.drawLine(drawToX+strWidth,0,width+strWidth,0,mPaint);
        }
   下面是自定义view的全部代码:
package com.linzhou.myview;

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


/*
 *项目名: MyView
 *包名:   com.linzhou.myview
 *创建者:  linzhou
 *创建时间:17/08/05
 *描述:   
 */

public class MyProgress extends View {

    private static final int LINE_HEIGHT = 2;//进度条高度;dp
    private static final int TEXT_SIZE = 13;//显示进度的字体的大小;sp
    private static final int FINISHLINE_COLOR = 0xFFFF0000;//完成部分进度的颜色
    private static final int UNFINISHLINE_COLOR = 0xFF00FFFF;//未完成部分进度的颜色
    private static final int TEXT_COLOR = FINISHLINE_COLOR;//字体的颜色
    private static final int PROGRESS = 0;//进度;

    private int mLineHeight = dptopx(LINE_HEIGHT);
    private int mTextSize = sptopx(TEXT_SIZE);
    private int mFinishLineColor = FINISHLINE_COLOR;
    private int mUnFinishLineColor = UNFINISHLINE_COLOR;
    private int mTextColor = TEXT_COLOR;
    private float mProgress =  PROGRESS;

    private Paint mPaint ;

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

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

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

        inint(attrs);

    }

    private void inint(AttributeSet attrs) {

        mPaint = new Paint();

        obtainStyledAttr(attrs);

        mPaint.setTextSize(mTextSize);
    }

    /**
     * 获取自定义属性
     * @param attrs
     */
    private void obtainStyledAttr(AttributeSet attrs) {
        TypedArray t = getContext()
                .obtainStyledAttributes(attrs,R.styleable.MyProgress);

        mLineHeight= (int) t.getDimension(
                R.styleable.MyProgress_progress_lineheight,mLineHeight);

         mTextSize= (int) t.getDimension(
                 R.styleable.MyProgress_progress_textsize,mTextSize);

        mFinishLineColor = t.getColor(
                R.styleable.MyProgress_progress_finishlinecolor,mFinishLineColor);

        mUnFinishLineColor = t.getColor(
                R.styleable.MyProgress_progress_unfinishlinecolor,mUnFinishLineColor);

        mTextColor = t.getColor(
                R.styleable.MyProgress_progress_textcolort,mTextColor);

        mProgress = t.getFloat(
                R.styleable.MyProgress_progress,mProgress);

        t.recycle();
    }

    public int getprogress() {
        return (int) (mProgress*100);
    }

    public void setprogress(float progress) {
        this.mProgress = progress;
        //通知view重绘
        invalidate();
    }

    /**
     * dp转px
     * @param dp
     * @return
     */
    private int dptopx(int dp) {
        return (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

    /**
     * sp转px
     * @param sp
     * @return
     */
    private int sptopx(int sp) {
        return (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
    }

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

        int width = MeasureSpec.getSize(widthMeasureSpec);
        int heitht = getmeasure(heightMeasureSpec);
        //通知view测量完毕,并将数据传给它
        setMeasuredDimension(width,heitht);
    }

    /**
     * 计算view的高度
     * @param heightMeasureSpec
     * @return view的高度
     */
    private int getmeasure(int heightMeasureSpec) {

        int result = 0;

        int Mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);

        switch (Mode){
            case MeasureSpec.EXACTLY://用户给定精确值
                result = size;
                break;
            case MeasureSpec.UNSPECIFIED://父容器对于子容器没有任何限制,子容器想要多大就多大
                //测量文字的高度
                int textHeight = (int) (mPaint.descent()+mPaint.ascent());
                //获取文字和进度条两者高度的最大值
                result = getPaddingBottom()+getPaddingTop()+Math.max(textHeight,mLineHeight);
                break;
            case MeasureSpec.AT_MOST://子容器可以是声明大小内的任意大小
                int textheight = (int) (mPaint.descent()+mPaint.ascent());
                int maxHeight = getPaddingBottom()+getPaddingTop()+Math.max(textheight,mLineHeight);
                //获取计算出来的高度和实际可用最大高度的最小值
                result = Math.min(maxHeight,size);
                break;
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {

        canvas.translate(getPaddingLeft(),getHeight()/2);

        String str = " "+getprogress()+"% ";
        int strWidth = (int) mPaint.measureText(str);
        int width = getWidth()-getPaddingLeft()-getPaddingRight()-strWidth;
        int drawToX = width*getprogress()/100;
        //已经完成的部分
        if (getprogress()!=0){
            mPaint.setColor(mFinishLineColor);
            mPaint.setStrokeWidth(mLineHeight);
            canvas.drawLine(0,0,drawToX,0,mPaint);
        }
        //进度文字
        mPaint.setColor(mTextColor);
        int y = (int) ((mPaint.descent()+mPaint.ascent())/2);
        canvas.drawText(str,drawToX,-y,mPaint);
        //未完成部分
        if (getprogress()!=100){
            mPaint.setColor(mUnFinishLineColor);
            mPaint.setStrokeWidth(mLineHeight);
            canvas.drawLine(drawToX+strWidth,0,width+strWidth,0,mPaint);
        }
    }
}

三、测试代码

  MainActivity的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:lz="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.linzhou.myview.MainActivity">

    <com.linzhou.myview.MyProgress
        android:id="@+id/pg1"
        android:layout_width="match_parent"
        android:layout_marginTop="20dp"
        android:layout_height="wrap_content"
        android:padding="10dp" />

    <com.linzhou.myview.MyProgress
        android:id="@+id/pg2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        lz:progress="0.5"
        lz:progress_textcolort="@color/coloAccent"
        lz:progress_finishlinecolor="@color/colorAccent"
        lz:progress_unfinishlinecolor="@color/coloAccent" />

    <Button
        android:id="@+id/start"
        android:text="start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>
   MainActivity代码:
package com.linzhou.myview;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private MyProgress pg1,pg2;
    private Button start;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Bundle bundle = msg.getData();
            int pg = bundle.getInt("pg");
            Log.d("linzhou123",pg+"");
            if (pg>=100) start.setEnabled(true);

            pg1.setprogress(((float)pg)/100);
            pg2.setprogress(((float)pg)/100);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        pg1 = (MyProgress) findViewById(R.id.pg1);
        pg2 = (MyProgress) findViewById(R.id.pg2);

        start = (Button) findViewById(R.id.start);

        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                start.setEnabled(false);
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0;i<=100;i++){
                            try {
                                Thread.sleep(200);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            Message message = new Message();
                            Bundle bundle = new Bundle();
                            bundle.putInt("pg",i);
                            message.setData(bundle);
                            mHandler.sendMessage(message);
                        }
                    }
                }).start();
            }
        });
    }
}
   运行图:

这里写图片描述
这里写图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值