Android自定義View詳解

聲明:作者原創(chuàng),轉(zhuǎn)載注明出處。

作者:帥氣陳吃蘋果

一、Android控件架構(gòu)

開發(fā)過程中,當(dāng)Android原生控件無法滿足項(xiàng)目需求時(shí),就需要我們自己自定義View來實(shí)現(xiàn)了。

Android中的每個(gè)控件都會(huì)在界面上占據(jù)一個(gè)矩形區(qū)域,控件大致分成兩類,ViewGroup控件和View控件。ViewGroup控件往往作為容器,它可以包含多個(gè)View控件,并管理被其包含的子控件。通過ViewGroup,整個(gè)界面上的控件形成了一個(gè)樹形結(jié)構(gòu),如下圖,上層控件負(fù)責(zé)下層子控件的測(cè)量和繪制,并傳遞交互事件。

Android控件樹:

這里寫圖片描述

二、自定義View

View類是Android中的一個(gè)超類,ViewGroup類也是繼承自View類。View中通常有下面這些比較重要的回調(diào)方法:

  1. onFinishInflate():從XML加載組件后回調(diào);
  2. onSizeChanged():組件大小改變時(shí)回調(diào);
  3. onMeasure():對(duì)組件的大小進(jìn)行測(cè)量;
  4. onLayout():對(duì)子控件進(jìn)行排列,確定子控件的位置;
  5. onDraw():繪制子控件的內(nèi)容;
  6. onTouchEvent():監(jiān)聽組件的觸摸事件;

其中,最常用的是onMeasure()、onLayout()、onDraw()onTouchEvent()。

通常情況下,自定義控件可以分為下面三類:

  1. 繼承現(xiàn)有控件,對(duì)其進(jìn)行擴(kuò)展;
  2. 組合不同的控件來實(shí)現(xiàn)新的控件;
  3. 重寫View實(shí)現(xiàn)全新的控件;

當(dāng)我們想要實(shí)現(xiàn)一個(gè)自定義View時(shí),需要思考它是屬于哪一類的自定義控件,并且思考實(shí)現(xiàn)這樣的控件,需要用到View中的哪些回調(diào)方法。

比如,當(dāng)你只是想改變TextView的外觀時(shí),它就是屬于第一類,那么你可以新建一個(gè)類,繼承TextView,并重寫onDraw()onMeasure()等方法。

在實(shí)現(xiàn)自定義控件的時(shí)候,我們往往需要對(duì)控件進(jìn)行測(cè)量、繪制、和布局等操作。

我們可以把自定義控件這個(gè)過程想象成畫畫,當(dāng)我們畫一個(gè)東西時(shí),要想:這個(gè)東西要畫多大,這個(gè)東西要怎么畫,這個(gè)東西要畫在哪個(gè)位置。

(一)View的測(cè)量

Android系統(tǒng)給我們提供了一個(gè)專門幫助我們測(cè)量View的類,MeasureSpec,它是一個(gè)32位的int值,其中高2位為測(cè)量的模式,低30位為測(cè)量的大小。測(cè)量模式又分為EXACTLY、AT_MOST、UNSPECIFIED。

  1. EXACTLY:
    精確值模式,當(dāng)我們將控件的layout_width屬性或layout_height屬性指定為具體數(shù)值或者match_parent時(shí),就代表著該控件的測(cè)量模式是EXACTLY模式。
  2. AT_MOST:
    最大值模式,當(dāng)控件的layout_width屬性或 layout_height屬性指定為wrap_content時(shí),控件的大小就會(huì)隨著內(nèi)容的變化而變化,內(nèi)容有多大,它就占據(jù)多大空間。
  3. UNSPECIFIED:
    不指定測(cè)量模式,View想多大就多大,常用于自定義View,

View類默認(rèn)的onMeasure()方法只支持EXACTLY模式,所以在實(shí)現(xiàn)自定義控件的時(shí)候,如果沒有重寫onMeasure()方法,那么在使用的時(shí)候必須指定控件的具體數(shù)值,而不能指定為wrap_content,否則會(huì)出現(xiàn)問題,后面的例子中會(huì)具體介紹出現(xiàn)的問題。

(二)View的繪制

既然是畫畫,那么就需要用到一些工具,比如畫布、畫筆、顏料等,而在Android中,每一個(gè)View都有一個(gè)用于繪圖的畫布,即Canvas,用于繪制圖形的畫筆是Paint,而顏料則是我們自己定義的一些顏色屬性,只要給畫筆設(shè)置顏色屬性,就相當(dāng)于擁有任意顏色任意數(shù)量的畫筆了。

Canvas的常用屬性

1)填充顏色

  drawARGB(int a, int r, int g, int b)

  drawColor(int color)

  drawRGB(int r, int g, int b)

  drawColor(int color, PorterDuff.Mode mode)

2)繪制幾何圖形

   canvas.drawArc() :繪制一個(gè)扇形或者一段弧形

   canvas.drawCircle():繪制一個(gè)圓形

   canvas.drawOval():繪制一個(gè)橢圓

   canvas.drawLine():繪制一條線

   canvas.drawPoint():繪制一個(gè)點(diǎn)

   canvas.drawRect():繪制一個(gè)矩形

   canvas.drawRoundRect():繪制一個(gè)圓角矩形

   canvas.drawVertices():繪制一個(gè)頂點(diǎn)

   cnavas.drawPath():繪制一條路徑

3)圖片

   canvas.drawBitmap() :繪制位圖,裝載畫布

   canvas.drawPicture():繪制圖片

4)文本

    canvas.drawText():繪制文字

Paint的常用屬性

    Paint.setAntiAlias():抗鋸齒
    Paint.setStyle():設(shè)置畫筆風(fēng)格
    Paint.setStrokeWidth():設(shè)置畫筆寬度
    Paint.setColor():設(shè)置畫筆顏色
    Paint.setTextSize():設(shè)置畫筆繪制文本的文字大小

三、實(shí)例

介紹了自定義View的分類、流程、常用回調(diào)方法以及需要用到的工具,接下來,偉大的畫家要開始畫畫了。

我們要實(shí)現(xiàn)的是一個(gè)圓形進(jìn)度條控件,中間的文本動(dòng)態(tài)顯示當(dāng)前進(jìn)度值,如圖:

這里寫圖片描述

按照前面說的思路來做,首先,這樣一個(gè)控件,好像沒有原生控件可以直接利用,并且它不是ViewGroup,所以,它屬于第3類自定義控件。

接下來思考可能要用到的回調(diào)方法,onDraw()方法是必須的,因?yàn)檫@是一個(gè)全新的控件。onMeasure()需要用到嗎?現(xiàn)在可能還不知道,等到具體實(shí)現(xiàn)的時(shí)候或許就知道了。

然后分析應(yīng)該怎么樣繪制這樣一個(gè)控件。它由一個(gè)圓環(huán)、一個(gè)圓弧、一段文本組成,那么很明顯了,需要三個(gè)步驟:

  1. 繪制圓環(huán);
  2. 繪制圓?。匆淹瓿蛇M(jìn)度的部分);
  3. 繪制文本;

可以動(dòng)手編碼了。新建一個(gè)類CircleProgressView 繼承自View,實(shí)現(xiàn)構(gòu)造方法,并重寫onDraw()、onMeasure()方法,如下:

public class CircleProgressView extends View {

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

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
   
    @Override
    protected void onDraw(Canvas canvas) {

    }
}

這個(gè)控件擁有一些屬性,比如圓環(huán)的寬度、圓環(huán)的顏色、進(jìn)度條的顏色、文字大小、文字顏色、當(dāng)前進(jìn)度值、控件的寬高等,還需要用到畫筆,所以可以在這個(gè)類中添加下列屬性:

//圓環(huán)的寬度
private int ringWidth;

//圓環(huán)填充顏色
private int ringColor;

//進(jìn)度條填充顏色
private int progressColor;

//文字大小
private int textSize;

//文字顏色
private int textColor;

//畫筆
private Paint mPaint;

//當(dāng)前進(jìn)度值
private int progressSize;

//控件本身的寬度
private int mWidth;

雖然有了這些屬性,但是當(dāng)我們?cè)谑褂眠@個(gè)控件的時(shí)候,該怎么樣給這些屬性賦值呢?這就需要用到構(gòu)造方法了。三個(gè)構(gòu)造方法的使用如下:

    /**
    * 當(dāng)在java代碼中直接new一個(gè)控件實(shí)例的時(shí)候,調(diào)用此構(gòu)造方法
    */
    public CircleProgressView(Context context) {
        super(context);
    }

    /**
    * 當(dāng)在XML文件中直接使用該控件的時(shí)候,
    * 并且該控件由自定義屬性的時(shí)候,調(diào)用此構(gòu)造方法
    */
    public CircleProgressView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    /**
    * 系統(tǒng)默認(rèn)只調(diào)用前兩個(gè)構(gòu)造方法,
    * 此方法通常是我們?cè)谇皟蓚€(gè)構(gòu)造方法中調(diào)用,
    * 用于獲取自定義屬性的值
    */
    public CircleProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

要使用自定義屬性,需要在res資源目錄的values目錄下創(chuàng)建一個(gè)attrs.xml的屬性定義文件,并添加屬性代碼:

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <declare-styleable name="CircleProgressView" >
        <attr name="ringWidth" format="dimension"/>
        <attr name="ringColor" format="color" />
        <attr name="progressColor" format="color"/>
        <attr name="textSize" format="dimension"/>
        <attr name="textColor" format="color"/>
        <attr name="progressSize" format="integer" />
        
    </declare-styleable>
    
</resources>

自定義屬性設(shè)置好了,像前面說的,可以在第三個(gè)構(gòu)造方法中獲取這些自定義屬性的值,由于不需要在java代碼中實(shí)例化創(chuàng)建該控件,可以在前兩個(gè)構(gòu)造方法中調(diào)用第三個(gè)構(gòu)造方法。通常也把一些初始化操作放在構(gòu)造方法中,比如我們這里用到的畫筆Paint的初始化,代碼如下:

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

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

    public CircleProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        //獲取屬性值
        TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.CircleProgressView);
        //第二個(gè)參數(shù)是當(dāng)我們沒有給這個(gè)控件對(duì)應(yīng)的屬性賦值時(shí)采用的默認(rèn)值
        ringWidth = (int) ta.getDimension(R.styleable.CircleProgressView_ringWidth,20);
        ringColor = ta.getColor(R.styleable.CircleProgressView_ringColor, Color.GRAY);
        progressColor = ta.getColor(R.styleable.CircleProgressView_progressColor,Color.BLUE);
        textSize = (int) ta.getDimension(R.styleable.CircleProgressView_textSize,60);
        textColor = ta.getColor(R.styleable.CircleProgressView_textColor,Color.BLACK);
        progressSize = ta.getInteger(R.styleable.CircleProgressView_progressSize,60);
        //回收TypedArray
        ta.recycle();
    }

控件本身的寬度可以在onMeasure()方法中獲取到:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getMeasuredWidth();
    }

準(zhǔn)備工作做好了,接下來開始畫畫了。

繪制圓環(huán):

繪制圓環(huán)可以看成是繪制一個(gè)邊框?qū)挾容^大的空心圓,首先獲取到圓心的坐標(biāo)和半徑,設(shè)置畫筆的屬性后,繪制圓:

    //獲取圓心坐標(biāo)及半徑
    float circleX = mWidth / 2;
    float circleY = mWidth / 2;
    float radius = mWidth / 2 - ringWidth / 2;
    //繪制圓環(huán)
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(ringWidth);
    mPaint.setColor(ringColor);
    canvas.drawCircle(circleX,circleY,radius,mPaint);

繪制圓?。?/strong>

    //繪制圓弧,填充進(jìn)度
    //RectF用于構(gòu)造一個(gè)矩形區(qū)域,作為傳入的橢圓對(duì)象
    RectF oval = new RectF(ringWidth / 2,ringWidth / 2,mWidth - ringWidth / 2,mWidth - ringWidth / 2);
    mPaint.setColor(progressColor);
    //drawArc()方法參數(shù):
    //1、圓弧所在的橢圓對(duì)象
    //2、圓弧的起始角度
    //3、圓弧的角度
    //4、是否顯示半徑連線
    //5、繪制時(shí)采用的畫筆
    canvas.drawArc(oval,0,progressSize * 360 / 100,false,mPaint);

繪制文本:

    //繪制文本
    String progressText = progressSize + "%";
    //設(shè)置畫筆顏色和文字大小
    mPaint.setColor(textColor);
    mPaint.setTextSize(textSize);
    //重置畫筆寬度,因?yàn)榍懊胬L制圓環(huán)和圓弧時(shí)用到的畫筆寬度不一樣
    mPaint.setStrokeWidth(0);
    //構(gòu)造一個(gè)矩形區(qū)域,用于放置文本
    Rect bound = new Rect();
    mPaint.getTextBounds(progressText,0,progressText.length(),bound);
    canvas.drawText(progressText,mWidth / 2 - bound.width() / 2,mWidth / 2 + bound.height() / 2,mPaint);

由于進(jìn)度值是動(dòng)態(tài)的,所以我們需要提供一個(gè)方法,用于傳入進(jìn)度值progressSize,在CircleProgressView類中添加如下方法:

    /**
     * 獲取進(jìn)度值
     * @return
     */
    public int getProgressSize() {
        return progressSize;
    }

    /**
     * 設(shè)置進(jìn)度值
     * @param progressSize
     */
    public void setProgressSize(int progressSize) {
        this.progressSize = progressSize;
    }

自定義控件的實(shí)現(xiàn)工作已經(jīng)完成了,接下來是如何使用我們的控件。

activity_main.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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <edu.sqchen.circleprogressview.CircleProgressView
        android:id="@+id/circle_progress_view"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_centerInParent="true"
        app:ringColor="@android:color/darker_gray"
        app:progressColor="@color/blue"
        app:ringWidth="10dp"
        app:textColor="@color/blue"
        />

</RelativeLayout>

MainActivity.class:

public class MainActivity extends AppCompatActivity {

    //自定義控件
    private CircleProgressView mProgressView;

    //已完成進(jìn)度
    private int totalProgress;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mProgressView = (CircleProgressView) findViewById(R.id.circle_progress_view);
        //已完成70%
        totalProgress = 70;
        //創(chuàng)建一個(gè)子線程,在子線程中做耗時(shí)操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                //設(shè)置進(jìn)度值從0開始變化
                mProgressView.setProgressSize(0);
                for(int i = 0; i < totalProgress; i++) {
                    mProgressView.setProgressSize(i + 1);
                    SystemClock.sleep(30);
                    //在子線程中刷新、重繪控件
                    mProgressView.postInvalidate();
                }
            }
        }).start();

    }
}

自定義圓形進(jìn)度條已經(jīng)實(shí)現(xiàn)了,效果如下:

[圖片上傳失敗...(image-ae6aeb-1513606785650)]

前面我們?cè)?code>activity_main.xml中給控件的寬高設(shè)置為具體指,那么假如要設(shè)置為wrap_content呢?修改activity_main.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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <edu.sqchen.circleprogressview.CircleProgressView
        android:id="@+id/circle_progress_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        app:ringColor="@android:color/darker_gray"
        app:progressColor="@color/blue"
        app:ringWidth="10dp"
        app:textColor="@color/blue"
        />

</RelativeLayout>

效果:

這里寫圖片描述

可以看到,控件的大小占據(jù)了整個(gè)屏幕,顯然不是我們想要的效果。

原因在于,我們雖然重寫了onMeasure()方法,但是我們沒有對(duì)測(cè)量模式AT_MOST作處理,它就會(huì)變成這樣的效果,這也是前面所說的可能出現(xiàn)的問題,解決方式自然是重寫onMeasure()方法,在里面對(duì)寬高指定為wrap_content時(shí)的處理,代碼如下:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
        mWidth = getMeasuredWidth();
    }

    /**
     * 對(duì)寬度進(jìn)行判斷
     * @param widthMeasureSpec
     * @return
     */
    private int measureWidth(int widthMeasureSpec) {
        int resultWidth = 0;
        //獲取設(shè)置的測(cè)量模式和大小
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);

        //如果是精確值模式,則寬度等于用戶設(shè)置的寬度
        if(specMode == MeasureSpec.EXACTLY) {
            resultWidth = specSize;
        } else {
            //否則,設(shè)置默認(rèn)值為400個(gè)像素,如果是最大值模式,則取用戶設(shè)置的值和默認(rèn)值中較小的一個(gè)
            resultWidth = 400;
            if(specMode == MeasureSpec.AT_MOST) {
                resultWidth = Math.min(resultWidth,specSize);
            }
        }
        return resultWidth;
    }

    /**
     * 對(duì)高度進(jìn)行判斷
     * @param heightMeasureSpec
     * @return
     */
    private int measureHeight(int heightMeasureSpec) {
        int resultHeight = 0;
        int specMode = MeasureSpec.getMode(heightMeasureSpec);
        int specSize = MeasureSpec.getSize(heightMeasureSpec);

        if(specMode == MeasureSpec.EXACTLY) {
            resultHeight = specSize;
        } else {
            resultHeight = 400;
            if(specMode == MeasureSpec.AT_MOST) {
                resultHeight = Math.min(resultHeight,specSize);
            }
        }
        return resultHeight;
    }

可以看到,我們對(duì)傳遞進(jìn)來的寬高進(jìn)行測(cè)量模式的判斷,如果是精確值模式,則采用用戶設(shè)置的具體寬度,否則判斷是否是最大值模式,則取用戶設(shè)置的值(即wrap_content)和默認(rèn)值400像素中較小的那個(gè)值。

需要注意的是,java代碼中設(shè)置的大小單位是像素,而XML文件中設(shè)置的大小單位是dp,根據(jù)手機(jī)分辨率不同而有所差異,可將像素轉(zhuǎn)換成dp,則可自適應(yīng)不同屏幕,統(tǒng)一大小。

現(xiàn)在再回過頭看前面的繪制流程、回調(diào)方法、繪制工具,對(duì)自定義View的整個(gè)過程就比較熟悉了。

CircleProgressView.class完整代碼:

package edu.sqchen.circleprogressview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by Administrator on 2017/6/1.
 */

public class CircleProgressView extends View {

    //圓環(huán)的寬度
    private int ringWidth;

    //圓環(huán)填充顏色
    private int ringColor;

    //進(jìn)度條填充顏色
    private int progressColor;

    //文字大小
    private int textSize;

    //文字顏色
    private int textColor;

    //畫筆
    private Paint mPaint;

    //當(dāng)前進(jìn)度值
    private int progressSize;

    //控件本身的寬度
    private int mWidth;

    /**
     *
     * @param context
     */
    public CircleProgressView(Context context) {
        this(context,null);
    }

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

    public CircleProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        //獲取屬性值
        TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.CircleProgressView);
        //第二個(gè)參數(shù)是當(dāng)我們沒有給這個(gè)控件對(duì)應(yīng)的屬性賦值時(shí)采用的默認(rèn)值
        ringWidth = (int) ta.getDimension(R.styleable.CircleProgressView_ringWidth,20);
        ringColor = ta.getColor(R.styleable.CircleProgressView_ringColor, Color.GRAY);
        progressColor = ta.getColor(R.styleable.CircleProgressView_progressColor,Color.BLUE);
        textSize = (int) ta.getDimension(R.styleable.CircleProgressView_textSize,60);
        textColor = ta.getColor(R.styleable.CircleProgressView_textColor,Color.BLACK);
        progressSize = ta.getInteger(R.styleable.CircleProgressView_progressSize,60);
        //回收TypedArray
        ta.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
        mWidth = getMeasuredWidth();
    }

    /**
     * 對(duì)寬度進(jìn)行判斷
     * @param widthMeasureSpec
     * @return
     */
    private int measureWidth(int widthMeasureSpec) {
        int resultWidth = 0;
        //獲取設(shè)置的測(cè)量模式和大小
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);

        //如果是精確值模式,則寬度等于用戶設(shè)置的寬度
        if(specMode == MeasureSpec.EXACTLY) {
            resultWidth = specSize;
        } else {
            //否則,設(shè)置默認(rèn)值為400個(gè)像素,如果是最大值模式,則取用戶設(shè)置的值和默認(rèn)值中較小的一個(gè)
            resultWidth = 400;
            if(specMode == MeasureSpec.AT_MOST) {
                resultWidth = Math.min(resultWidth,specSize);
            }
        }
        return resultWidth;
    }

    /**
     * 對(duì)高度進(jìn)行判斷
     * @param heightMeasureSpec
     * @return
     */
    private int measureHeight(int heightMeasureSpec) {
        int resultHeight = 0;
        int specMode = MeasureSpec.getMode(heightMeasureSpec);
        int specSize = MeasureSpec.getSize(heightMeasureSpec);

        if(specMode == MeasureSpec.EXACTLY) {
            resultHeight = specSize;
        } else {
            resultHeight = 400;
            if(specMode == MeasureSpec.AT_MOST) {
                resultHeight = Math.min(resultHeight,specSize);
            }
        }
        return resultHeight;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //獲取圓心坐標(biāo)及半徑
        float circleX = mWidth / 2;
        float circleY = mWidth / 2;
        float radius = mWidth / 2 - ringWidth / 2;
        //繪制圓環(huán)
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(ringWidth);
        mPaint.setColor(ringColor);
        canvas.drawCircle(circleX,circleY,radius,mPaint);

        //繪制圓弧,填充進(jìn)度
        //RectF用于構(gòu)造一個(gè)矩形區(qū)域,作為傳入的橢圓對(duì)象
        RectF oval = new RectF(ringWidth / 2,ringWidth / 2,mWidth - ringWidth / 2,mWidth - ringWidth / 2);
        mPaint.setColor(progressColor);
        //drawArc()方法參數(shù):
        //1、圓弧所在的橢圓對(duì)象
        //2、圓弧的起始角度
        //3、圓弧的角度
        //4、是否顯示半徑連線
        //5、繪制時(shí)采用的畫筆
        canvas.drawArc(oval,0,progressSize * 360 / 100,false,mPaint);

        //繪制文本
        String progressText = progressSize + "%";
        //設(shè)置畫筆顏色和文字大小
        mPaint.setColor(textColor);
        mPaint.setTextSize(textSize);
        //重置畫筆寬度,因?yàn)榍懊胬L制圓環(huán)和圓弧時(shí)用到的畫筆寬度不一樣
        mPaint.setStrokeWidth(0);
        //構(gòu)造一個(gè)矩形區(qū)域,用于放置文本
        Rect bound = new Rect();
        mPaint.getTextBounds(progressText,0,progressText.length(),bound);
        canvas.drawText(progressText,mWidth / 2 - bound.width() / 2,mWidth / 2 + bound.height() / 2,mPaint);
    }

    /**
     * 獲取進(jìn)度值
     * @return
     */
    public int getProgressSize() {
        return progressSize;
    }

    /**
     * 設(shè)置進(jìn)度值
     * @param progressSize
     */
    public void setProgressSize(int progressSize) {
        this.progressSize = progressSize;
    }
}

源碼下載地址:Github下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容