自定义view实例:渐变色仪器表盘

渐变色的仪表盘在很对应用中应该是比较常见的,如图:
在这里插入图片描述
在绘制这个view的时候,没什么难度,主要还是颜色渐变的使用。
好,不废话,先把代码贴上,然后再一步步说明:

一 具体绘制过程

public class TemperatureProgressBar extends View {
    private int mRadius;
    private int mProgressBarWidth;
    private int mTemperatureTextSize;
    private int mTemperatureLabSize;
    private String mTemperatureLabText;
    private String mTemperatureText;
    private Paint mProgressBarBgPain;
    private Paint mProgressBarPain;
    private Paint mProgressBarTextPain;
    private Context mContext;
    private int mStartAngle;
    private int mSweetAngle;

    private Path mPathBg;
    private Path mProgressBarPath;

    /**
     * 当前进度
     */
    private int mCurrentProgress;
    /**
     * 最大角度
     */
    private int mMaxAngle;
    /**
     * 最大进度
     */
    private int mMaxProgress;

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

    public TemperatureProgressBar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initialize(context, attrs);
    }

    public TemperatureProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialize(context, attrs);
    }

    public TemperatureProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initialize(context, attrs);
    }

    private void initialize(Context context, @Nullable AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TemperatureProgressBar);
        mProgressBarWidth = (int) typedArray.getDimension(R.styleable.TemperatureProgressBar_barWidth, dp2px(15, context));
        mTemperatureTextSize = (int) typedArray.getDimension(R.styleable.TemperatureProgressBar_temperatureTextSize, dp2px(24, context));
        mTemperatureLabSize = (int) typedArray.getDimension(R.styleable.TemperatureProgressBar_temperatureLabSize, dp2px(14, context));
        mTemperatureLabText = typedArray.getString(R.styleable.TemperatureProgressBar_temperatureLabText);
        if (mTemperatureLabText == null) {
            mTemperatureLabText = "当前室温";
        }
        typedArray.recycle();
        mContext = context;
        mProgressBarBgPain = new Paint();
        mProgressBarBgPain.setAntiAlias(true);
        mProgressBarBgPain.setStrokeWidth(mProgressBarWidth);
        mProgressBarBgPain.setStyle(Paint.Style.STROKE);
        mProgressBarBgPain.setStrokeCap(Paint.Cap.ROUND);
        mProgressBarBgPain.setStrokeJoin(Paint.Join.ROUND);

        mProgressBarPain = new Paint();
        mProgressBarPain.setAntiAlias(true);
        mProgressBarPain.setStrokeWidth(mProgressBarWidth);
        mProgressBarPain.setStyle(Paint.Style.STROKE);
        mProgressBarPain.setStrokeCap(Paint.Cap.ROUND);
        mProgressBarPain.setStrokeJoin(Paint.Join.ROUND);

        mProgressBarTextPain = new Paint();
        mProgressBarTextPain.setAntiAlias(true);
        mProgressBarTextPain.setColor(Color.parseColor("#ff607df0"));

        mPathBg = new Path();
        mProgressBarPath = new Path();
        mProgressBarPath = new Path();
        mStartAngle = -240;
        mSweetAngle = 300;
        //圆弧扫过的角度即为最大角度
        mMaxAngle = mSweetAngle;
        mMaxProgress = 100;
        mTemperatureText = "0";
    }

    private int dp2px(final float dpValue, Context context) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = measureSize(heightMeasureSpec);
        int width = measureSize(widthMeasureSpec);
        setMeasuredDimension(width, height);
    }

    private int measureSize(int measureSpec) {
        int specMode = View.MeasureSpec.getMode(measureSpec);
        int specSize = View.MeasureSpec.getSize(measureSpec);
        int result = 0;
        if (specMode == View.MeasureSpec.EXACTLY) {
            result = specSize;
        } else if (specMode == View.MeasureSpec.AT_MOST) {
            result = dp2px(200, mContext);
            result = Math.min(result, specSize);
        }
        return result;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRadius = Math.max(w, h) / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(mRadius, mRadius);
        drawLab(canvas);
        drawProgressBarBg(canvas);
        drawProgress(canvas);
    }

    private void drawProgressBarBg(Canvas canvas) {
        mProgressBarBgPain.setColor(Color.parseColor("#EBEBEB"));
        mPathBg.addArc(-mRadius + mProgressBarWidth / 2, -mRadius + mProgressBarWidth / 2, mRadius - mProgressBarWidth / 2, mRadius - mProgressBarWidth / 2, mStartAngle, mSweetAngle);
        canvas.drawPath(mPathBg, mProgressBarBgPain);
    }

    private void drawProgress(Canvas canvas) {
        int colors[] = { Color.GREEN,Color.YELLOW,Color.RED};
        Matrix matrix = new Matrix();
        matrix.setRotate(70, 0, 0);
        //sweepGradient默认开始渐变角度为0度
        Shader sweepGradient = new SweepGradient(0, 0, colors, null);
        sweepGradient.setLocalMatrix(matrix);
        mProgressBarPain.setShader(sweepGradient);
        if (mCurrentProgress >mMaxProgress){
            mCurrentProgress = mMaxProgress;
        }else if (mCurrentProgress < 0){
            mCurrentProgress = 0;
        }
        int sweetAngle = mMaxAngle/mMaxProgress * mCurrentProgress;
        mProgressBarPath.addArc(-mRadius + mProgressBarWidth / 2, -mRadius + mProgressBarWidth / 2, mRadius - mProgressBarWidth / 2, mRadius - mProgressBarWidth / 2, mStartAngle, sweetAngle);
        canvas.drawPath(mProgressBarPath,mProgressBarPain);
    }

    private void drawLab(Canvas canvas) {
        if (mProgressBarTextPain != null){
            mProgressBarTextPain.setTextSize(mTemperatureTextSize);
            String temperatureText =mTemperatureText+"℃";
            //测量String的宽度,必须在设置画笔设置字体尺寸之后,否则测量到的值不准确
            float measureTextWidth = mProgressBarTextPain.measureText(temperatureText);
            mProgressBarTextPain.setColor(Color.parseColor("#607df0"));
            canvas.drawText(temperatureText, -measureTextWidth/2, mTemperatureTextSize, mProgressBarTextPain);
        }

        mProgressBarTextPain.setTextSize(mTemperatureLabSize);
        if (mTemperatureLabText != null){
            mTemperatureLabText = "当前室温";
            float labTextWidth = mProgressBarTextPain.measureText(mTemperatureLabText);
            canvas.drawText(mTemperatureLabText,-labTextWidth/2,-mTemperatureLabSize-10,mProgressBarTextPain);
        }
    }
   private void startAnimation(){
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0,mCurrentProgress);
        valueAnimator.setDuration(1000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int currentProgress = (int) animation.getAnimatedValue();
               updateProgress(currentProgress);
            }
        });
        valueAnimator.start();
    }
    private void updateProgress(int currentProgress){
        this.mCurrentProgress = currentProgress;
        invalidate();
    }

    public void setCurrentProgress(int currentProgress) {
        this.mCurrentProgress = currentProgress;
        startAnimation();
    }
}

二 attr.xml中的自定义属性:

  <declare-styleable name="TemperatureProgressBar">
        <!--进度条宽度-->
        <attr name="barWidth" format="dimension"/>
        <!--温度值字体大小-->
        <attr name="temperatureTextSize" format="dimension"/>
        <!--控件描述的字体大小-->
        <attr name="temperatureLabSize" format="dimension"/>
        <!---->
        <attr name="temperatureLabText" format="string"/>

    </declare-styleable>

三 使用
1.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SmartHomeActivity">
    <com.example.yiguozhen151.my_project.temperature.TemperatureProgressBar
        android:id="@+id/temp_progress_bar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        />
</RelativeLayout>

2.activity

/**
 * @author yiguozhen151
 */
public class SmartHomeActivity extends AppCompatActivity {
    private TemperatureProgressBar mTemperatureProgressBar;

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

    private void initialize() {
        mTemperatureProgressBar = findViewById(R.id.temp_progress_bar);
        mTemperatureProgressBar.setCurrentProgress(90);
    }
}

四 接下来详细介绍绘制过程
1.对控件的自定义属性,资源等进行初始化:

private void initialize(Context context, @Nullable AttributeSet attrs) {
//控件定义的属性初始化
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TemperatureProgressBar);
        mProgressBarWidth = (int) typedArray.getDimension(R.styleable.TemperatureProgressBar_barWidth, dp2px(15, context));
        mTemperatureTextSize = (int) typedArray.getDimension(R.styleable.TemperatureProgressBar_temperatureTextSize, dp2px(24, context));
        mTemperatureLabSize = (int) typedArray.getDimension(R.styleable.TemperatureProgressBar_temperatureLabSize, dp2px(14, context));
        mTemperatureLabText = typedArray.getString(R.styleable.TemperatureProgressBar_temperatureLabText);
        if (mTemperatureLabText == null) {
            mTemperatureLabText = "当前室温";
        }
        typedArray.recycle();
        mContext = context;
        //背景画笔初始化
        mProgressBarBgPain = new Paint();
        mProgressBarBgPain.setAntiAlias(true);
        mProgressBarBgPain.setStrokeWidth(mProgressBarWidth);
        mProgressBarBgPain.setStyle(Paint.Style.STROKE);
        //设置画笔始末端为圆滑形状
        mProgressBarBgPain.setStrokeCap(Paint.Cap.ROUND);
        mProgressBarBgPain.setStrokeJoin(Paint.Join.ROUND);
		//进度画笔初始化
        mProgressBarPain = new Paint();
        mProgressBarPain.setAntiAlias(true);
        mProgressBarPain.setStrokeWidth(mProgressBarWidth);
        mProgressBarPain.setStyle(Paint.Style.STROKE);
        mProgressBarPain.setStrokeCap(Paint.Cap.ROUND);
        mProgressBarPain.setStrokeJoin(Paint.Join.ROUND);
	
        mProgressBarTextPain = new Paint();
        mProgressBarTextPain.setAntiAlias(true);
        mProgressBarTextPain.setColor(Color.parseColor("#ff607df0"));

        mPathBg = new Path();
        mProgressBarPath = new Path();
        mProgressBarPath = new Path();
        mStartAngle = -240;
        mSweetAngle = 300;
        //圆弧扫过的角度即为最大角度
        mMaxAngle = mSweetAngle;
        mMaxProgress = 100;
        mTemperatureText = "0";

2.初始化完成之后,当然是需要进行控件的测量:

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = measureSize(heightMeasureSpec);
        int width = measureSize(widthMeasureSpec);
        setMeasuredDimension(width, height);
    }

    private int measureSize(int measureSpec) {
    //分别根据父布局传入的测量模式及测量值,确定最后的尺寸
        int specMode = View.MeasureSpec.getMode(measureSpec);
        int specSize = View.MeasureSpec.getSize(measureSpec);
        int result = 0;
        if (specMode == View.MeasureSpec.EXACTLY) {
            result = specSize;
        } else if (specMode == View.MeasureSpec.AT_MOST) {
            result = dp2px(200, mContext);
            result = Math.min(result, specSize);
        }
        return result;
    }

分别根据父布局传入的测量模式及测量值,确定最后的尺寸,再通过 setMeasuredDimension(width, height)来设置控件的宽高。

3.在onSizeChanged() 方法中最后确定尺寸:

   protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRadius = Math.max(w, h) / 2;
    }

4.接下来,是最主要的,开始绘制:

  protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //首先,将画布移动到距离左边及顶部为控件一半的位置
        canvas.translate(mRadius, mRadius);
        //绘制圆盘上的描述文字
        drawLab(canvas);
        //绘制圆盘背景
        drawProgressBarBg(canvas);
        //绘制进度
        drawProgress(canvas);
    }

1)在绘制文字的时候,需要注意的是:当需要使用画笔测量传入的String长度时之前,必须设置要求的字体大小之后再进行测量,否则测量到的值将会偏小。

  private void drawLab(Canvas canvas) {
        if (mProgressBarTextPain != null){
            mProgressBarTextPain.setTextSize(mTemperatureTextSize);
            String temperatureText =mTemperatureText+"℃";
            //测量String的宽度,必须在设置画笔设置字体尺寸之后,否则测量到的值不准确
            float measureTextWidth = mProgressBarTextPain.measureText(temperatureText);
            mProgressBarTextPain.setColor(Color.parseColor("#607df0"));
            canvas.drawText(temperatureText, -measureTextWidth/2, mTemperatureTextSize, mProgressBarTextPain);
        }

        mProgressBarTextPain.setTextSize(mTemperatureLabSize);
        if (mTemperatureLabText != null){
            mTemperatureLabText = "当前室温";
            float labTextWidth = mProgressBarTextPain.measureText(mTemperatureLabText);
            canvas.drawText(mTemperatureLabText,-labTextWidth/2,-mTemperatureLabSize-10,mProgressBarTextPain);
        }
    }

2)绘制背景圆弧:这里没有什么,主要的还是需要注意的绘制的圆弧的区域的计算,需要考虑圆弧的宽度。

 private void drawProgressBarBg(Canvas canvas) {
        mProgressBarBgPain.setColor(Color.parseColor("#EBEBEB"));
        mPathBg.addArc(-mRadius + mProgressBarWidth / 2, -mRadius + mProgressBarWidth / 2, mRadius - mProgressBarWidth / 2, mRadius - mProgressBarWidth / 2, mStartAngle, mSweetAngle);
        canvas.drawPath(mPathBg, mProgressBarBgPain);
    }

3)绘制进度:

  private void drawProgress(Canvas canvas) {
        int colors[] = { Color.GREEN,Color.YELLOW,Color.RED};
        Matrix matrix = new Matrix();
        matrix.setRotate(70, 0, 0);
        //sweepGradient默认开始渐变角度为0度
        Shader sweepGradient = new SweepGradient(0, 0, colors, null);
        sweepGradient.setLocalMatrix(matrix);
        mProgressBarPain.setShader(sweepGradient);
        if (mCurrentProgress >mMaxProgress){
            mCurrentProgress = mMaxProgress;
        }else if (mCurrentProgress < 0){
            mCurrentProgress = 0;
        }
        int sweetAngle = mMaxAngle/mMaxProgress * mCurrentProgress;
        mProgressBarPath.addArc(-mRadius + mProgressBarWidth / 2, -mRadius + mProgressBarWidth / 2, mRadius - mProgressBarWidth / 2, mRadius - mProgressBarWidth / 2, mStartAngle, sweetAngle);
        canvas.drawPath(mProgressBarPath,mProgressBarPain);
    }

这个是今天最主要的:如何实现颜色的渐变
1)首先,我们发现:Pain里面又一个方法 Pain.setShader(),以前基本没怎么使用过这个方法,百度一下原来是设置画笔颜色渐变的;好了,我们已经找到方法了,接下来是如何使用的问题;
2)查看源码,Pain.setShader()方法中要求传入一个Shader类。而Shader下有三个常用的子类:SweepGradient,LinearGradient,RadialGradient
嗯?Gradient?想想这个是不是有点眼熟?我们再会想一下,我们在使用shape设置背景图,控件颜色渐变的时候,是不是使用过这个:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <gradient
        android:angle="0"
        android:endColor="#FFC54E"
        android:startColor="#FF9326"
        android:type="linear" />
    <corners
        android:bottomLeftRadius="25dp"
        android:bottomRightRadius="25dp"
        android:topLeftRadius="25dp"
        android:topRightRadius="25dp" />
</shape>

其中的“type”也就是我们这里使用的。所以,这里就一目了然了。接下来我们可以来了解一下这几个类的用法;
LinearGradient:

public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile)
/**
* Create a shader that draws a linear gradient along a line.
*
* @param x0 The x-coordinate for the start of the gradient line
* @param y0 The y-coordinate for the start of the gradient line
* @param x1 The x-coordinate for the end of the gradient line
* @param y1 The y-coordinate for the end of the gradient line
* @param color0 The color at the start of the gradient line.
* @param color1 The color at the end of the gradient line.
* @param tile The Shader tiling mode
*/

x0,y0,x1,y1是起始位置和渐变的结束位置,color0,color1是渐变颜色,最后一个参数表示绘制模式:

Shader.TileMode有3种参数可供选择,分别为CLAMP、REPEAT和MIRROR:

[1] CLAMP的作用是如果渲染器超出原始边界范围,则会复制边缘颜色对超出范围的区域进行着色
[2] REPEAT的作用是在横向和纵向上以平铺的形式重复渲染位图
[3] MIRROR的作用是在横向和纵向上以镜像的方式重复渲染位图

public LinearGradient (float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile);
/**
* Create a shader that draws a linear gradient along a line.
*
* @param x0 The x-coordinate for the start of the gradient line
* @param y0 The y-coordinate for the start of the gradient line
* @param x1 The x-coordinate for the end of the gradient line
* @param y1 The y-coordinate for the end of the gradient line
* @param colors The colors to be distributed along the gradient line
* @param positions May be null. The relative positions [0…1] of
* each corresponding color in the colors array. If this is null,
* the the colors are distributed evenly along the gradient line.
* @param tile The Shader tiling mode
*/

x0,y0,x1,y1 参数和上面一样,tile和上面一样。
colors表示渐变的颜色数组;
positions指定颜色数组的相对位置,同时,position的取值范围[0,1],作用是指定某个位置的颜色值,如果传null,渐变就线性变化。

SweepGradient:

    /**
     * A Shader that draws a sweep gradient around a center point.
     *
     * @param cx       The x-coordinate of the center
     * @param cy       The y-coordinate of the center
     * @param colors   The colors to be distributed between around the center.
     *                 There must be at least 2 colors in the array.
     * @param positions May be NULL. The relative position of
     *                 each corresponding color in the colors array, beginning
     *                 with 0 and ending with 1.0. If the values are not
     *                 monotonic, the drawing may produce unexpected results.
     *                 If positions is NULL, then the colors are automatically
     *                 spaced evenly.
     */
    public SweepGradient(float cx, float cy,
            @NonNull @ColorInt int colors[], @Nullable float positions[]) {
        if (colors.length < 2) {
            throw new IllegalArgumentException("needs >= 2 number of colors");
        }
        if (positions != null && colors.length != positions.length) {
            throw new IllegalArgumentException(
                    "color and position arrays must be of equal length");
        }
        mType = TYPE_COLORS_AND_POSITIONS;
        mCx = cx;
        mCy = cy;
        mColors = colors.clone();
        mPositions = positions != null ? positions.clone() : null;
    }

    /**
     * A Shader that draws a sweep gradient around a center point.
     *
     * @param cx       The x-coordinate of the center
     * @param cy       The y-coordinate of the center
     * @param color0   The color to use at the start of the sweep
     * @param color1   The color to use at the end of the sweep
     */
    public SweepGradient(float cx, float cy, @ColorInt int color0, @ColorInt int color1) {
        mType = TYPE_COLOR_START_AND_COLOR_END;
        mCx = cx;
        mCy = cy;
        mColor0 = color0;
        mColor1 = color1;
        mColors = null;
        mPositions = null;
    }

这个是SweepGradient源码中的构造器,其中:
cx,cy,圆的中心坐标。
color0,color1渐变颜色
colors渐变颜色数组
positions 颜色位置,范围[0,1],
positions的特殊应用:SweepGradient 无法指定颜色从某个角度到某个角度,但是可以利用positons指定颜色位置实现相应弧度显示相应颜色。另外,若positions为null则表示线性渐变,如果不为空可以改变渐变中某些颜色的位置

还有一点需要注意的是:SweepGradient默认开始渐变的角度是0度,所以需要根据需要对渐变的角度进行切换。这里,就需要用到矩阵Marix了,如:

 Matrix matrix = new Matrix();
        matrix.setRotate(70, 0, 0);
        //sweepGradient默认开始渐变角度为0度
        Shader sweepGradient = new SweepGradient(0, 0, colors, null);
        sweepGradient.setLocalMatrix(matrix);

首先创建单位矩阵,然后对矩阵进行旋转变换,最后通过setLocalMatrix(matrix)方法进行矩阵变换。

至此,完成了基本绘制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

易小四

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值