Android-View學習

參考郭霖大大的博客做的筆記~~~
Android LayoutInflater原理分析,帶你一步步深入瞭解View(一)

1 獲取

1)LayoutInflater layoutInflater = LayoutInflater.from(context);

2)LayoutInflater layoutInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

2 使用

@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mainLayout = (LinearLayout) findViewById(R.id.main_layout);
		LayoutInflater layoutInflater = LayoutInflater.from(this);
		View buttonLayout = layoutInflater.inflate(R.layout.button_layout, null);
		mainLayout.addView(buttonLayout);
	}

3 原理

LayoutInflater其實就是使用Android提供的pull解析方式來解析佈局文件的。第23行,調用了createViewFromTag()這個方法,並把節點名和參數傳了進去,它是用於根據節點名來創建View對象的。在createViewFromTag()方法的內部又會去調用createView()方法,然後使用反射的方式創建出View的實例並返回。

第21行同樣是createViewFromTag()方法來創建View的實例,然後還會在第24行遞歸調用rInflate()方法來查找這個View下的子元素,每次遞歸完成後則將這個View添加到父佈局當中。

這樣的話,把整個佈局文件都解析完成後就形成了一個完整的DOM結構,最終會把最頂層的根佈局返回,至此inflate()過程全部結束。

4 設置寬高注意

平時我們經常使用layout_width和layout_height來設置View的大小,並且一直都能正常工作,就好像這兩個屬性確實是用於設置View的大小的。而實際上則不然,它們其實是用於設置View在佈局中的大小的,也就是說,首先View必須存在於一個佈局中,之後如果將layout_width設置成match_parent表示讓View的寬度填充滿布局,如果設置成wrap_content表示讓View的寬度剛好可以包含其內容,如果設置成具體的數值則View的寬度會變成相應的數值。這也是爲什麼這兩個屬性叫作layout_width和layout_height,而不是width和height。

5 Activity佈局

任何一個Activity中顯示的界面其實主要都由兩部分組成,標題欄和內容佈局。標題欄就是在很多界面頂部顯示的那部分內容,比如剛剛我們的那個例子當中就有標題欄,可以在代碼中控制讓它是否顯示。而內容佈局就是一個FrameLayout,這個佈局的id叫作content,我們調用setContentView()方法時所傳入的佈局其實就是放到這個FrameLayout中的,這也是爲什麼這個方法名叫作setContentView(),而不是叫setView()。

Android視圖繪製流程完全解析,帶你一步步深入瞭解View(二)

每一個視圖的繪製過程都必須經歷三個最主要的階段,即onMeasure()、onLayout()和onDraw()。

1 onMeasure()

onMeasure()方法用於測量視圖的大小。View系統的繪製流程會從ViewRoot的performTraversals()方法中開始,在其內部調用View的measure()方法。measure()方法接收兩個參數,widthMeasureSpec和heightMeasureSpec,這兩個值分別用於確定視圖的寬度和高度的規格和大小。

MeasureSpec的值由specSize和specMode共同組成的,其中specSize記錄的是大小,specMode記錄的是規格。specMode一共有三種類型,如下所示:

1)EXACTLY

表示父視圖希望子視圖的大小應該是由specSize的值來決定的,系統默認會按照這個規則來設置子視圖的大小,開發人員當然也可以按照自己的意願設置成任意的大小。

2)AT_MOST

表示子視圖最多隻能是specSize中指定的大小,開發人員應該儘可能小得去設置這個視圖,並且保證不會超過specSize。系統默認會按照這個規則來設置子視圖的大小,開發人員當然也可以按照自己的意願設置成任意的大小。

3)UNSPECIFIED

表示開發人員可以將視圖按照自己的意願設置成任意的大小,沒有任何限制。這種情況比較少見,不太會用到。

onMeasure()方法是可以重寫的,也就是說,如果你不想使用系統默認的測量方式,可以按照自己的意願進行定製:

public class MyView extends View {
 
	......
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		setMeasuredDimension(200, 200);
	}
 
}

這樣的話就把View默認的測量流程覆蓋掉了,不管在佈局文件中定義MyView這個視圖的大小是多少,最終在界面上顯示的大小都將會是200*200。

需要注意的是,在setMeasuredDimension()方法調用之後,我們才能使用getMeasuredWidth()和getMeasuredHeight()來獲取視圖測量出的寬高,以此之前調用這兩個方法得到的值都會是0。

2 onLayout()

View中的onLayout()方法就是一個空方法,因爲onLayout()過程是爲了確定視圖在佈局中所在的位置,而這個操作應該是由佈局來完成的,即父視圖決定子視圖的顯示位置。ViewGroup中的onLayout()方法:

@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

可以看到,ViewGroup中的onLayout()方法是一個抽象方法,這就意味着所有ViewGroup的子類都必須重寫這個方法。比如,像LinearLayout、RelativeLayout等佈局,都是重寫了這個方法,然後在內部按照各自的規則對子視圖進行佈局的。

注意,getMeasureWidth()方法在measure()過程結束後就可以獲取到了,而getWidth()方法要在layout()過程結束後才能獲取到。同時,getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設置的,而getWidth()方法中的值則是通過視圖右邊的座標減去左邊的座標計算出來的。

3 onDraw()

ViewRoot中的代碼繼續執行並創建出一個Canvas對象,然後調用View的draw()方法來執行具體的繪製工作。

Android視圖狀態及重繪流程分析,帶你一步步深入瞭解View(三)

1 視圖狀態

1)enabled:表示當前視圖是否可用,不可用的視圖是無法響應onTouch事件的。

2)focused:

表示當前視圖是否獲得到焦點。通常情況下有兩種方法可以讓視圖獲得焦點,即通過鍵盤的上下左右鍵切換視圖,以及調用requestFocus()方法。而現在的Android手機幾乎都沒有鍵盤了,因此基本上只可以使用requestFocus()這個辦法來讓視圖獲得焦點了。而requestFocus()方法也不能保證一定可以讓視圖獲得焦點,它會有一個布爾值的返回值,如果返回true說明獲得焦點成功,返回false說明獲得焦點失敗。一般只有視圖在focusable和focusable in touch mode同時成立的情況下才能成功獲取焦點,比如說EditText。

3)window_focused:

表示當前視圖是否處於正在交互的窗口中,這個值由系統自動決定,應用程序不能進行改變。

4)selected

表示當前視圖是否處於選中狀態。一個界面當中可以有多個視圖處於選中狀態,調用setSelected()方法能夠改變視圖的選中狀態,傳入true表示選中,傳入false表示未選中。

5)pressed

表示當前視圖是否處於按下狀態。可以調用setPressed()方法來對這一狀態進行改變,傳入true表示按下,傳入false表示未按下。通常情況下這個狀態都是由系統自動賦值的,但開發者也可以自己調用這個方法來進行改變。

2 視圖重繪

invalidate()方法雖然最終會調用到performTraversals()方法中,但這時measure和layout流程是不會重新執行的,因爲視圖沒有強制重新測量的標誌位,而且大小也沒有發生過變化,所以這時只有draw流程可以得到執行。而如果你希望視圖的繪製流程可以完完整整地重新走一遍,就不能使用invalidate()方法,而應該調用requestLayout()了。

Android自定義View的實現方法,帶你一步步深入瞭解View(四)

如果說要按類型來劃分的話,自定義View的實現方式大概可以分爲三種,自繪控件、組合控件、以及繼承控件。

1 自繪控件

自繪控件的意思就是,這個View上所展現的內容全部都是我們自己繪製出來的。繪製的代碼是寫在onDraw()方法中的。

2 組合控件

組合控件的意思就是,我們並不需要自己去繪製視圖上顯示的內容,而只是用系統原生的控件就好了,但我們可以將幾個系統原生的控件組合到一起,這樣創建出的控件就被稱爲組合控件。

3 繼承控件

繼承控件的意思就是,我們並不需要自己重頭去實現一個控件,只需要去繼承一個現有的控件,然後在這個控件上增加一些新的功能,就可以形成一個自定義的控件了。這種自定義控件的特點就是不僅能夠按照我們的需求加入相應的功能,還可以保留原生控件的所有功能。

4 構造函數

無論是我們繼承系統View還是直接繼承View,都需要對構造函數進行重寫,構造函數有多個,至少要重寫其中一個才行。

View的構造函數有4種重載:

// 如果View是在Java代碼裏面new的,則調用第一個構造函數
 public CarsonView(Context context) {
        super(context);
    }

// 如果View是在.xml裏聲明的,則調用第二個構造函數
// 自定義屬性是從AttributeSet參數傳進來的
    public  CarsonView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

// 不會自動調用
// 一般是在第二個構造函數裏主動調用
// 如View有style屬性時
    public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //API21之後才使用
    // 不會自動調用
    // 一般是在第二個構造函數裏主動調用
    // 如View有style屬性時
    public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章