Android技能樹 — View小結

前言

最近年末了,打算把本身的Android知識都整理一下。java

Android技能樹系列:android

Android基礎知識git

Android技能樹 — 動畫小結github

Android技能樹 — View小結面試

Android技能樹 — Activity小結算法

Android技能樹 — View事件體系小結canvas

Android技能樹 — Android存儲路徑及IO操做小結數組

Android技能樹 — 多進程相關小結bash

Android技能樹 — Drawable小結數據結構

Android技能樹 — Fragment整體小結

數據結構基礎知識

Android技能樹 — 數組,鏈表,散列表基礎小結

Android技能樹 — 樹基礎知識小結(一)

算法基礎知識

Android技能樹 — 排序算法基礎小結

此次是相對View作個小結,主要是View的工做原理,繪製流程等。爲何要總結這塊,由於平時自定義View的狀況多多少少都會遇到,若是能深入瞭解這塊知識,對自定義View的掌握才能更透徹。有些人可能會說那我確定不會的,我也不用看這個總結文章了,不要緊,我此次寫的很簡單,基本你們都能理解。看完後,你們應該都會本身寫效果不復雜的自定義View和自定義ViewGroup。

PS: 非廣告。我自己View的相關知識也是之前從其餘地方學到的。我比較推薦這塊內容看(Android開發藝術探索 和 扔物線的View相關內容。因此文中有些的知識點也會引用這二塊地方。)

以下圖所示:我主要是整理了這些相關知識:

腦圖下載連接

View小結


咱們能夠看大分類:

咱們知道一個View要繪製好,是要有三步的(我估計百分之99.9的人都知道這三步): measure測量,layout肯定位置,而後draw畫出來。因此我此次也是主要這三步來講明的。而你們可能看到這裏有一個額外的ViewRoot的知識點,主要是給前面的三步作個補充知識。

ViewRoot(補充知識)

ps:不看其實問題也不大,不想了解的直接看本文的主要的measure,layout,draw三步曲。

ViewRoot字面意思是否是讓你感受是整個ViewTree的根節點。錯!ViewRoot不是View,它的實現類是ViewRootImpl,它是DecorViewWindowManager之間的紐帶。因此ViewRoot更恰當來講是DecorView的「管理者」。

(PS:下次面試官問你ViewRoot是啥,你可別說是ViewTree的根節點。哈哈。)

因此這時候既然開始整個界面要繪製了。明顯就是ViewRoot開始發起調用方法,畢竟「管理者」麼。因此View的繪製流程是從ViewRootperformTraversals方法開始的。因此performTraversals方法依次調用performMeasure,performLayoutperformDraw三個方法。由於這三個方法及後面的方法調用都差很少,咱們以performMeasure爲例,performMeasure會調用measure方法,而measure方法又會調用onMeasure方法(PS:是否是就發現了爲啥咱們平時都是重寫onMeasure方法了。),而後又會在onMeasure方法裏面去調用全部子View的measure過程。

咱們能夠看到思惟腦圖中有提到頂級View就是DecorView,那DecorView是什麼呢? DecorView是一個FrameLayout,裏面包含了一個豎向的LinearLayout,通常來講這個LinearLayout是有上下二部分(這裏具體跟Android SDK和主題有關):

是否是看到了熟悉的Content這個名字,沒錯。咱們在Activity裏面設置佈局setContentView就是把咱們的佈局加到這個id爲android.R.id.contentFrameLayout裏面。

咱們如今正式進入View整個繪製流程:

View的大小

你們能夠看到,爲了方便你們理解,我寫了二個現實生活場景故事對比。

故事對比<1>

咱們能夠看到,咱們的氣球放到櫃子裏面,決定氣球大小的因素有二個:櫃子給它的限制,還有它自身的因素(質量好壞,好的能吹的很大)。而咱們的View也是同樣的,首先咱們用MeasureSpec來決定咱們的View大小,那咱們的MeasureSpec和睦球同樣,也受到二個因素的影響:

  1. ViewGroup的影響
  2. 自身的LayoutParams

總結起來就是一句話:在測量過程當中,系統會將View的LayoutParams根據父容器ViewGroup所施加的規則下,轉換得出相對應的MeasureSpec,而後根據這個MeasureSpec來測量出View的高/寬。

可能你們會問什麼是MeasureSpec,別急,咱們立刻就來介紹

MeasureSpec知識

其實直接看腦圖,應該就能看得懂吧,主要是這麼幾個知識點:

  1. MeasureSpec是由SpecMode和SpecSize組合成的。
  2. SpecMode的種類:UNSPECIFIED,EXACTLY,AT_MOST。
  3. 普通的View是由父容器限制和自身的LayoutParams來生成相應的MeasureSpec,而DecorView由於是頂層View了。咱們能夠想象哪來的父容器啊,在外面一層就直接是屏幕了,因此是由屏幕的尺寸和自身的LayoutParams決定。

對比故事<2>

沒錯,經過對比,咱們能夠發現規律原來很簡單。由於咱們腦子裏面能夠用這個氣球的對比故事更好的理解。

我作一個總結表格:(要理解上面的分析過程,而不是背下這個表格,背下來沒啥意思)

View的測量

經過上面咱們已經知道MeasureSpec是用來肯定View的測量的,也已經能根據不一樣的狀況來得到相應的MeasureSpec了。那咱們的到底應該在哪裏去建立MeasureSpec呢?而後給子View去約束呢?

其實奧祕就在咱們平時重寫的onMeasure()方法中:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}        
複製代碼

咱們是否是看到了onMeasure方法裏面傳入了(int widthMeasureSpec, int heightMeasureSpec),沒錯,這裏傳入的二個參數,就是當前你重寫這個方法的所在的View(子View或者ViewGroup)的進行過一系列的操做最後得到的MeasureSpec。

那咱們拿到這二個參數後,View仍是不知道咱們到底給它的寬和高是多少。應該確定最後是咱們調用類型:view.setMeasureWidth(XX),view.setMeasureHeight(XX)這樣,它才能被設置測量的寬和高。沒錯,setMeasuredDimension(int measuredWidth, int measuredHeight)方法就是咱們用來設置view的測量寬和高。

固然你可能會問,那我若是直接調用這個方法來設置view的寬和高,那我感受我不用MeasureSpec都不要緊啊。好比下面的代碼:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    //沒有使用相應的MeasureSpec
    setMeasuredDimension(100,100);
}
複製代碼

沒錯,咱們能夠不是經過正規的測量過程來決定測量的寬和高,咱們就是任性的直接定了寬高是100。可是這樣就不符合規則流程了。並且作出來的東西也不會特別好。好比這時候,你在xml中對你的view設置match_parent,wrap_content,200dp就會都無效,由於代碼最後都是用了100。

onMeasure()方法的構成

咱們前面提過,自定義View是要重寫onMeasure()方法的,咱們再仔細分析下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
    //咱們通常會本身寫的代碼
    ........
    ........
    .......
    
}
複製代碼

咱們能夠看到,主要分爲二塊:

  1. super.onMeasure(),
  2. 本身寫的代碼。

咱們根據不一樣的狀況一步步來看這些代碼的做用。

直接繼承View.java

super.onMeasure() 分析1 :好比咱們的自定義View直接繼承了View.java:

public class DemoView extends View {
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
    }
}

複製代碼

咱們能夠查看super.onMeasure方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(
        getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),      
        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
    );
}
複製代碼

咱們看到果真調用了setMeasureDimension方法來進行寬高的設置了。


PS:接下來的源碼這個分析能夠不看,直接看結論。嘿嘿。嘿嘿。我知道不少人都不想看。

咱們能夠看到主要是三個方法(咱們這裏就看width的測量):

  1. 先getSuggestedMinimumWidth方法獲取了某個值。
  2. 經過getDefaultSize方法來對第一步獲取到的值和約束一塊兒處理後,獲得最終值。
  3. 經過setMeasuredDimension方法把咱們最終的值給賦值進去。

1和2的方法先不看,咱們起碼知道了。咱們最終肯定一個View的測量大小,是經過setMeasuredDimension來設置的(其實我感受我說的廢話,看這個方法的名字就很明確了)。

咱們再回頭來看1中的方法:

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
複製代碼

若是咱們的View沒有設置background,則返回的最小值爲mMinWidth(啥是mMinWidth?????就是咱們在xml設置的android:minWidth的值)。若是咱們設置了background,則獲取mBackground.getMinimumWidth()(其實這個方法就是返回Drawable的原始寬度)。最後返回max(mMinWidth, mBackground.getMinimumWidth())兩者中的最大值。

咱們再來看2中的方法:

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
}
複製代碼

其實上面咱們的MeasureSpec的建立規則會的話,其實應該就能看的懂。若是是specMode是UNSPECIFIED,則返回咱們1中的方法getSuggestedMinimumWidth獲取到的值,若是是AT_MOSTEXACTLY,則直接返回specSize。(View源碼這裏的寬度的建立規則和咱們前面講的測量的規則區別就在於,當specMode是UNSPECIFIED的時候,返回的是getSuggestedMinimumWidth的值,而咱們是返回了0。)

結論1:若是寫的自定義View是直接繼承View的,並且寫了super.measure(),則會默認給這個View設置了一個測量寬和高(這個寬高是多少?若是沒有設置背景,則是xml裏面設置的android:minWidth/minHeight(這個屬性默認值是0),若是有背景,則取背景Drawable的原始高寬值和android:minWidth/minHeight兩者中的較大者。)

繼承現有控件

super.onMeasure() 分析2 :好比咱們的自定義View繼承了現有的控件,好比ImageView.java:

public class Image2View extends ImageView {

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
複製代碼

這時候咱們的super.onMeasure()方法調用的就是ImageView裏面的onMeasure方法了:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    //ImageView 的一大堆計算寬高的代碼。
    ......
    ......
    ......
    
    //固然最終確定要把算好的寬高告訴View
    setMeasuredDimension(widthSize, heightSize);
}
複製代碼

咱們發現若是咱們的View直接繼承ImageView,ImageView已經運行了一大堆已經寫好的代碼測出了相應的寬高。咱們能夠在它基礎上更改便可。

好比咱們的Image2View是一個自定義的正方形的ImageView,:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    //這裏已經幫咱們測好了ImageView的規則下的寬高,而且經過了setMeasuredDimension方法賦值進去了。
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    //咱們這裏經過getMeasuredWidth/Height放來獲取已經賦值過的測量的寬和高
    //而後在ImageView幫咱們測量好的寬高中,取小的值做爲正方形的邊。
    //而後從新調用setMeasuredDimension賦值進去覆蓋ImageView的賦值。
    //咱們從頭到位都沒有進行復雜測量的操做,全靠ImageView。哈哈
    int width = getMeasuredWidth();
    int height = getMeasuredHeight();
    if (width < height) {
        setMeasuredDimension(width, width);
    } else {
        setMeasuredDimension(height, height);
    }
}
複製代碼

結論2:若是寫的自定義View是繼承現有控件的,並且寫了super.measure(),則會默認使用那個現有控件的測量寬高,你能夠在這個已經測量好的寬高上作修改,固然也能夠所有從新測過再改掉。

本身寫的代碼與super.measure的先後位置

super.onMeasure() 分析3:咱們寫的本身的代碼與super.measure的先後位置關係

咱們能夠看到,無論你是繼承View仍是現有的控件(好比ImageView),super.onMeasure()中都默認會按照本身的邏輯測量一個寬和高,而後調用setMeasuredDimension()方法賦值進去。

  1. 若是咱們的本身的代碼寫在super.measure前面,那麼你寫的測量的邏輯測定好寬高,而且賦值後,最終都會再次被super.measure中的setMeasuredDimension()所覆蓋。
  2. 若是咱們的本身的代碼寫在super.measure後面,你能夠在你繼承的父類的測量結果的基礎進行更改(固然你不用父類的測量結果也是不要緊的),而後再次調用setMeasuredDimension()賦值。
  3. 若是你的測量寬高的邏輯,不是基於你繼承的控件的測量的基礎上進行,徹底由你來從新測定的話,super.onMeasure()不寫也不會有問題。

具體實現自定義View的測量

1. 好比咱們直接是繼承現有的控件,好比ImageView,實現一個正方形的ImageView(上面已經提到過了):
public class Image2View extends ImageView {

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //這裏的super.onMeasure()方法裏面,已是調用了ImageView的onMeasure()方法。
        //因此已經進行了測量了。而且在這個方法最後調用了setMeasuredDimension(widthSize, heightSize);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //因此你不寫任何東西,這個測量結果都已經肯定過了,由於已經執行過了setMeasuredDimension。
        //但好比你想要在ImageView的基礎上,讓這個ImageView變成一個正方形的ImageView。
        //由於測出來的寬高可能不一樣,是一個矩形。咱們就須要手動的再去設置一次寬和高。
        int width = getMeasuredWidth();//獲取ImageView源碼裏面已經測量好的寬度
        int height = getMaxHeight();//獲取ImageView源碼裏面已經測量好的高度
        if (width < height) {
            setMeasuredDimension(width, width);
        } else {
            setMeasuredDimension(height, height);
        }
    }
}

複製代碼

咱們發現,咱們是在已經咱們繼承的現有的控件幫咱們測量好寬高後,能夠再次在這個已經測量好的寬高的基礎上進行更改。咱們並無用到咱們前面學到的MeasureSpec的知識,由於super.onMeasure()中已經幫咱們把MeasureSpec處理好了。

2. 好比咱們本身直接繼承了View:
public class CircleView extends View {
    public CircleView(Context context) {
        super(context);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
        //View測量寬高的三步曲
    
        //1.設置默認值,wrap_content的狀況下的值。
        //由於wrap_content只是說不超過某個最大值,若是不設置默認值,效果與Match_parent同樣了。
        int defaultWidthSize = 200;
        int defaultHeightSize = 200;
        
        //2.調用resolveSize()方法,把MeasureSpec和咱們的默認值放進去
        //這個方法返回一個最終根據你傳入的默認值及MeasureSpec共同做用後的最終結果
        defaultWidthSize = resolveSize(defaultWidthSize, widthMeasureSpec);
        defaultHeightSize = resolveSize(defaultHeightSize, heightMeasureSpec);
        
        //調用setMeasuredDimension方法賦值寬和高
        setMeasuredDimension(defaultWidthSize, defaultHeightSize);

    }
}
複製代碼

是否是超級超級超級簡單。你們可能就會問,那個resolveSize()方法是什麼,怎麼這麼神奇。

PS:下面的resolveSize()源碼分析不看也沒啥關係,反正會用就好了。哈哈,不影響使用。

咱們能夠來看下它的源碼:

public static int resolveSize(int size, int measureSpec) {
    return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
}


public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {

    //1.拿到specMode 和 specSize

    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    
    //2.根據不一樣的specMode來進行判斷最終值是什麼
    switch (specMode) {
    
        
        case MeasureSpec.AT_MOST:
            /*
                2.1若是specMode是AT_MOST模式,咱們原本應該直接是specSize
                可是若是咱們的默認值比咱們的specSize大就很尷尬了。氣球默認的大小都裝不進櫃子了。這時候咱們View的大小要設置成specSize,若是默認大小比咱們的specSize小就不要緊,直接爲默認值。
            */
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
            
        /*
            2.2若是是EXACTLY,直接就是specSize    
        */
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
            
         /*
           2.3若是是UNSPECIFIED模式,則直接就是咱們設的默認值
         */
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}
複製代碼
3. ViewGroup的測量

在講ViewGroup的測量前面,我要提問個問題,你們應該知道了某個View的MeasureSpec在是在onMeasure()方法的參數裏面傳進來的。咱們是直接拿來用了。那又是那裏調用了onMeasure()方法幫忙把這二個參數帶進來的呢。這二個參數又是哪裏生成的呢?

答案就是這個子View的父容器給它的。父容器在他本身的onMeasure()方法裏面會根據本身的onMeasure()傳進來的MeasureSpec,及這個子View的自身的LayoutParams狀況,生成相應的childMeasureSpec,而後調用子View的measure()傳遞進去的(前面提過,measure()方法會調用onMeasure()方法。)

好比咱們寫一個圓形排布的ViewGroup(LinearLayout是一排的排布)。

public class CircleLayout extends ViewGroup {
    
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        
        //1.父容器的onMeasure()傳進來的二個參數widthMeasureSpec和 heightMeasureSpec
        //2.還差子View的LayoutParams,獲取子View的LayoutParams
        //3.經過兩者產生新的MeasureSpec而後給子View。
        //4.而產生的新的ChildMeasureSpec的規則就是咱們前面表格總結過的規則。
        
        /*
        PS:下面這段是我寫的代碼,並非正確的,由於父容器可能包含多個子View,
        因此到某個子View的時候,給它的specSize應該是父容器的剩餘空間,
        因此傳入的父容器的可用空間原本是不停的減小的,外加還有margin,padding值也要減去。
        我就是主要意思下,讓你們懂得原理。
        */
        
        
        //先判斷初始時候父容器的大小,由於父容器也是個View,因此也是三步曲。
        //設置默認值(能夠是0,由於父容器通常默認不會佔有空間)
        int defaultWidthSize = 500;
        int defaultHeightSize = 500;
        //resolveSize處理獲取寬和高
        int resultWidthSize = resolveSize(defaultWidthSize, widthMeasureSpec);
        int resultHeightSize = resolveSize(defaultHeightSize, heightMeasureSpec);
        
        //好比咱們這裏以width爲例子:
        //咱們前面提過了,最終給子View的MeasureSpec是由父View的MeasureSpec與子View的LayoutParam共同肯定。
        //先獲取父View的MeasureSpec的mode和size
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
            
        //根據不一樣的SpecMode及子View的LayoutParams來產生新的ChildMeasureSpec。
        
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            
            LayoutParams params = view.getLayoutParams();
            int childWidthSpec, childHeightSpec;
            
            //先根據父View的MeasureSpec來進行大分類:
            switch (specMode) {
        
                case MeasureSpec.EXACTLY:
                    //說明是固定值,好比100dp等
                    if (params.width >= 0) {
                        resultSize = params.width;
                        resultMode = MeasureSpec.EXACTLY;
                    } else if (childDimension == LayoutParams.MATCH_PARENT) {
                        
                        resultSize = specSize;
                        resultMode = MeasureSpec.EXACTLY;
                    } else if (childDimension == LayoutParams.WRAP_CONTENT) {
              
                        resultSize = specSize;
                        resultMode = MeasureSpec.AT_MOST;
                    }
                
                    break;
                
                case MeasureSpec.AT_MOST:
                    .....
                    .....
                    break;
                    
                case MeasureSpec.UNSPECIFIED:
                    .....
                    .....
                    break;
                
                
            }
            
             childWidthSpec = MeasureSpec.makeMeasureSpec(resultWidthSize, MeasureSpec.EXACTLY);
             
             getChildAt(i).measure(childWidthSpec, childHeightSpec);
            
        }
        
        
        
        /*
            可能有人說,生成新的規則我都懂,可是每次都要寫上面一大段的代碼,
            我不想寫自定義ViewGroup了。我仍是放棄吧,別急,你們也發現上面的規則的確是固定的。
            那有沒有相似咱們在上面設置本身寬高時候的相似resolveSize的方法呢。
            若是沒有特定的需求,的確咱們不須要寫上面一大段。
            有二種方法。
         
        */
        
        
        //方法1:能夠經過調用measureChildren()一會兒把全部的子View測量好
         measureChildren(widthMeasureSpec, heightMeasureSpec);
        
        //方法2:經過measureChild()一個個來測量。
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            measureChild(view , widthMeasureSpec,heightMeasureSpec);
        }    
        
        //設置父容器的大小
        setMeasuredDimension(XXXX,XXXX)
    
    }
}
複製代碼

沒錯,最後咱們能夠用measureChildren(widthMeasureSpec, heightMeasureSpec);measureChild(view , widthMeasureSpec,heightMeasureSpec);方法來,咱們也知道它的內部確定也是根據相應的規則,生成對應的childMeasureSpec,而後調用child的measure方法。

咱們能夠看下源碼(PS:不想看仍是不要緊,能夠跳過):

//measureChildren其實只是幫咱們遍歷了全部的View,幫咱們把可見的View分別調用measureChild方法來處理。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

//而measureChild方法裏面就是獲取子View的LayoutParams和傳進來的MeasureSpec,
//把這兩者經過getChildMeasureSpec方法得到一個新的childMeasureSpec,而後傳給child.measure方法。
protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
           mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
複製代碼

若是具體想看getChildMeasureSpec作了什麼,可有再去看下源碼,可是他們生成的規則跟咱們前面講的仍是同樣的。我這裏很少說了。

測量完後獲取View的寬和高

這個就十分簡單了。直接看腦圖便可。

View的位置

這塊比較簡單,我也很少說了。(別吐槽我,這文章太多了。寫太多沒人會耐心看完。)

View的繪製

咱們都知道View的大小和位置都肯定好了,確定就差繪畫了。

View 繪畫draw()

咱們都知道是經過draw()方法來繪製的。

而draw()方法具體作了什麼呢,咱們能夠看源碼這個方法的工做過程的介紹:

draw()源碼裏面的介紹:

/*
 * Draw traversal performs several drawing steps which must be executed
 * in the appropriate order:
 *
 *      1. Draw the background
 *      2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content
 *      4. Draw children
 *      5. If necessary, draw the fading edges and restore layers
 *      6. Draw decorations (scrollbars for instance)
 */
複製代碼

分別是先繪製背景,而後繪製本身的內容,而後繪製子View的內容,最後畫裝飾和前景。

推薦你們看扔物線大佬的文章,講的很清楚,我就不花大篇幅寫基礎了。

HenCoder Android 自定義 View 1-5: 繪製順序

Canvas的使用

咱們知道不論是onDraw(Canvas canvas),dispatchDraw(Canvas canvas),onDrawForeground(Canvas canvas)等都是參數是Canvas(畫布)。因此咱們知道了是用Canvas來繪畫。

這裏也是推薦扔物線大佬的相關文章,講的很細,我也再也不大篇幅的寫各類基礎使用知識。

HenCoder Android 開發進階: 自定義 View 1-1 繪製基礎

HenCoder Android 開發進階:自定義 View 1-4 Canvas 對繪製的輔助

Canvas怎麼使用呢: 主要分爲二大塊:

Canvas繪製類方法

這塊很簡單,直接用Canvas來畫顏色,畫矩形,畫圓形,畫直線等各類圖形。雖然簡單,但畢竟這纔是基本的繪製,用的最多。

Canvas的輔助類方法

其中幾何變化又分爲二維變換和三維變換:

二維變換

三維變換

Paint相關

咱們知道Paint是畫筆,咱們能夠設置顏色,畫筆粗細等。

繼續推薦扔物線大佬的相關文章(基礎我就不寫了):

HenCoder Android 開發進階: 自定義 View 1-2 Paint 詳解

顏色相關

效果

繪製文字相關

Paint初始化部分相關


結語

有錯誤的地方,請你們輕點噴,我膽子很小的。。。。

相關文章
相關標籤/搜索