1. 程式人生 > >自定義View實現圓形水波進度條(上)

自定義View實現圓形水波進度條(上)

來源:伯樂線上專欄作者 - Code4Android

連結:http://android.jobbole.com/84776/

每次聽到某大牛談論自定義View,頓時敬佩之心,如滔滔江水連綿不絕,心想我什麼時候能有如此境界,好了,心動不如行動,於是我開始了自定義View之路,雖然過程有坎坷,但是結果我還是挺滿意的。我知道大牛還遙不可及,但是我已使出洪荒之力。此篇部落格記錄本人初入自定義View之路。

既然是初出茅廬,自然是按部就班的進行,先來一張效果圖

本文章所寫專案程式碼的GitHub連結

https://github.com/xiehui999/CustomBall

自定義屬性

自定義屬性,就是在資原始檔夾下values目錄中建立一個attrs.xml檔案,

檔案結構如下所示,atrr標籤就是我們要自定義的一些屬性,name就是自定義屬性的名字,那麼format是做什麼的呢?

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="">
        <attr name="centerText" format=""></attr>
        <attr name=" ">
            <enum name=""  value=" "></enum>
            <enum name="" value=" "></enum>
        </attr>
    </declare-styleable>
</resources>

format是屬性對應的值的型別,有十個值

  • enm 列舉型別,例 android:orientation=”vertical” 此值有horizontal,和 vertical

  • dimension 尺寸值

  • color 顏色值,例 android:textColor = “#00FF00”

  • boolean 布林值,true or false

  • flag 位或運算

  • float 浮點型

  • fraction 百分數,

  • reference 參考某一資源ID,例 android:background = “@drawable/ic_launcher”

  • string 字串型別

  • integer 整型值

知道了這些值得含義,就可以自定義我們自己的屬性了,對於這個進度條,我們可以自定義圓的半徑,顏色,和圓中心文字的大小,顏色,文字,最後attrs.xml檔案為

<?xmlversion="1.0"encoding="utf-8"?>
<resources>
    <declare-styleablename="CustomBallView">
        <attrname="centerText"format="string"></attr>
        <attrname="centerTextSize"format="dimension"></attr>
        <attrname="centerTextColor"format="color"></attr>
        <attrname="ballColor"format="color"></attr>
        <attrname="ballRadius"format="dimension"></attr>
    </declare-styleable>
</resources>


佈局檔案配置相關內容

在佈局檔案要配置我們自定義的屬性,首先要自定義名稱空間,

如上圖,如果在as中名稱空間寫成http://schemas.android.com/apk/res/包名 此時as會報錯,這是gradle造成的,在eclipse中如果自定義的屬性 是不能用res-auto的 必須得替換成你自定義view所屬的包名,如果你在恰好使用的自定義屬性被做成了lib 那就只能使用res-auto了,而在android-studio裡,無論你是自己寫自定義view 還是引用的lib裡的自定義的view 都只能使用res-auto這個寫法。以前那個包名的寫法 在android-studio裡是被廢棄無法使用的

所以配置後的佈局檔案如下

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:customBallView="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.xh.customball.MainActivity"
    tools:showIn="@layout/activity_main">
 
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        android:text="Hello World!" />
 
    <com.example.xh.customball.CustomBall
        android:background="@color/colorPrimary"
        android:layout_centerInParent="true"
        android:layout_margin="10dp"
        customBallView:centerText="30%"
        customBallView:centerTextSize="28dp"
        customBallView:centerTextColor="#000000"
        customBallView:ballColor="@color/colorAccent"
        customBallView:ballRadius="30dp"
        android:layout_width="260dp"
        android:layout_height="260dp">
    </com.example.xh.customball.CustomBall>
</LinearLayout>


自定義控制元件

有了上邊的操作,接下來就開始到了真正自定義控制元件的時候了,建立一個CustomBall類繼承View類,先看構造方法,我們寫成構造方法最終呼叫三個引數的構造方法,獲取自定義屬性的值及初始化工作就在三個引數構造方法中進行。下面我先來繪製一個圓,文字畫在圓心試試手,效果如圖

當然繪製這個圖形,首先獲取我們自定義屬性值,可通過下面獲取屬性值

注意通過TypedArray 獲取屬性值後要執行typedArray.recycle();回收記憶體,防止記憶體洩漏。

/**
* 獲取自定義屬性
*/
TypedArraytypedArray=context.obtainStyledAttributes(attrs,R.styleable.customBallView);
        centerText=typedArray.getString(R.styleable.customBallView_centerText);
        Log.e("TAG","centerText"+centerText);
        centerTextSize=typedArray.getDimension(R.styleable.customBallView_centerTextSize,24f);
        centerTextColor=typedArray.getColor(R.styleable.customBallView_centerTextColor,0xFFFFFF);
        ballColor=typedArray.getColor(R.styleable.customBallView_ballColor,0xFF4081);
        radius=typedArray.getDimension(R.styleable.customBallView_ballRadius,260f);
        typedArray.recycle();


初始化畫筆

/**
     * 初始化畫筆
     */
    privatevoidinitPaint(){
        roundPaint = newPaint();
        roundPaint.setColor(ballColor);
        roundPaint.setAntiAlias(true);//抗鋸齒
        fontPaint = newPaint();
        fontPaint.setTextSize(centerTextSize);
        fontPaint.setColor(centerTextColor);
        fontPaint.setAntiAlias(true);
        fontPaint.setFakeBoldText(true);//粗體
 
    }


接下來我們先畫一個圓,先通過下面方法獲取空間本身的寬和高,然後呼叫canvas.drawCircle(width/2, height/2, radius, roundPaint);畫圓,在原點設定為控制元件中心位置,即點(width/2, height/2),半徑為radius,畫筆roundPaint,接下來繪製文字,將位子繪製在圓的中心。

width = getWidth();

height = getHeight();

如果我們通過canvas.drawText(centerText, width/2, height/2, fontPaint);繪製文字的話,發現文字並不是在中心位置,那麼我們可以做一下調整,canvas.drawText(centerText, width/2, height/2, fontPaint);先通過float textWidth = fontPaint.measureText(centerText);獲取文字的寬度,canvas.drawText(centerText, width/2-textWidth /2, height/2, fontPaint);此時文字依然不在中心,那麼此時我們研究一下文字到底是怎麼繪製的,為什麼座標試試中心了,繪製出來的效果依然有偏差呢。

要關注文字繪製的話,FontMetrics這個類是必須要知道的因為它的作用是測量文字,它裡面呢就定義了top,ascent,descent,bottom,leading五個成員變數其他什麼也沒有。先看原始碼

publicstaticclassFontMetrics{
        /**
         * The maximum distance above the baseline for the tallest glyph in
         * the font at a given text size.
         */
        publicfloat  top;
        /**
         * The recommended distance above the baseline for singled spaced text.
         */
        publicfloat  ascent;
        /**
         * The recommended distance below the baseline for singled spaced text.
         */
        publicfloat  descent;
        /**
         * The maximum distance below the baseline for the lowest glyph in
         * the font at a given text size.
         */
        publicfloat  bottom;
        /**
         * The recommended additional space to add between lines of text.
         */
        publicfloat  leading;
    }


這個類是Paint的靜態內部類,通過註釋我們就知道了每個變數的含義,為了更生動的理解這幾個變數含義,我們通過下面的一張圖來分別解釋每個變數的含義

  • Baseline(基線) 在Android中,文字的繪製都是從Baseline處開始的

  • ascent(上坡度)Baseline往上至文字“最高處”的距離我們稱之為ascent,

  • descent(下坡度)Baseline往下至文字“最低處”的距離我們稱之為descent(下坡度)

  • leading(行間距)表示上一行文字的descent到該行文字的ascent之間的距離

  • top 對於ascent上面還有一部分內邊距,內邊距加上ascent即為top值

  • bottom descent和內邊距的加上descent距離值得注意的一點,Baseline上方的值為負,下方的值為正如下圖文字30%的ascent,descent,top,bottom。

通過上面的分析,我們就得出了將文字繪製中心的程式碼如下

//測量文字的寬度
floattextWidth = fontPaint.measureText(centerText);
        floatx = width / 2 - textWidth / 2;
        Paint.FontMetricsfontMetrics = fontPaint.getFontMetrics();
        floatdy = -(fontMetrics.descent + fontMetrics.ascent) / 2;
        floaty = height / 2  +dy;
        canvas.drawText(centerText,x,y,fontPaint);


至此這個簡單自定義的View基本實現,此時我改了佈局配置檔案為寬高

android:layout_width="wrap_content"

android:layout_height="wrap_content"

或者

android:layout_width="match_parent"

android:layout_height="match_parent"

Oh my God,為什麼效果是一樣的啊,此時再回到自定義的類,我們發現我們沒有實現onMeasure裡面測量的程式碼,接下來讓我們實現onMeasure操作,如下

@Override
    protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
        //測量模式
        intwidthMode = MeasureSpec.getMode(widthMeasureSpec);
        intheightMode = MeasureSpec.getMode(heightMeasureSpec);
        //測量規格大小
        intwidthSize = MeasureSpec.getSize(widthMeasureSpec);
        intheightSize = MeasureSpec.getSize(heightMeasureSpec);
 
        intwidth;
        intheight;
        if(widthMode == MeasureSpec.EXACTLY){
            width=widthSize;
        }elseif(widthMode == MeasureSpec.AT_MOST){
            width=(int)Math.min(widthSize,radius*2);
        }else{
 
            width=windowWidth;
        }
        if(heightMode == MeasureSpec.EXACTLY){
            height=heightSize;
        }elseif(heightMode == MeasureSpec.AT_MOST){
            height=(int)Math.min(heightSize,radius*2);
        }else{
            height=windowHeight;
        }
        setMeasuredDimension(width,height);
    }


測量主要依靠MeasureSpec,MeasureSpec(測量規格)是一個32位的int資料.其中高2位代表SpecMode即某種測量模式,低32位為SpecSize代表在該模式下的規格大小,測量模式有三種

  • EXACTLY 確切的,在佈局檔案中設定的寬高是固定的,此時測量大小就是我們設定的寬高

  • AT_MOST 至多,不能超出

  • UNSPECIFIED 未指定

MeasureSpec的詳細解釋

http://blog.csdn.net/lfdfhl/article/details/50880382

通過上面的分析,繪製此圖形的完整程式碼為 點選檢視

https://github.com/xiehui999/CustomBall/blob/master/app/src/main/java/com/example/xh/customball/CustomBall.java

控制元件升級

上面我們已經實現了圓形和文字的繪製,那麼接下來,我們先開始實現中心新進度的更新繪製。先看效果圖