Android自定义带进度的刻度条

本文介绍如何在Android中自定义一个带有进度和刻度的水平进度条控件,详细讲解了自定义属性声明、测量、布局、绘制以及触摸事件处理等步骤,并提供了代码示例,展示了如何在XML布局中使用该自定义控件。

如何自定义控件?
1.自定义属性的声明和获取;
2.测量onMeasure;
3.布局onLayout(ViewGroup);
4.绘制onDraw;
5.onTouchEvent;
6.onInterceptTouchEvent(ViewGroup);
7.状态的恢复与保存(与Activity生命周期有关);

自定义绘制的PrograssBar的水平进度条如下 有刻度 刻度在中间显示
这里写图片描述

下面由代码实现:在values文件夹下建立attr.xml 放入要绘制进度条所需要的。分别是进度条字体①颜色和②大小,Prograss走过的条的③颜色和④高度,Prograss没有走过的条的⑤颜色和⑥高度,还有显示数值和进度条之间有间隔的空间⑦宽度。一共七个参数。

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- 声明-->
    <attr name="prograss_unreach_color" format="color"></attr>
    <attr name="prograss_unreach_height" format="dimension"></attr>
    <attr name="prograss_reach_color" format="color"></attr>
    <attr name="prograss_reach_height" format="dimension"></attr>
    <attr name="text_color" format="color"></attr>
    <attr name="text_size" format="dimension"></attr>
    <attr name="prograss_text_offset" format="dimension"></attr>

    <!-- 使用-->
    <declare-styleable name="horizonalPrograssBarWithPrograss">
        <attr name="prograss_unreach_color" ></attr>
        <attr name="prograss_unreach_height" ></attr>
        <attr name="prograss_reach_color" ></attr>
        <attr name="prograss_reach_height" ></attr>
        <attr name="text_color"></attr>
        <attr name="text_size"></attr>
        <attr name="prograss_text_offset" ></attr>


    </declare-styleable>
</resources>

新建包View,并新建类class horizonalPrograssBarWithPrograss,如下:

package com.chase.view;

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.widget.ProgressBar;

import com.chase.cn.customprograssbar.R;

/**
 * Created by Chase on 2016/11/29.
 */

public class horizonalPrograssBarWithPrograss extends ProgressBar {

    //这里声明默认值
    private static final int DEFAULT_TEXT_SIZE = 10;//sp
    private static final int DEFAULT_TEXT_COLOR = 0xFFC00D1;
    private static final int DEFAULT_COLOR_UNREACH = 0XFFD3D6DA;
    private static final int DEFAULT_HEIGHT_UNREACH = 2;//dp
    private static final int DEFAULT_COLOR_REACH = 0xFFC00D1;
    private static final int DEFAULT_HRIGHT_REACH = 2;//dp
    private static final int DEFAULT_TEXT_OFFSET = 10;//dp

    //编写我们的值
    protected int mTextSize = dp2px(DEFAULT_TEXT_SIZE);
    protected int mTextColor = DEFAULT_TEXT_COLOR;
    protected int mUnReachColor = DEFAULT_COLOR_UNREACH;
    protected int mUnReachHeight = DEFAULT_HEIGHT_UNREACH;
    protected int mReachColor = DEFAULT_COLOR_REACH;
    protected int mReachHeight = DEFAULT_HRIGHT_REACH;
    protected int mTextOffset = dp2px(DEFAULT_TEXT_OFFSET);

    protected Paint mPaint = new Paint();
    protected int mRealWidth; //进度条实际宽度

    //2个参数法
    public horizonalPrograssBarWithPrograss(Context context, AttributeSet attrs) {
        this(context, attrs, 0);//2个参数继承3个参数的方法
    }

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


    }

    //获取自定义属性
    private void obtainStyledAttrs(AttributeSet attrs) {
        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.horizonalPrograssBarWithPrograss);

        mTextSize = (int) ta.getDimension(R.styleable.horizonalPrograssBarWithPrograss_text_size, mTextSize);
        mTextColor = ta.getColor(R.styleable.horizonalPrograssBarWithPrograss_text_color, mTextColor);
        mTextOffset = (int) ta.getDimension(R.styleable.horizonalPrograssBarWithPrograss_prograss_text_offset, mTextOffset);
        mUnReachColor = ta.getColor(R.styleable.horizonalPrograssBarWithPrograss_prograss_unreach_color, mUnReachColor);
        mUnReachHeight = (int) ta.getDimension(R.styleable.horizonalPrograssBarWithPrograss_prograss_unreach_height, mUnReachHeight);
        mReachColor = ta.getColor(R.styleable.horizonalPrograssBarWithPrograss_prograss_reach_color, mReachColor);
        mReachHeight = (int) ta.getDimension(R.styleable.horizonalPrograssBarWithPrograss_prograss_reach_height, mReachHeight);

        mPaint.setTextSize(mTextSize);
        ta.recycle();
    }

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        int widthMode = MeasureSpec.getMode(widthMeasureSpec);//因为是水平进度条 默认宽度为用户的输入 所以不需要判断
        int widthVal = MeasureSpec.getSize(widthMeasureSpec);

        int height = measureHeight(heightMeasureSpec);

        setMeasuredDimension(widthVal, height);//完成测量
        //实际绘制区域的宽度
        mRealWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
    }

    //高度测量的方法
    private int measureHeight(int heightMeasureSpec) {
        int result = 0;
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);

        if (mode == MeasureSpec.EXACTLY) {//精确值,用户给的精确值 如200dp marchparent
            result = size;
        } else {//否则宽度 是由文字的距离确定的
            int textHeight = (int) (mPaint.descent() - mPaint.ascent());
            result = getPaddingTop() //上边距
                    + getPaddingBottom() //下边距
                    + Math.max(Math.max(mReachHeight, mUnReachHeight), Math.abs(textHeight));//三者最大值
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }

        return result;
    }

    //1个参法
    public horizonalPrograssBarWithPrograss(Context context) {
        this(context, null);//继承两个参数的构造方法,当用户在使用时new的时候用,但是使用时一般是用两个参数的构造方法的
    }



 /**
     *
     下面是绘制进度条的方法
     * @param canvas
     */
    @Override
    protected synchronized void onDraw(Canvas canvas) {
//        super.onDraw(canvas);
        canvas.save();
        canvas.translate(getPaddingLeft(), getHeight() / 2);//移动画布到 最左边 正中间

        //判断是否需要绘制unReachBar
        boolean noNeedUnReach = false;
        //DrawReachBar如下:

        //拿到文本宽度
        String text = getProgress() + "";
        int textWidth = (int) mPaint.measureText(text);

        float ratio = getProgress() * 1.0f / getMax();
        float prograssX = ratio * mRealWidth;
        if (prograssX + textWidth > mRealWidth) {
            prograssX = mRealWidth - textWidth;//防止进度条到100文本出去
            noNeedUnReach = true;
        }

        float endX = prograssX - mTextOffset / 2;
        if (endX > 0) {
            mPaint.setColor(mReachColor);
            mPaint.setStrokeWidth(mReachHeight);
            canvas.drawLine(0, 0, endX, 0, mPaint);
        }

        //Draw Text
        mPaint.setColor(mTextColor);
        int y = (int) (-(mPaint.descent()+mPaint.ascent())/2);
        canvas.drawText(text,prograssX,y,mPaint);

        //Draw UnReachBar
        if (!noNeedUnReach){
            float start = prograssX + mTextOffset/2 +textWidth;
            mPaint.setColor(mUnReachColor);
            mPaint.setStrokeWidth(mReachHeight);
            canvas.drawLine(start,0,mRealWidth,0,mPaint);
        }

        canvas.restore();
    }

    //转换方法dp 2 px
    protected int dp2px(int dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, getResources().getDisplayMetrics());
    }

    protected int sp2px(int spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, getResources().getDisplayMetrics());
    }


}

这是进度条的绘制完成了,上面进度条高度的测量,实际比较关键的是测量文字的高度,如图:这里写图片描述

然后现在可以在activity_main.xml中定义并测试:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:chase="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <com.chase.view.horizonalPrograssBarWithPrograss
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:padding="5dp"
            android:progress="50" />

        <com.chase.view.horizonalPrograssBarWithPrograss
            android:id="@+id/prograss2"
            chase:text_color="#44ff0000"
            chase:prograss_unreach_color="#000"
            chase:prograss_reach_height="5dp"
            chase:prograss_unreach_height="5dp"
            chase:text_size="10sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:padding="5dp"
            android:progress="0" />


    </LinearLayout>
</ScrollView>

就如上面的,我们可以自定义进度条的属性,只需要添加这行:(这是在AS中)

xmlns:chase="http://schemas.android.com/apk/res-auto"

Eclipse中应该要这么写(跟上ManiFeast中显示的包名):

xmlns:chase="http://schemas.android.com/apk/com.chase.cn.customprograssbar"

如前面所示,第二个PrograssBar进行进度变化测试,在MainActivity中:用handler进行测试:

package com.chase.cn.customprograssbar;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;

import com.chase.view.horizonalPrograssBarWithPrograss;


public class MainActivity extends AppCompatActivity {

    private horizonalPrograssBarWithPrograss mPrograss;

    private static final int MSG_UPDATE = 0x111;
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            int prograss = mPrograss.getProgress();
            mPrograss.setProgress(++prograss);
            if (prograss>=100){
                handler.removeMessages(MSG_UPDATE);
            }else {
                handler.sendEmptyMessageDelayed(MSG_UPDATE,100);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mPrograss = (horizonalPrograssBarWithPrograss) findViewById(R.id.prograss2);
        handler.sendEmptyMessage(MSG_UPDATE);
    }
}

下面的代码自定义了圆形的进度条,样式与上面的相似,只不过是圆形的。

首先在attr.xml中继续添加:添加圆形需要的半径

<declare-styleable name="RoundPrograssBarWithPrograss">
        <attr name="radius" format="dimension" ></attr>
    </declare-styleable>

在View包下新建class RoundPrograssBarWithPrograss,继承上面的水平进度条。

package com.chase.view;

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

import com.chase.cn.customprograssbar.R;

/**
 * Created by Chase on 2016/11/30.
 */

public class RoundPrograssBarWithPrograss extends horizonalPrograssBarWithPrograss {

    private int mRadius = dp2px(30);

    private int mMaxPaintWidth;

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

        mReachHeight = (int) (mUnReachHeight * 2.5f);

        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.RoundPrograssBarWithPrograss);
        mRadius = (int) ta.getDimension(R.styleable.RoundPrograssBarWithPrograss_radius, mRadius);
        ta.recycle();

        //设置画笔属性
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);//抗锯齿
        mPaint.setDither(true);//防抖动
        mPaint.setStrokeCap(Paint.Cap.ROUND);//设置连接处为弧形
    }

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

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

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

        mMaxPaintWidth = Math.max(mReachHeight, mUnReachHeight);//最大画笔宽度 取进度条 unreach和reach的最大
        //设置期望值,默认四个Paddding一致
        int expect = mRadius * 2 + mMaxPaintWidth + getPaddingLeft() + getPaddingRight();

        //用系统提供的方法来测量用户给出的数据
        int width = resolveSize(expect, widthMeasureSpec);
        int height = resolveSize(expect, heightMeasureSpec);

        int realWidth = Math.min(width, height);//取二者最小值,防止宽高取得不一致、

        mRadius = (realWidth - getPaddingLeft() - getPaddingRight() - mMaxPaintWidth) / 2;

        setMeasuredDimension(realWidth, realWidth);
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        String text = getProgress() + "%";
        float textWidth = mPaint.measureText(text);
        float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;

        canvas.save();
        canvas.translate(getPaddingLeft() + mMaxPaintWidth / 2, getPaddingTop() + mMaxPaintWidth / 2);
        mPaint.setStyle(Paint.Style.STROKE);
        //Draw unReachBar
        mPaint.setColor(mUnReachColor);
        mPaint.setStrokeWidth(mUnReachHeight);
        canvas.drawCircle(mRadius,mRadius,mRadius,mPaint);
        //Draw ReachBar
        mPaint.setColor(mReachColor);
        mPaint.setStrokeWidth(mReachHeight);
        float sweepAngle = getProgress()*1.0f/getMax()*360; //算出弧度
        canvas.drawArc(new RectF(0,0,mRadius*2,mRadius*2),0,sweepAngle,false,mPaint);
        //Draw Text
        mPaint.setColor(mTextColor);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawText(text,mRadius-textWidth/2,mRadius-textHeight,mPaint);
        canvas.restore();
    }
}

然后再activity_main.xml中可以使用了:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:chase="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <com.chase.view.horizonalPrograssBarWithPrograss
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:padding="5dp"
            android:progress="50" />

        <com.chase.view.horizonalPrograssBarWithPrograss
        android:id="@+id/prograss2"
        chase:text_color="#44ff0000"
        chase:prograss_unreach_color="#000"
        chase:prograss_reach_height="5dp"
        chase:prograss_unreach_height="5dp"
        chase:text_size="10sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:padding="5dp"
        android:progress="0" />

        <com.chase.view.RoundPrograssBarWithPrograss
        android:id="@+id/prograss3"
        chase:text_color="#44ff0000"
        chase:radius="25dp"
        chase:prograss_reach_height="5dp"
        chase:prograss_unreach_height="5dp"
        chase:text_size="10sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:padding="5dp"
        android:progress="30" />

        <com.chase.view.RoundPrograssBarWithPrograss
            android:id="@+id/prograss4"
            chase:text_color="#44ff0000"
            chase:radius="80dp"
            chase:prograss_reach_height="5dp"
            chase:prograss_unreach_height="5dp"
            chase:text_size="10sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:padding="5dp"
            android:progress="20" />


    </LinearLayout>
</ScrollView>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值