學(xué)習(xí)資料:
Android群英傳Android開發(fā)藝術(shù)探索- 劉望舒Android View體系系列
- 愛哥AigeStudio自定義控件其實(shí)很簡單系列
感謝以上各位大神前輩們 :)
除了愛哥的自定義系列博客,再推薦一個非常不錯的系列,GcsSloop的自定義系列,可以看完我寫的再去看,我寫的很基礎(chǔ),適合剛開始學(xué)習(xí)自定義View的同學(xué)來了解基礎(chǔ)知識,原理及更深入的知識,可看兩位大神的系列 : )
安利,安利,安利
整個系列學(xué)習(xí),記錄的大概過程:
- 準(zhǔn)備
- 開始了解Canvas和Paint
- Paint 繪制文字屬性
- Paint 關(guān)于ColorMatrix學(xué)習(xí)
- Paint 關(guān)于PorterDuffXfermode學(xué)習(xí)
- Paint 關(guān)于Shader的學(xué)習(xí)
- 補(bǔ)充學(xué)習(xí)Bitmap
- Canvas 方法 以及屬性學(xué)習(xí)
- Matrix學(xué)習(xí)
- Drawable補(bǔ)充學(xué)習(xí)
- 貝塞爾曲線入門學(xué)習(xí)
- SurfaceView學(xué)習(xí)
- Camera 圖像處理入門學(xué)習(xí)
- View測量
- ViewGroup知識學(xué)習(xí)
- 注意事項和繼承View知識點(diǎn)學(xué)習(xí)
- 觸摸事件學(xué)習(xí)
- 滑動學(xué)習(xí)
- ViewDragHelper學(xué)習(xí)使用
- PathMeasure學(xué)習(xí)
- 學(xué)習(xí)總結(jié)
-
畫圖板練習(xí)
是一個長期的學(xué)習(xí)計劃 : )
本篇就是個讀書筆記而已
1.Android控件架構(gòu)
Android中的每個控件都會在界面中占據(jù)一個矩形區(qū)域??丶笾路譃?code>View和ViewGroup。ViewGroup控件作為父類控件可以包含多個View控件。

通過ViewGroup,整個界面控件形成樹形結(jié)構(gòu),也即是控件樹。上層控件負(fù)責(zé)下層控件的測量和繪制,并傳遞交互事件。在一個Activity中,findViewById()就是在控件樹中以樹的深度優(yōu)先遍歷來查找對應(yīng)的元素。
在每棵樹的頂部,都有一個ViewParent對象,所有的交互管理事件都有這個ViewParent對象調(diào)度和分配
通常情況下,在Activity中使用setContent()方法設(shè)置一個布局在調(diào)用本方法后,布局內(nèi)容才會真正顯示出來。

每個Activity都包含有一個Window對象,通常是PhoneWindow。PhoneWindow將一個DecorView設(shè)置為整個應(yīng)用的窗口的根View。DecorView作為窗口界面頂層視圖,里面封裝了一些窗口操作的通用方法。
DecorView將內(nèi)容顯示在PhoneWindow上,并通過WindowManagerService來進(jìn)行接收,并通過Activity對象來回調(diào)對應(yīng)的onClickListener。顯示時,將屏幕分成兩個部分,TitleView和ContentView。Content是一個id為content的FrameLayout,activity_main.xml就在其中。
通過以上就可以得到下面的標(biāo)準(zhǔn)視圖樹:

視圖樹的第二層加載一個LinearLayout作為ViewGroup。這一層布局結(jié)構(gòu)會根據(jù)對應(yīng)的參數(shù)設(shè)置不同的布局。
最常用的布局,上面顯示TitleBar下面是Content。如果用戶通過設(shè)置requestWindowFeature(Window.FEATURE_NO_TITLE)來設(shè)置全屏顯示,視圖樹就只有Content。這也是為什么requestWindowFeature()要在setContent()之前生效的原因。
當(dāng)程序在onCreate()方法中調(diào)用了setContentView()方法后,ActivityManagerService會回調(diào)onResume()方法,系統(tǒng)會把整個DecorView添加進(jìn)PhoneWindow中,顯示出來后,完成界面的繪制。
2.坐標(biāo)體系
View的位置只要由它的四個頂點(diǎn)來決定。分別對應(yīng)于View的四個屬性:
-
top,getTop()左上角的縱坐標(biāo) -
left,getLeft()左上角的橫坐標(biāo) -
right,getRight()右下角的橫坐標(biāo) -
bottom,getBottom()右下角的縱坐標(biāo)
View的這些坐標(biāo)都是相對于View的父容器來說。

在Android 3.0 后,
View增加了:x,y,tranlastionX和translationY。x,y是View左上角的坐標(biāo),tranlastionX和translationY是View左上角相對于父容器的偏移量。
換算關(guān)系:
x = left + translationX
y = top + translationY
需要注意的是,View在平移過程中,top和left表示的原始左上角的位置信息,其值并不會發(fā)生改變,此時發(fā)生改變的是x,y,tranlastionX和translationY
圖顏色配的有點(diǎn)多。 :)
3.View的測量
一個View顯示在屏幕上需要經(jīng)歷三個流程:測量,布局,繪制
測量的目的在于告訴系統(tǒng)繪制一個多大的,位置在哪里。這個過程在onMeasure()方法中進(jìn)行。
測量主要依賴MeasureSppec類。MeasureSpec是一個32位的int值,高2位為測量的模式,低30位為測量的大小。
測量的模式共有三種:
-
EXACTLY精確模式,兩種情況
控件的layout_width,layout_height
- 指定數(shù)值時,例如
layout_width="100dp" - 指定為
match_parent
-
AT_MOST最大值模式
控件的layout_width,layout_height指定為wrap_content
控件大小一般會隨著子空間的或內(nèi)容的變化而變化,此時要求控件的尺寸只要不超過父類控件允許的最大尺寸即可
-
UNSPECIFIED未指明模式
不指定控件大小,View想多大就多大。
View類默認(rèn)的onMeasure()方法只支持EXACTLY模式。自定義View時,需要重寫onMeasure()方法后,才可以支持其他的模式。假如想要自定義的控件支持wrap_content,就要在onMeasure()中告訴系統(tǒng)自定義控件wrap_content時的大小。
3.1 最基礎(chǔ)的實(shí)現(xiàn)
用來測試onMeasure()方法。
public class MeasureView extends View {
public MeasureView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
在布局中使用:
<com.szlk.customview.custom.MeasureView
android:id="@+id/mv_custom_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorAccent" />

運(yùn)行之后,wrap_content在此時和match_parent效果是一樣的。都是沾滿全屏。此時在Acitivity中拿到的MeasureView的大小和match_parent是一樣的。
3.2 修改onMeasure()方法
重寫onMeasure()方法后

public class MeasureView extends View {
public MeasureView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),measuredHeight(heightMeasureSpec));
}
/**
* 測量寬
* @param widthMeasureSpec
*/
private int measureWidth(int widthMeasureSpec) {
int result ;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else {
result = 200;
if (specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);
}
}
return result;
}
/**
* 測量高
* @param heightMeasureSpec
*/
private int measuredHeight(int heightMeasureSpec) {
int result ;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = 200;
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);
}
}
return result;
}
}
加入了利用MeasureSpec來判斷模式。根據(jù)不同模式,進(jìn)行對寬高賦值。在AT_MOST也就是wrap_content時,默認(rèn)最大的寬高都是200px
3.3 涉及的部分源碼
getMode()
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
由@return可知,返回結(jié)果就是MeasureSpec的三種模式
getSize()
/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
返回的是在布局文件中聲明的值,結(jié)果是px
onMeasure()
/**
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overridden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
*
* <p>
* <strong>CONTRACT:</strong> When overriding this method, you
* <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
* measured width and height of this view. Failure to do so will trigger an
* <code>IllegalStateException</code>, thrown by
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
* </p>
*
* <p>
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
* </p>
*
* <p>
* If this method is overridden, it is the subclass's responsibility to make
* sure the measured height and width are at least the view's minimum height
* and width ({@link #getSuggestedMinimumHeight()} and
* {@link #getSuggestedMinimumWidth()}).
* </p>
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
*
* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
* @see android.view.View.MeasureSpec#getSize(int)
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
在View的onMeasure()方法源碼中,調(diào)用了setMeasuredDimension()方法來確定View的寬和高
View的源碼23000行。 : )
4.最后
寫的都比較表面,目前深入不了,基本就是看Android 群英傳第三章的讀寫筆記。也不曉得能不能對學(xué)習(xí)自定義View有些幫助。后面學(xué)習(xí),遇到一些知識點(diǎn)也會再補(bǔ)充。
下一篇學(xué)習(xí)了解一下Canvas和Paint都是干嘛的。