自定義View公式

SamanLan發表於2017-12-13

本文基本不說原理,只說流程、公式、套路與“安全措施”。

Step 1

建構函式只需要用到兩個,其餘兩個百分之95的人與需求不會用得到。

public class CustomView extends View {
    public CustomView(Context context) {
        super(context);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}
複製程式碼

很簡單,程式碼new用到第一個建構函式,xml則會用到第二個建構函式。

Step 2

獲取xml配置的屬性,以及使用自定義的屬性

public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView, 0, 0);
        float attr_1 = ta.getDimension(R.styleable.CustomView_attr_1, 0f);
        ta.recycle();
}
複製程式碼

在第二個建構函式使用TypeArray獲取xml中設定的attrs。 注意使用完typearray記得呼叫recycle() 再看看自定義屬性是怎麼寫 於values -> attrs

<declare-styleable name="CustomView">
    <attr name="attr_1" format="dimension" />
</declare-styleable>
複製程式碼

注意了,這裡和上面獲取屬性,名稱是拼接起來的。如上面程式碼,nameCustomView,而attrnameattr_1,那麼通過typearray獲取attr_1屬性的值是使用R.styleable.CustomView_attr_1。 接著看看xml佈局怎麼使用自定義屬性

佈局怎麼使用自定義屬性

佈局怎麼使用自定義屬性2
第一種寫法AS會提示你修改成第二種寫法,故我們也用第二種寫法,除非你的自定義屬性名稱與其他的自定義屬性名稱有衝突就可以使用第一種來解決(指定自定義屬性的路徑別名)。

Step 3

測量

  1. 單一view的測量 measure()為final方法,子類不可複寫,最後會呼叫````onMeasure()onMeasure()這裡進行你的需求來measure->setMeasureDimension()儲存測量後的子view的寬高。如果在使用自定義view時,用了wrap_content。那麼在onMeasure中就要呼叫setMeasuredDimension,來指定view的寬高。如果使用的matchl_parent或者一個具體的dp值。那麼直接使用super.onMeasure``即可。
  2. viewgroup的測量 measure()->onMeasure()->測量子view->setMeasureDimension() 在這裡我們需要在onMeasure()測量子view。測量子view的方法一般有四種 2.1 getChildAt(int index).可以拿到index上的子view。 通過getChildCount得到子view的數目,再迴圈遍歷出子view。 接著,subView.measure(int wSpec, int hSpec); //使用子view自身的測量方法 2.2 或者呼叫viewGroup的測量子view的方法: //某一個子view,多寬,多高, 內部加上了viewGroup的padding值 measureChild(subView, int wSpec, int hSpec); 2.3 //所有子view 都是 多寬,多高, 內部呼叫了measureChild方法 measureChildren(int wSpec, int hSpec); 2.4 //某一個子view,多寬,多高, 內部加上了viewGroup的padding值、margin值和傳入的寬高wUsed、hUsed
    measureChildWithMargins(subView, intwSpec, int wUsed, int hSpec, int hUsed);

Step 4

佈局 view 的佈局兩個步驟layout->onLayout layout:計算自身位置呼叫setFrame() onLayout:看下面解析

  1. 自定義View 1.1 在單一的View中,只會用到layout,而onLayout是個空實現。
  2. 自定義ViewGroup,呼叫layout()計算自身的位置,呼叫onLayout()遍歷子View並呼叫子View layout()確定自身子View的位置。 那麼自定義ViewGroup一般套路就是
// 虛擬碼,會省略方法的一些引數
onLayout(){
// 迴圈所有子View
        for (int i=0; i<getChildCount(); i++) {
            View child = getChildAt(i);   

            // 計算當前子View的四個位置值
            // 計算的邏輯需要自己實現,也是自定義View的關鍵
            ...

            // 對計算後的位置值進行賦值
            int mLeft  = Left
            int mTop  = Top
            int mRight = Right
            int mBottom = Bottom

            // 呼叫子view的layout()並傳遞計算過的引數
            // 從而計算出子View的位置
            child.layout(mLeft, mTop, mRight, mBottom);
        }
}
複製程式碼

然後到一個注意點了

  • getWidth() / getHeight() = View最終的寬 / 高
  • getMeasuredWidth() / getMeasuredHeight() = View的測量的寬 / 高
在哪裡賦值 賦值方法 何處可用
getMeasuredWidth() onMeasure() setDimension() onMeasure()後
getWidth() layout() setFrame() 在layout()呼叫後

那何種情況下兩者會值不同 just one

@Override
public void layout( int l , int t, int r , int b){
   super.layout(l,t,r+10,b+200)
}
複製程式碼

Step 5

draw繪製 view的繪製流程為: 繪製過程如下:

  1. 繪製view背景
  2. 繪製view內容
  3. 繪製子View
  4. 繪製裝飾(漸變框,滑動條等等)

程式碼流程為draw() ->1 ->2 onDraw() ->3 dispatchDraw() ->4 其中自定義View沒有子view,故不需要第三步(空實現),一般只需要完成第二步即可,在onDraw繪製我們需要的內容。 其次自定義ViewGroup有子view,故需要第三步,但一般我們也無需理會(系統已經為我們實現了該方法),故是在onDraw繪製我們需要的內容,但但但但是,一般viewgroup也無需繪製什麼東西

又到注意事項: 上面說到viewgroup也無需繪製什麼東西,故viewgroup需要在onDraw()繪製東西的時候,需要呼叫setWillNotDraw(boolean willNotDraw),設定為false。這樣viewgroup才會執行onDraw(),否則不執行。

Step 6

重新整理 Android中實現view的更新有兩組方法,一組是invalidate,另一組是postInvalidate,其中前者是在UI執行緒自身中使用,而後者在非UI執行緒中使用。 那麼套路就是handler+invalidatepostInvalidateonDraw根據條件+invalidate

相關文章