1. 程式人生 > 其它 >安卓儀表盤自定義View

安卓儀表盤自定義View

技術標籤:安卓開發androidandroid儀表盤安卓儀表盤view安卓自定義view儀表盤android儀表盤自定義

先上一張效果圖:
在這裡插入圖片描述
先分析實現方案:
1.自定義屬性,比如漸變色的兩個色值、儀表盤下面的字、中間的刻度值等等。
2.重寫onMeasure()方法設定儀表盤的大小。(由於這是一個圓但是下方還有一個圓角矩形所以需要高度比寬度大那麼一丟丟)
3.繪製外層圓弧 固定顏色。
4.繪製漸變圓弧。
5.繪製下方圓角矩形(有兩個矩形一個純白色大的矩形,一個漸變色小的矩形)
6.繪製背景文字。
7.繪製中間的數值和百分號。
8.加上動畫 讓值和圓弧動起來。

1.自定義屬性:(可以根據自己的新要求去定義)

public DashboardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DashboardView);
        startColor = typedArray.getColor(R.styleable.DashboardView_start_color, Color.parseColor("#FC9A4F"));
        endColor = typedArray.getColor(R.styleable.DashboardView_end_color, Color.parseColor("#EF7038"));
        outerColor = typedArray.getColor(R.styleable.DashboardView_outer_color, Color.parseColor("#D9D9D9"));
        circularValue = typedArray.getInt(R.styleable.DashboardView_circular_value, 0);
        text = typedArray.getString(R.styleable.DashboardView_text_content);
        //圓弧的大小
        paintSize = (int) typedArray.getDimension(R.styleable.DashboardView_circular_size,SizeTransformationUtils.dip2px(context, 13));
        typedArray.recycle();
    }

2.重寫onMeasure()方法設定儀表盤的大小。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = MeasureSpec.getSize(widthMeasureSpec);
        height = MeasureSpec.getSize(heightMeasureSpec);
        if (width > height) {
            width = height;
        } else {
            height = width;
        }
        setMeasuredDimension(width + paintSize, height + 2 * paintSize);
    }

3.繪製外層圓弧 固定顏色。

private void drawOuterCircular(Canvas canvas) {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStrokeWidth(paintSize);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(outerColor);
        canvas.drawArc(paintSize / 2, paintSize / 2, height + paintSize / 2, height + paintSize / 2, startAngle + 90, 360 - 2 * startAngle, false, paint);
    }

4.繪製漸變圓弧。

private void drawInnerCircular(Canvas canvas) {
        Paint paint = new Paint();
        paint.setStrokeWidth(paintSize);
        paint.setStyle(Paint.Style.STROKE);
        //設定成圓頭
        paint.setStrokeCap(Paint.Cap.ROUND);
        Shader shader = new SweepGradient((height + paintSize) / 2, (height + paintSize) / 2, startColor, endColor);
        //相當於開始漸變的角度從6點鐘方向開始漸變,預設是3點鐘方向開始的。
        Matrix matrix = new Matrix();
        matrix.setRotate(90, getMeasuredWidth() / 2, getMeasuredHeight() / 2);
        shader.setLocalMatrix(matrix);
        paint.setShader(shader);
        canvas.drawArc(paintSize / 2, paintSize / 2, height + paintSize / 2, height + paintSize / 2, startAngle + 90, totalAngle / total * circularValue, false, paint);
    }

這裡用到了兩個不常用的玩意:
1.設定畫筆的畫帽的樣子 預設是直角 因為我們的圓弧需要圓頭所以設定如下:

paint.setStrokeCap(Paint.Cap.ROUND);

2.設定漸變shader 安卓中實現該類的實現類有好幾個我們用到的正好是SweepGradient這個實現類,效果就是從3點鐘方向掃描漸變:

Shader shader = new SweepGradient((height + paintSize) / 2, (height + paintSize) / 2, startColor, endColor);
paint.setShader(shader);

由於我們需要從6點鐘的方向開始漸變所以

Matrix matrix = new Matrix();
        matrix.setRotate(90, getMeasuredWidth() / 2, getMeasuredHeight() / 2);
        shader.setLocalMatrix(matrix);

5.繪製下方圓角矩形(有兩個矩形一個純白色大的矩形,一個漸變色小的矩形)

 private void drawTextBackGround(Canvas canvas) {
        //繪製白色背景
        Paint paint = new Paint();
        paint.setColor(Color.WHITE);
        canvas.drawRoundRect(whiteRect, paintSize, paintSize, paint);
        //設定漸變
        Shader shader = new LinearGradient(width / 2 - rect.width() / 2 + paintSize / 2 + paintSize * 0.38f,
                height - rect.height() / 5 * 2 + paintSize * 0.38f,
                width / 2 + rect.width() / 2 + paintSize / 2 - paintSize * 0.38f,
                height + rect.height() / 5 * 3 - paintSize * 0.38f, startColor, endColor, Shader.TileMode.CLAMP);
        paint.setShader(shader);
        //繪製漸變色背景
        gradientsRect = new RectF(width / 2 - rect.width() / 2 + paintSize / 2 + paintSize * 0.38f,
                height - rect.height() / 5 * 2 + paintSize * 0.38f,
                width / 2 + rect.width() / 2 + paintSize / 2 - paintSize * 0.38f,
                height + rect.height() / 5 * 3 - paintSize * 0.38f);
        canvas.drawRoundRect(gradientsRect, paintSize, paintSize, paint);
    }

你看這裡又用到了Shader 這個類 但是我們這次用的是LinearGradient實現類 效果是從左往右漸變。

6.繪製背景文字。

private void drawContentText(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setTextSize(SizeTransformationUtils.sptopx(context, 13));
        Rect rect = new Rect(0, 0, paintSize * 6, (int) (paintSize * 2.53f));
        paint.getTextBounds(text, 0, text.length(), rect);
        //此處Y值應該是基線的位置
        canvas.drawText(text, width / 2 - rect.width() / 2 + paintSize / 2, (float) (height - rect.height() / 2.75 + paintSize), paint);
    }

7.繪製中間的數值和百分號。

private void drawCircularValue(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(Color.parseColor("#333333"));
        paint.setTextSize(SizeTransformationUtils.sptopx(context, 32));
        Rect rectValue = new Rect();
        Rect rect = new Rect();
        String value = circularValue + "";
        String str = "%";
        paint.getTextBounds(value, 0, value.length(), rectValue);
        paint.getTextBounds(str, 0, str.length(), rect);
        //此處Y值應該是基線的位置
        canvas.drawText(value, width / 2 - (rectValue.width() / 2 + rect.width() / 2) + paintSize / 2, height / 2 + paintSize, paint);
        paint.setTextSize(SizeTransformationUtils.sptopx(context, 12));
        //繪製百分號
        canvas.drawText(str, width / 2 + rectValue.width() / 2, height / 2 + paintSize, paint);
    }

8.加上動畫 讓值和圓弧動起來。

private void startAnimator() {
        ValueAnimator animator = ValueAnimator.ofInt(0, circularValue);
        animator.setDuration(3000);
        animator.setInterpolator(new DecelerateInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                circularValue = (int) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();
    }

9.向外部提供方法:

/**

  • 設定總值和當前值(給外部呼叫的)
  • @param total
  • @param circularValue
    */
    public void setTotalAndValue(int total, int circularValue) {
    this.total = total;
    this.circularValue = circularValue;
    startAnimator();
    }

/**

  • 設定漸變色的顏色值
  • @param startColor
  • @param endColor
    */
    public void setColor(int startColor, int endColor) {
    this.startColor = startColor;
    this.endColor = endColor;
    invalidate();
    }
    以下貼上全部程式碼:
    DashboardView.class
package com.example.customview;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;

import androidx.annotation.Nullable;

import com.example.toollibrary.SizeTransformationUtils;

/**
 * 儀表盤自定義View
 * <p>
 * 1.自定義屬性 1.外層不變的圓弧顏色 2.動態會改變的圓弧顏色 3.下方的模組名稱 4.內部數值。
 * 3.測量大小,把View設定成正方形
 * 3.繪製外面的圓弧。
 * 4.繪製漸變的圓弧。
 * 5.繪製下方文字和背景。
 * 6.繪製內部數值和百分號
 */
public class DashboardView extends View {
    private int startColor;
    private int endColor;
    private int outerColor;
    //下面的文字
    private String text;
    //圓弧中間的數值
    private int circularValue;
    //view的寬高
    private int width;
    private int height;

    //畫筆大小
    private int paintSize;
    private Context context;
    private RectF gradientsRect;
    private RectF whiteRect;
    private RectF rect;
    private float startAngle, totalAngle;
    private int total = 100;

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

    public DashboardView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DashboardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DashboardView);
        startColor = typedArray.getColor(R.styleable.DashboardView_start_color, Color.parseColor("#FC9A4F"));
        endColor = typedArray.getColor(R.styleable.DashboardView_end_color, Color.parseColor("#EF7038"));
        outerColor = typedArray.getColor(R.styleable.DashboardView_outer_color, Color.parseColor("#D9D9D9"));
        circularValue = typedArray.getInt(R.styleable.DashboardView_circular_value, 0);
        text = typedArray.getString(R.styleable.DashboardView_text_content);
        paintSize = (int) typedArray.getDimension(R.styleable.DashboardView_circular_size,SizeTransformationUtils.dip2px(context, 13));
        typedArray.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = MeasureSpec.getSize(widthMeasureSpec);
        height = MeasureSpec.getSize(heightMeasureSpec);
        if (width > height) {
            width = height;
        } else {
            height = width;
        }
        setMeasuredDimension(width + paintSize, height + 2 * paintSize);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        rect = new RectF(0, 0, paintSize * 6, paintSize * 2.53f);
        whiteRect = new RectF(width / 2 - rect.width() / 2 + paintSize / 2, height - rect.height() / 5 * 2, width / 2 + rect.width() / 2 + paintSize / 2, height + rect.height() / 5 * 3);
        startAngle = (float) Math.toDegrees(Math.sin((whiteRect.right - whiteRect.left) / 2 / (height / 2)));
        totalAngle = 360 - (float) Math.toDegrees(Math.sin((whiteRect.right - whiteRect.left) / 2 / (height / 2))) * 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawOuterCircular(canvas);
        drawInnerCircular(canvas);
        drawTextBackGround(canvas);
        drawContentText(canvas);
        drawCircularValue(canvas);
    }

    /**
     * 繪製中間的值
     *
     * @param canvas
     */
    private void drawCircularValue(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(Color.parseColor("#333333"));
        paint.setTextSize(SizeTransformationUtils.sptopx(context, 32));
        Rect rectValue = new Rect();
        Rect rect = new Rect();
        String value = circularValue + "";
        String str = "%";
        paint.getTextBounds(value, 0, value.length(), rectValue);
        paint.getTextBounds(str, 0, str.length(), rect);
        //此處Y值應該是基線的位置
        canvas.drawText(value, width / 2 - (rectValue.width() / 2 + rect.width() / 2) + paintSize / 2, height / 2 + paintSize, paint);
        paint.setTextSize(SizeTransformationUtils.sptopx(context, 12));
        //繪製百分號
        canvas.drawText(str, width / 2 + rectValue.width() / 2, height / 2 + paintSize, paint);
    }

    /**
     * 繪製文字圓角背景
     *
     * @param canvas
     */
    private void drawTextBackGround(Canvas canvas) {
        //繪製白色背景
        Paint paint = new Paint();
        paint.setColor(Color.WHITE);
        canvas.drawRoundRect(whiteRect, paintSize, paintSize, paint);
        //設定漸變
        Shader shader = new LinearGradient(width / 2 - rect.width() / 2 + paintSize / 2 + paintSize * 0.38f,
                height - rect.height() / 5 * 2 + paintSize * 0.38f,
                width / 2 + rect.width() / 2 + paintSize / 2 - paintSize * 0.38f,
                height + rect.height() / 5 * 3 - paintSize * 0.38f, startColor, endColor, Shader.TileMode.CLAMP);
        paint.setShader(shader);
        //繪製漸變色背景
        gradientsRect = new RectF(width / 2 - rect.width() / 2 + paintSize / 2 + paintSize * 0.38f,
                height - rect.height() / 5 * 2 + paintSize * 0.38f,
                width / 2 + rect.width() / 2 + paintSize / 2 - paintSize * 0.38f,
                height + rect.height() / 5 * 3 - paintSize * 0.38f);
        canvas.drawRoundRect(gradientsRect, paintSize, paintSize, paint);
    }

    /**
     * 繪製下面的文字
     *
     * @param canvas
     */
    private void drawContentText(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setTextSize(SizeTransformationUtils.sptopx(context, 13));
        Rect rect = new Rect(0, 0, paintSize * 6, (int) (paintSize * 2.53f));
        paint.getTextBounds(text, 0, text.length(), rect);
        //此處Y值應該是基線的位置
        canvas.drawText(text, width / 2 - rect.width() / 2 + paintSize / 2, (float) (height - rect.height() / 2.75 + paintSize), paint);
    }

    /**
     * 繪製內圓弧
     *
     * @param canvas
     */
    private void drawInnerCircular(Canvas canvas) {
        Paint paint = new Paint();
        paint.setStrokeWidth(paintSize);
        paint.setStyle(Paint.Style.STROKE);
        //設定成圓頭
        paint.setStrokeCap(Paint.Cap.ROUND);
        Shader shader = new SweepGradient((height + paintSize) / 2, (height + paintSize) / 2, startColor, endColor);
        //相當於開始漸變的角度從6點鐘方向開始漸變,預設是3點鐘方向開始的。
        Matrix matrix = new Matrix();
        matrix.setRotate(90, getMeasuredWidth() / 2, getMeasuredHeight() / 2);
        shader.setLocalMatrix(matrix);
        paint.setShader(shader);
        canvas.drawArc(paintSize / 2, paintSize / 2, height + paintSize / 2, height + paintSize / 2, startAngle + 90, totalAngle / total * circularValue, false, paint);
    }

    /**
     * 繪製外圓弧(給外部呼叫的)
     *
     * @param canvas
     */
    private void drawOuterCircular(Canvas canvas) {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStrokeWidth(paintSize);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(outerColor);
        canvas.drawArc(paintSize / 2, paintSize / 2, height + paintSize / 2, height + paintSize / 2, startAngle + 90, 360 - 2 * startAngle, false, paint);
    }

    /**
     * 設定總值和當前值(給外部呼叫的)
     *
     * @param total
     * @param circularValue
     */
    public void setTotalAndValue(int total, int circularValue) {
        this.total = total;
        this.circularValue = circularValue;
        startAnimator();
    }

    /**
     * 設定漸變色的顏色值
     *
     * @param startColor
     * @param endColor
     */
    public void setColor(int startColor, int endColor) {
        this.startColor = startColor;
        this.endColor = endColor;
        invalidate();
    }


    /**
     * 開啟動畫
     */
    private void startAnimator() {
        ValueAnimator animator = ValueAnimator.ofInt(0, circularValue);
        animator.setDuration(3000);
        animator.setInterpolator(new DecelerateInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                circularValue = (int) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();
    }
}

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="DashboardView">
        <attr name="outer_color" format="color" />
        <attr name="start_color" format="color" />
        <attr name="end_color" format="color" />
        <attr name="text_content" format="string" />
        <attr name="circular_value" format="integer" />
        <attr name="circular_size" format="dimension" />
    </declare-styleable>
</resources>

使用程式碼:xml

<com.example.customview.DashboardView
                            android:id="@+id/dv_memory"
                            android:layout_width="0dp"
                            android:layout_height="118dp"
                            android:layout_gravity="center"
                            android:layout_weight="1"
                            app:circular_value="97"
                            app:end_color="#EF7038"
                            app:outer_color="#D9D9D9"
                            app:start_color="#FC9A4F"
                            app:text_content="執行記憶體" />

java程式碼:

dvMemory.setTotalAndValue(100,80);

就完成了儀表盤的繪製和使用。