本文基本不說原理央星,只說流程、公式惫东、套路與“安全措施”莉给。
Step 1
構(gòu)造函數(shù)只需要用到兩個,其余兩個百分之95的人與需求不會用得到廉沮。
public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
很簡單颓遏,代碼new用到第一個構(gòu)造函數(shù),xml則會用到第二個構(gòu)造函數(shù)滞时。
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();
}
在第二個構(gòu)造函數(shù)使用TypeArray
獲取xml
中設置的attrs
。
注意使用完typearray
記得調(diào)用recycle()
再看看自定義屬性是怎么寫
于values
-> attrs
<declare-styleable name="CustomView">
<attr name="attr_1" format="dimension" />
</declare-styleable>
注意了漂洋,這里和上面獲取屬性遥皂,名稱是拼接起來的。如上面代碼刽漂,name
是CustomView
演训,而attr
的name
是attr_1
,那么通過typearray
獲取attr_1
屬性的值是使用R.styleable.CustomView_attr_1
贝咙。
接著看看xml布局怎么使用自定義屬性
第一種寫法AS會提示你修改成第二種寫法样悟,故我們也用第二種寫法,除非你的自定義屬性名稱與其他的自定義屬性名稱有沖突就可以使用第一種來解決(指定自定義屬性的路徑別名)庭猩。
Step 3
測量
- 單一view的測量
measure()
為final方法窟她,子類不可復寫,最后會調(diào)用````onMeasure()蔼水。
onMeasure()這里進行你的需求來measure->
setMeasureDimension()儲存測量后的子view的寬高震糖。如果在使用自定義view時,用了
wrap_content趴腋。那么在
onMeasure中就要調(diào)用
setMeasuredDimension吊说,來指定view的寬高。如果使用的matchl_parent或者一個具體的dp值优炬。那么直接使用
super.onMeasure``即可颁井。 - viewgroup的測量
measure()->onMeasure()->測量子view->setMeasureDimension()
在這里我們需要在onMeasure()測量子view。測量子view的方法一般有四種
2.1 getChildAt(int index).可以拿到index上的子view蠢护。
通過getChildCount得到子view的數(shù)目雅宾,再循環(huán)遍歷出子view。
接著葵硕,subView.measure(int wSpec, int hSpec); //使用子view自身的測量方法
2.2 或者調(diào)用viewGroup的測量子view的方法:
//某一個子view眉抬,多寬,多高, 內(nèi)部加上了viewGroup的padding值
measureChild(subView, int wSpec, int hSpec);
2.3 //所有子view 都是 多寬宣决,多高, 內(nèi)部調(diào)用了measureChild方法
measureChildren(int wSpec, int hSpec);
2.4 //某一個子view,多寬,多高, 內(nèi)部加上了viewGroup的padding值贤惯、margin值和傳入的寬高wUsed孵构、hUsed
measureChildWithMargins(subView, intwSpec, int wUsed, int hSpec, int hUsed);
Step 4
布局
view 的布局兩個步驟layout
->onLayout
layout:計算自身位置調(diào)用setFrame()
onLayout:看下面解析
- 自定義View
1.1 在單一的View中烟很,只會用到layout
雾袱,而onLayout
是個空實現(xiàn)芹橡。 - 自定義ViewGroup,調(diào)用
layout()
計算自身的位置煎殷,調(diào)用onLayout()
遍歷子View并調(diào)用子Viewlayout()
確定自身子View的位置豪直。
那么自定義ViewGroup一般套路就是
// 偽代碼弓乙,會省略方法的一些參數(shù)
onLayout(){
// 循環(huán)所有子View
for (int i=0; i<getChildCount(); i++) {
View child = getChildAt(i);
// 計算當前子View的四個位置值
// 計算的邏輯需要自己實現(xiàn)剑梳,也是自定義View的關(guān)鍵
...
// 對計算后的位置值進行賦值
int mLeft = Left
int mTop = Top
int mRight = Right
int mBottom = Bottom
// 調(diào)用子view的layout()并傳遞計算過的參數(shù)
// 從而計算出子View的位置
child.layout(mLeft, mTop, mRight, mBottom);
}
}
然后到一個注意點了
- getWidth() / getHeight() = View最終的寬 / 高
- getMeasuredWidth() / getMeasuredHeight() = View的測量的寬 / 高
在哪里賦值 | 賦值方法 | 何處可用 | |
---|---|---|---|
getMeasuredWidth() | onMeasure() | setDimension() | onMeasure()后 |
getWidth() | layout() | setFrame() | 在layout()調(diào)用后 |
那何種情況下兩者會值不同
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的繪制流程為:
繪制過程如下:
- 繪制view背景
- 繪制view內(nèi)容
- 繪制子View
- 繪制裝飾(漸變框骂倘,滑動條等等)
代碼流程為draw()
->1
->2 onDraw()
->3 dispatchDraw()
->4
其中自定義View沒有子view诅需,故不需要第三步(空實現(xiàn))荧库,一般只需要完成第二步即可,在onDraw
繪制我們需要的內(nèi)容场刑。
其次自定義ViewGroup
有子view牵现,故需要第三步邀桑,但一般我們也無需理會(系統(tǒng)已經(jīng)為我們實現(xiàn)了該方法)壁畸,故是在onDraw
繪制我們需要的內(nèi)容瓤摧,但但但但是照弥,一般viewgroup也無需繪制什么東西
又到注意事項:
上面說到viewgroup
也無需繪制什么東西,故viewgroup
需要在onDraw()
繪制東西的時候悔常,需要調(diào)用setWillNotDraw(boolean willNotDraw)
机打,設置為false
片迅。這樣viewgroup才會執(zhí)行onDraw()
,否則不執(zhí)行驱闷。
Step 6
刷新
Android中實現(xiàn)view的更新有兩組方法空免,一組是invalidate
,另一組是postInvalidate
扼菠,其中前者是在UI線程自身中使用娇豫,而后者在非UI線程中使用畅厢。
那么套路就是handler+invalidate
或postInvalidate
或onDraw根據(jù)條件+invalidate