安卓实习生之——自定义ProgressBar

现有需求,根据服务端获取到的数据,进行百分比展示,如下图所示:在这里插入图片描述
若要使用自定义View来实现,有几个步骤,第一步,创建资源文件attrs.xml:首先添加

<resources xmlns:tools="http://schemas.android.com/tools">
    <attr name="borderRadius" format="dimension" />
    <attr name="borderColor" format="color" />
    <attr name="type">
        <enum name="circle" value="0" />
        <enum name="round" value="1" />
    </attr>

介绍一下这几个参数的作用:
dimension: 对应的是尺寸,可以是px、dp等
color:对应的是0xXXXXXX之类的16进制数,如:0xFF00CC56
一般都是带有透明度的颜色,这里的‘FF’的作用即是如此
value:value值就比较好理解了,即是对应的是原型还是方形
这是格式化参数类型,我们还要有我们所需要的参数的默认值

<declare-styleable name="HorizontalProgressBar">
    <attr name="horizontalProgressColor" format="color" />
    <attr name="horizontalUnProgressColor" format="color" />
    <attr name="horizontalProgressWidth" format="dimension" />
    <attr name="horizontalProgressHeight" format="dimension" />
    <attr name="horizontalBorderWidth" format="dimension" />
</declare-styleable>

horizontalProgressColor:绘制的正确率的颜色
horizontalUnProgressColor:未绘制的颜色,如上方进度条的灰色部分颜色
horizontalProgressWidth:绘制的长度等
horizontalProgressHeight:绘制的部分高度
horizontalBorderWidth:进度条边框宽度
注:这些都是默认参数,颜色或者高度、边框宽度都可以通过传值进来或者自行设置
接下来就开始正式进行自定义View了,首先创建一个类,名为:HorizontalProgressBar,继承自ProgressBar,继承之后,需重写几个构造方法,一般为默认三个:

    public HorizontalProgressBar(Context context) {
        super(context);
    }
    public HorizontalProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }
    public HorizontalProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

这里我们只需要用到两个参数的构造方法,在其中调用init(context,attrs)方法
在init方法中,我们开始进行参数初始化

        int mBoardWidth;  // 进度条圆弧宽度
        final int DEFAULT_UN_PROGRESS_COLOR = 0xFFE3EDF3; // 剩下的部分颜色
        final int DEFAULT_PROGRESS_GOOD_COLOR = 0xFF00CC56; // 绘制部分的颜色
        final TypedArray typedArray = context.obtainStyledAttributes(attrs,
                R.styleable.HorizontalProgressBar); // 获取自定义的属性
        mBoardWidth = typedArray.getDimensionPixelOffset(R.styleable.HorizontalProgressBar_horizontalBorderWidth,
                DEFAULT_BOARD_WIDTH); // 获取自定义的圆弧边框
        mProHeight = typedArray.getDimensionPixelOffset(R.styleable.HorizontalProgressBar_horizontalProgressHeight,
                DEFAULT_PROGRESS_HEIGHT); // 获取自定义的进度条的高度
        mProColor = typedArray.getColor(R.styleable.HorizontalProgressBar_horizontalProgressColor,
                DEFAULT_PROGRESS_GOOD_COLOR); // 获取自定义的进度条的默认颜色
        mUnProColor = typedArray.getColor(R.styleable.HorizontalProgressBar_horizontalUnProgressColor,
                DEFAULT_UN_PROGRESS_COLOR); // 获取剩余部分的进度条默认颜色

上面的参数定义好理解有注释在,但是,这里要着重了解一下

final TypedArray typedArray = context.obtainStyledAttributes(attrs,
                R.styleable.HorizontalProgressBar); // 获取自定义的属性

这行代码,这里的看到有一个没见过的方法,context.obtainStyledAttributes方法,我们点进去看,如下

  public TypedArray obtainStyledAttributes(AttributeSet set,
                @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
            return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr, defStyleRes);
        }

这里的第二个参数即是@StyleableRes int[] attrs,这儿怎么理解,@StyleableRes这个注解的意思即在R文件中找到对应的styleable资源类型,这儿可能会有一点疑问,它是怎么找到我们所需要的属性的呢?这个问题我用几行代码示例来表示:

  <declare-styleable name="ArcProgressBar">
        <attr name="arcBorderWidth" format="dimension" />
        <attr name="arcUnProgressColor" format="color" />
        <attr name="arcProgressColor" format="color" />
        <attr name="arcRadius" format="dimension" />
        <attr name="arcDegree" format="integer" />
        <attr name="arcStartAngleBase" format="integer" />
        <attr name="arcCapRound" format="boolean" />
    </declare-styleable>

    <!-- 同步练习作答问题的正确率横向进度条参数 -->

    <declare-styleable name="HorizontalProgressBar">
        <attr name="horizontalProgressColor" format="color" />
        <attr name="horizontalUnProgressColor" format="color" />
        <attr name="horizontalProgressWidth" format="dimension" />
        <attr name="horizontalProgressHeight" format="dimension" />
        <attr name="horizontalBorderWidth" format="dimension" />
    </declare-styleable>

如上,在attrs.xml文件中,我定义了两个自定义View的资源文件,一个是name=“ArcProgressBar” 另一个是name=“HorizontalProgressBar”,那这儿就很明显了,在context.obtainStyledAttributes方法中,第二个参数是通过在attrs.xml文件中通过name来找对应的资源的,而且注意它的返回类型,是一个数组@StyleableRes int[] attrs,数组中的值即我们定义的绘制长度,绘制颜色以及绘制线条宽度等等,现在我们只需了解返回类型为数组就行了。
好了,继续往下,刚刚的代码继续往下读,也很简单,即把定义的各个变量进行赋值即可。赋值完是不是就可以开始画矩形了?NO,有件事儿还忘了,我们还需要对画笔进行初始化,即

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);

这儿需要注意第二个参数Paint.Style.FILL_AND_STROKE
这是啥意思,这就相当于你给这个自定义的水平条形进度条设置的绘画格式,

      Paint.Style.FILL:填充内部
      Paint.Style.FILL_AND_STROKE  :填充内部和描边
      Paint.Style.STROKE  :描边

这个好理解,填充内部的画,整个画的进度条就是实心的了,所以一般我们画那种中间有内容的进度条时,一般都是STROKE参数,这个自行测试,不多讲。(这步如果忘了设置Paint的绘画格式的画,会报NPE的,,空指针)
继续往下,现在就来到了自定义View的最重要的一步了,即onMeasure()和onDraw()方法,首先,onMeasure方法,顾名思义,起到的作用即是测量作用,用来测量整个屏幕的宽度,这儿需要注意几个参数

wrap_content   --- MeasureSpec.AT_MOST
match_parent   --- MeasureSpec.EXACTLY
具体值          --- MeasureSpec.UNSPECIFIED

当我们在xml文件中设置了本个自定义view的长度是wrap_content、match_parent或者是具体的xxxdp的时候,对应的onMeasure的模式为以上三种,不过以上三种模式是否设置是可选值,可以不设置,这儿我并没有画时间了解,以后用到再了解

  // 测量函数,用来测量整个自定义的View的宽高
    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthVal = MeasureSpec.getSize(widthMeasureSpec);
        int heightVal = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(widthVal, heightVal);
    }

尽管不用设置测量模式,但是要注意的是:

setMeasuredDimension(widthVal, heightVal); 

这儿要注意,因为后面我们在绘制的时候,需要取高度和宽度,所以必须要这行代码,但是有人说这行可以不要吗?行,当然可以不要,你也可以通过super.onMeasure(widthMeasureSpec,heightMeasureSpec)调用父类的onMeasure方法来达到目的,但是在ProgressBar的源码中

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     /******** 上面有很多,咱们不看
        setMeasuredDimension(measuredWidth, measuredHeight);
    }

那么这就一目了然了,父类帮我们调用了setMeasureDimension()方法,不过一般还是写上用来提醒自己注意这些细节也是好的。
接下来就开始了我们的onDraw()方法了,首先进行画布状态保存:

        canvas.save(); // 用来保存画布状态的
        canvas.translate(getPaddingLeft(), getHeight() / 2); //移动画布到 最左边 正中间

接下来就是获取屏幕宽度了,在onMeasure方法中,我们setMeasureDimension过了,所以这儿就能直接取到对应屏幕宽度值,mProWidth = getMeasuredWidth(); // 获取屏幕长度,还记得我们的需求吗?要求从服务端获取到一个百分比,比如是40,那我们先拿到这个40

    public void setProgress(int progress) {
        if (mPaintedWidth != progress) {
            mPaintedWidth = progress;
            saveProgress = progress;
            invalidate(); //去调用onDraw()方法
        }
    }

这儿要注意,获取到新的数据之后,调用invalidate()去调用ondraw()方法,ok,回到onDraw()方法,拿到百分比之后,
mProWidth = getMeasuredWidth(); // 获取屏幕长度 mPaintedWidth = mPaintedWidth / 100 * mProWidth; // 将获取到的数据等比例放大到所需要绘制的长度
有注释也能看懂,接下来开始绘制了,这儿

            mPaint.setColor(mUnProColor);
            canvas.drawRoundRect(new RectF(left, top, mProWidth - rxRadius / 2,
                    mProHeight), rxRadius, ryRadius, mPaint);
                    // 上方的四个参数对应 左 上 右 下  即开始坐标和结束坐标,刚好给了自定义的View定了一个总体的绘制范围!
                   /***  部分代码,省略,后面会贴出源码
                    canvas.restore(); // 这个别忘了 将画布状态更新到原点,即左上角(0,0),防止绘制多个view的时候,
                    上次的结束位置为下一次的开始位置,达不到需求

上面的那个好理解,设置画笔颜色而已,但是这儿有个drawRoundRect()方法,跟进去瞧瞧,Canvas类中
在这里插入图片描述
入参好理解,上面注释已经解释过了,继续跟进super.drawRoundRect方法,进入到BaseCanvas类中的在这里插入图片描述
看了一番之后,发现还能继续跟进,最后,进入到BaseCanvas类中的drawRoundRect的重载方法中,

 public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
            @NonNull Paint paint) {
        throwIfHasHwBitmapInSwMode(paint);
        nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry,
                paint.getNativeInstance());
    }

诶,到这儿发现,又有一个paint.getNativeInstance()方法,抱着死磕的态度,我们继续跟进,到Paint方法中的getNativeInstance()方法中一探究竟

   public long getNativeInstance() {
        long newNativeShader = mShader == null ? 0 : mShader.getNativeInstance();
        if (newNativeShader != mNativeShader) {
            mNativeShader = newNativeShader;
            nSetShader(mNativePaint, mNativeShader);
        }
        long newNativeColorFilter = mColorFilter == null ? 0 : mColorFilter.getNativeInstance();
        if (newNativeColorFilter != mNativeColorFilter) {
            mNativeColorFilter = newNativeColorFilter;
            nSetColorFilter(mNativePaint, mNativeColorFilter);
        }
        return mNativePaint;
    }

哦,原来这儿的主要作用就是返回要给本地的paint的对象,刚刚我们在调用drawRoundRect方法的时候传入了一个paint对象,所以这儿是用作为这个方法的入参的。那我们基本上就搞清楚了这个方法的作用了,,这个地方返回的是一个mNativePaint参数,看名字就好理解了,一个本地的paint对象,不用过多深入。上面的drawRoundRect方法的最后一个参数就是返回一个画笔对象,那么整个这个方法的意思就都弄清楚了,那我们也就大致弄清楚如何自定义ProgressBar了…

好了,以下是页面全部源码

package com.iflytek.icola.lib_base.views;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.ColorInt;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.widget.ProgressBar;

import com.iflytek.icola.lib_base.R;


public class HorizontalProgressBar extends ProgressBar {

    private final int DEFAULT_BOARD_WIDTH = dp2px(15);
    private final int DEFAULT_PROGRESS_HEIGHT = dp2px(10);

    private Paint mPaint;
    private float mPaintedWidth; // 需要画的宽度(带颜色的)
    private int mProHeight; // 进度条的高度
    private int mProColor; // 进度条的颜色
    private int mUnProColor; // 剩余部分的进度条颜色
    float start; // 灰色进度条部分起始位置
    private int saveProgress; // 保存progress大小

    public HorizontalProgressBar(Context context) {
        super(context);
    }

    public HorizontalProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

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

    /**
     * 初始化参数
     */
    private void init(Context context, AttributeSet attrs) {
        int mBoardWidth;  // 进度条圆弧宽度
        final int DEFAULT_UN_PROGRESS_COLOR = 0xFFE3EDF3; // 剩下的部分颜色
        final int DEFAULT_PROGRESS_GOOD_COLOR = 0xFF00CC56; // 良好  60~85
        final TypedArray typedArray = context.obtainStyledAttributes(attrs,
                R.styleable.HorizontalProgressBar); // 获取自定义的属性
        mBoardWidth = typedArray.getDimensionPixelOffset(R.styleable.HorizontalProgressBar_horizontalBorderWidth,
                DEFAULT_BOARD_WIDTH); // 获取自定义的圆弧边框
        mProHeight = typedArray.getDimensionPixelOffset(R.styleable.HorizontalProgressBar_horizontalProgressHeight,
                DEFAULT_PROGRESS_HEIGHT); // 获取自定义的进度条的高度
        mProColor = typedArray.getColor(R.styleable.HorizontalProgressBar_horizontalProgressColor,
                DEFAULT_PROGRESS_GOOD_COLOR); // 获取自定义的进度条的默认颜色
        mUnProColor = typedArray.getColor(R.styleable.HorizontalProgressBar_horizontalUnProgressColor,
                DEFAULT_UN_PROGRESS_COLOR); // 获取剩余部分的进度条默认颜色
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(mBoardWidth);
        /**
         * Paint.Style.FILL:填充内部
         * Paint.Style.FILL_AND_STROKE  :填充内部和描边
         * Paint.Style.STROKE  :描边
         */
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        typedArray.recycle(); // 回收属性
    }

    // 测量函数,用来测量整个自定义的View的宽高
    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthVal = MeasureSpec.getSize(widthMeasureSpec);
        int heightVal = MeasureSpec.getSize(heightMeasureSpec);

        /**
         * 这儿也可使用super.onMeasure(widthMeasureSpec,heightMeasureSpec)
         *ProgressBar中的onMeasure()方法中有方法setMeasureDimension(widthMeasureSpec, heightMeasureSpec)方法
         * 方便自己看,直接使用下列方式执行
         * serMeasureDimension之后才能getMeasuredWidth()
         */
        setMeasuredDimension(widthVal, heightVal);
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        int mProWidth; // 进度条的宽度(总的宽度)
        canvas.save();
        canvas.translate(getPaddingLeft(), getHeight() / 2); //移动画布到 最左边 正中间
        mProWidth = getMeasuredWidth(); // 获取屏幕长度
        mPaintedWidth = mPaintedWidth / 100 * mProWidth; // 将获取到的数据等比例放大到所需要绘制的长度
        int rxRadius = 20; // x方向上的圆角半径
        int ryRadius = 20;// y方向上的圆角半径
        int left = rxRadius / 2; // 左边起始坐标
        int top = 0;  // 上边起始坐标
        if (mPaintedWidth != 0) {
            start = mPaintedWidth; // 计算灰色部分的起始位置
            // 绘制默认的进度条
            mPaint.setColor(mUnProColor);
            //设置灰色条形的进度条
            canvas.drawRoundRect(new RectF(left, top, mProWidth - rxRadius / 2,
                    mProHeight), rxRadius, ryRadius, mPaint);

            // 绘制完成率
            mPaint.setColor(mProColor);
            // 对应四个参数  左 上 右 下
            canvas.drawRoundRect(new RectF(left, top, mPaintedWidth - rxRadius / 2,
                    mProHeight), rxRadius, ryRadius, mPaint);
            canvas.restore();
            mPaintedWidth = saveProgress;
        } else {
            // 绘制默认的进度条
            mPaint.setColor(mUnProColor);
            canvas.drawRoundRect(new RectF(left, top, mProWidth - rxRadius / 2,
                    mProHeight), rxRadius, ryRadius, mPaint);
            canvas.restore();
        }
    }

    public void setProgress(int progress) {
        if (mPaintedWidth != progress) {
            mPaintedWidth = progress;
            saveProgress = progress;
            invalidate(); //去调用onDraw()方法
        }
    }

    public void setProgressColor(@ColorInt int progressColor) {
        if (mProColor != progressColor) {
            mProColor = progressColor;
            invalidate(); //去调用onDraw()方法
        }
    }

    /**
     * dp 2 px
     *
     * @param dpVal
     */
    protected int dp2px(int dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, getResources().getDisplayMetrics());
    }
}

xml文件:

  <com.iflytek.icola.lib_base.views.HorizontalProgressBar  // 这儿是你得自定义View的类名
        android:id="@+id/hpb_math_synchronous_exercise_class_analysis_right_rate"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginStart="@dimen/dimen_6"
        android:layout_marginEnd="@dimen/dimen_5"
        android:layout_toStartOf="@+id/tv_math_synchronous_exercise_class_analysis_right_rate"
        android:layout_toEndOf="@+id/rl_pg_circle"
        android:max="100"
        android:progress="40"
        app:horizontalBorderWidth="@dimen/dimen_4"
        app:horizontalProgressColor="#FF00BAFF"
        app:horizontalProgressHeight="@dimen/dimen_10"
        app:horizontalProgressWidth="@dimen/dimen_600"
        app:horizontalUnProgressColor="#FFE3EDF3" />

看着简单,但是做起来花了不少时间,慢慢来吧,这是个循序渐进的过程!对以上代码有不懂的地方,更欢迎问我,(实习生的第一次任务!) 以上!
转载请注明本片博客地址: https://blog.csdn.net/shilao9170/article/details/86681724

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值