通過圓形載入View了解自定義View

這是自定義View的第一篇文章,通過制作簡單的自定義View來了解自定義View的流程。
自定義View是Android學(xué)習(xí)和開發(fā)中必不可少的一部分。通過自定義View我們可以制作豐富絢麗的控件,自定義View主要有三種方式,具體如下:

  1. 繼承已有的View,來擴(kuò)展我們的View
  2. 組合多個(gè)View來實(shí)現(xiàn)一個(gè)復(fù)合的View
  3. 完全重寫View,來實(shí)現(xiàn)制作全新的控件

這里,我們講第三種方法來了解自定義View的流程。

自定義View主要依賴的方法

自定義VIew中,我們主要重寫onMeasure,onDraw這兩種方法來展現(xiàn)一個(gè)View。

onMeasure:主要工作是對(duì)我們要繪制的View進(jìn)行測量,因?yàn)椴贿M(jìn)行測量的話,系統(tǒng)不知道要繪制的View有多大,無法繪制出我們需要的樣子。
onDraw: 主要工作就是繪制我們需要的圖形,這個(gè)是自定義View中定制性最強(qiáng)也是最主要的工作。

下面我們先了解一下這兩個(gè)方法具體能做什么,然后我們通過一個(gè)實(shí)例來學(xué)習(xí)一下具體的用法。

onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

我們從上面的方法可以看出onMeasure 方法中有兩個(gè)參數(shù)widthMeasureSpec,heightMeasureSpec這兩個(gè)參數(shù)每一個(gè)參數(shù)中都包含了測量值的大小和測量的模式。
測量模式有一下三種:

  1. EXACTLY
    當(dāng)我們把控layout_width或者layout_height屬性設(shè)為match_parent或者是個(gè)100dp那樣的精確值時(shí)就會(huì)用這種測量模式。
  2. AT_MOST
    即最大值模式,當(dāng)控件的layout_width屬性或者layout_height指定為wrap_content時(shí),空間大小一般隨著控件的子控件或者內(nèi)容的變化而變化,這是控件的尺寸只要不超過父控件允許的最大尺寸就行了。
  3. UNSPECIFIED
    這個(gè)是我們按照自身的想法,想繪制多大就繪制多大,沒有任何限制。

具體的做法一般是,我們先根據(jù)參數(shù),得到具體的測量模式與測量值,在根據(jù)測試的模式不同,計(jì)算不同的寬度和高度。最后通過setMeasuredDimension(int measuredWidth,int measuredHeight)將我們計(jì)算過的寬和高設(shè)置進(jìn)去,完成測量工作。

onDraw

onDraw(Canvas canvas)是我們展現(xiàn)自定義View的主要方法,他的參數(shù)是一個(gè)canvas也就是說一個(gè)畫布,為了繪制圖案,我們有了畫布以外,我們還需要一個(gè)畫筆Paint。這個(gè)畫筆來決定你畫圖案的顏色,線條的粗細(xì),是否抗鋸齒,圖案的風(fēng)格等等。而畫什么圖案就交由canvas對(duì)象,調(diào)用canvas.drawXXX()來實(shí)現(xiàn)想要的圖案,具體的文檔,參見Canvas官方文檔

自制圓形載入View

上面說了這么多,都是在YY,我們具體通過一個(gè)例子來走一遍自定義View的流程。效果如下:


|center

首先我們新建一個(gè)CircleLoadingView.java文件。該類繼承View,生成構(gòu)造器,顯式調(diào)用父類的構(gòu)造器,并初始化我們的Paint對(duì)象,覆寫onMeasure,onDraw方法,實(shí)現(xiàn)自定義View.

第一步——初始化

在構(gòu)造方法中,我們調(diào)用自己寫的initView方法來初始化Paint對(duì)象

private void initView(){
        paint = new Paint();
        //設(shè)置畫筆的顏色
        paint.setColor(circleColor);
        //設(shè)置抗鋸齒,讓圖像更清晰
        paint.setAntiAlias(true);
        //設(shè)置畫筆的風(fēng)格,有三種屬性FILL,STROLE,FILL_AND_STROKE,我們不需要填充,所以設(shè)置為STROKE
        paint.setStyle(Paint.Style.STROKE);
        //設(shè)置畫筆的粗細(xì)
        paint.setStrokeWidth(circleStrokewidth);
    }

第二步——onMeasure

接下來,我們需要測量我們的View的大小,重寫onMeasure()方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //設(shè)置一個(gè)默認(rèn)的寬和高,AT_MOST模式需要
        int result = 0;
        //通過MeasureSpec.getMode與getSize方法獲取寬和高的測量方式與測量大小
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //保存最后的測量值,優(yōu)化代碼的話可以不用這個(gè)變量的。
        int width = 0,height = 0;
        //對(duì)測量模式進(jìn)行判斷,如果是EXACTLY的話則最后的測量值就是系統(tǒng)幫我們測量的結(jié)果。
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        }else{
            //如果是UNSPECIFIED 則使用我們的默認(rèn)值作為最后的測量值
            result = 300;
            width = result;
            //如果是AT_MOST 則就要用系統(tǒng)測量結(jié)果與我們默認(rèn)結(jié)果取最小值來決定最后的測量結(jié)果
            if (widthMode == MeasureSpec.AT_MOST) {
                width = Math.min(result,widthSize);
            }
        }
        //高度和寬度的過程是一致的。
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        }else {
            result = 300;
            height = result;
            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(result, heightSize);
            }
        }
        //把我們最后的寬和高設(shè)置進(jìn)去
        setMeasuredDimension(width,height);

    }

這就是onMeasure方法的代碼,可以看出來,這個(gè)都是可以放到其他地方復(fù)用的模板代碼。

第三步——onDraw

我們基本已經(jīng)完成了80%的工作了,接下來只需要重寫onDraw()繪制我們需要的一個(gè)弧形就可以了。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //決定我們的弧形外接的矩形的寬和高
        float wdistance = (float)(wlength * 0.6);
        float hdistance = (float)(hlength * 0.6);
        //定義外接矩形
        RectF rectF = new RectF(
                wlength / 2 - wdistance / 2,
                hlength / 2 - hdistance / 2,
                wlength / 2 + wdistance / 2,
                hlength / 2 + hdistance / 2);
         //畫弧形
        canvas.drawArc(
                rectF,//外接矩形
                270,//起始角度
                (float)(240),//劃過的度數(shù)
                false,//是否是扇形
                paint//畫筆
        );

    }

看了上面的代碼,可能有些糊涂,因?yàn)槔锩娑嗔撕芏鄾]有提到的變量。首先的變量就是wlength,與hlength這個(gè)指的是我們onMeasure測量以后的寬和高,我們保存下來。在onMeasure方法中,我們加入下面代碼。

 wlength = width;
 hlength = height;

同時(shí)我們定義了外接矩形的寬和高就是我們View的0.6倍,上,下, 左,右各留了0.2倍的內(nèi)邊距。然后通過new Rectf()方法來生成矩形對(duì)象,4個(gè)參數(shù)分別為矩形的左邊距Y軸的距離(也就是說X軸的坐標(biāo)),上邊距X軸的距離(也就是說Y軸的坐標(biāo)),右邊距Y軸的距離(也就是說X軸的坐標(biāo)),下邊距Y軸的距離(也就是說Y軸的坐標(biāo))。具體見下圖


|center

canvas 實(shí)際上就是這個(gè)坐標(biāo)軸。
定義好外接矩形之后,我們開始調(diào)用drawArc方法開始繪制弧形,這個(gè)方法接受5個(gè)參數(shù),第二個(gè)參數(shù)是弧起始的角度,這個(gè)接受一個(gè)整數(shù)代表角度,他是這樣確定起始的角度的。以我們的手表的3點(diǎn)開始,順時(shí)針轉(zhuǎn)過我們定義的角度,這時(shí)的位置就是我們開始的位置,比如我們現(xiàn)在定義的是270,那就是手表3點(diǎn)的位置轉(zhuǎn)過270度為我們弧形開始的位置。就是12點(diǎn)的位置。然后第三個(gè)參數(shù),代表弧形劃過的角度。也是順時(shí)針。第四個(gè)參數(shù)則代表是否用扇形,我們這里不用,也就是說只是一個(gè)兩個(gè)端點(diǎn)不連接圓心的弧,第五個(gè)是我們初始化過的paint。這樣我們的自定義的弧形就出來了。
我們就可以把我們的控件放到xml中,當(dāng)成普通的view去引用了。直接看代碼:

<com.example.byhieg.circleloadingview.CircleLoadingView
        android:id="@+id/loading"
        android:layout_width="100dp"
        android:layout_height="100dp"
      android:layout_centerInParent="true"
        />
這里的引用View一定要用全名。我們就可以直接運(yùn)行程序。

第四步——?jiǎng)悠饋?/h4>

這個(gè)弧形其實(shí)并沒有卵用,不能動(dòng)。我們接下來讓他動(dòng)起來,這里我們就用繪圖自帶的方法postInvalidate()來實(shí)現(xiàn)重繪。要讓一個(gè)圖像動(dòng)起來最簡單的辦法就是讓他在每一個(gè)坐標(biāo)點(diǎn)就繪制一下,然后讓他在每個(gè)坐標(biāo)點(diǎn)都出現(xiàn)一次就實(shí)現(xiàn)了動(dòng)畫的效果。就像小時(shí)候有那種很多頁的漫畫書,我們快速翻閱漫畫書就感覺漫畫書中的漫畫動(dòng)起來了,道理是一樣的。
那這次我們只需要在onDraw方法中,不斷修改弧形的第二個(gè)參數(shù),讓他每次不同就可以了。具體的我們看代碼:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float wdistance = (float)(wlength * 0.6);
        float hdistance = (float)(hlength * 0.6);

        RectF rectF = new RectF(
                wlength / 2 - wdistance / 2,
                hlength / 2 - hdistance / 2,
                wlength / 2 + wdistance / 2,
                hlength / 2 + hdistance / 2);
        canvas.drawArc(
                rectF,
                //每次重繪,起始的角度就會(huì)多5度。
                270 + 5 * i,
                (float)(240),
                false,
                paint
        );
        i++;
        //調(diào)用重繪,來實(shí)現(xiàn)圖像不斷繪制
        postInvalidate();
    }

這樣,我們?cè)俅芜\(yùn)行程序的時(shí)候,我們的弧形就動(dòng)起來。當(dāng)我們?cè)诮佑|動(dòng)畫概念的時(shí)候,又會(huì)發(fā)現(xiàn)有很多方法實(shí)現(xiàn)弧形旋轉(zhuǎn)。

第五步——完善

前面4步,我們已經(jīng)實(shí)現(xiàn)了簡單的自定義View,并且有一個(gè)可觀的效果。但是我們還要完善一下,比如我們可以在XML中指定弧形的顏色,弧形的粗細(xì)。這個(gè)時(shí)候,我們就需要在values文件下新建一個(gè)attrs.xml。在里面制定我們的自定義屬性,然后在我們的View文件中讀取這些屬性。
我們先看一下attrs.xml中,我們的代碼:

<resources>
    <declare-styleable name="CircleLoadingView">
        <attr name="circleColor" format="color" />
        <attr name="circleStrokewidth" format="float" />
    </declare-styleable>
</resources>

這里,我們就定義了2個(gè)屬性,一個(gè)是弧形的顏色,一個(gè)是弧形的粗細(xì),格式分別是color和float。然后我們?cè)趘iew中的initView代碼中,獲取那些屬性。代碼如下:

TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.CircleLoadingView);
        circleStrokewidth = ta.getFloat(R.styleable.CircleLoadingView_circleStrokewidth, 0);
        circleColor = ta.getColor(R.styleable.CircleLoadingView_circleColor, 0);

然后我們就可以在我們的xml中引用這些屬性了。

<com.example.byhieg.circleloadingview.CircleLoadingView
        android:id="@+id/loading"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_centerInParent="true"
        circleview:circleColor="@android:color/holo_blue_dark"
        circleview:circleStrokewidth="15"/>

這里的circleview是要在根布局的設(shè)置中聲明的 xmlns:circleview="http://schemas.android.com/apk/res-auto"
因?yàn)?,這個(gè)主要出現(xiàn)在加載過程中,所以他需要一個(gè)方法來讓他顯示和隱藏。我們?cè)赩iew的文件中,設(shè)置一個(gè)方法叫做setViewVisable
當(dāng)為true的時(shí)候,就可以顯示,當(dāng)為false就可以隱藏。這樣,這個(gè)自定義View就比較完善了。
隱藏方法代碼如下:

public void setViewVisable(boolean choose) {
        if (choose) {
            this.setVisibility(View.VISIBLE);
        }else{
            this.setVisibility(View.GONE);
        }
    }

PS

這個(gè)小控件的源碼我放到網(wǎng)上了,大家可以對(duì)照參考下
CircleLoadingView的源碼

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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