Android中View繪製流程以及invalidate()等相關方法分析
前言: 本文是我讀《Android核心剖析》第13章----View工作原理總結而成的,在此膜拜下作者 。同時真摯地向渴望瞭解
Android 框架層的網友,推薦這本書,希望你們能夠在Android開發裡學到更多的知識 。
整個View樹的繪圖流程是在ViewRoot.java類的performTraversals()函式展開的,該函式做的執行過程可簡單概況為
根據之前設置的狀態,判斷是否需要重新計算檢視大小(measure)、是否重新需要安置檢視的位置(layout)、以及是否需要重繪
(draw),其框架過程如下:
步驟其實為host.layout()
接下來溫習一下整個View樹的結構,對每個具體View物件的操作,其實就是個遞迴的實現。
關於這個 DecorView 根檢視的說明,可以參考我的這篇部落格:
流程一: mesarue()過程
主要作用:為整個View樹計算實際的大小,即設定實際的高(對應屬性:mMeasuredHeight)和寬(對應屬性:
mMeasureWidth),每個View的控制元件的實際寬高都是由父檢視和本身檢視決定的。
具體的呼叫鏈如下:
ViewRoot根物件地屬性mView(其型別一般為ViewGroup型別)呼叫measure()方法去計算View樹的大小,回撥
View/ViewGroup物件的onMeasure()方法,該方法實現的功能如下:
1、設定本View檢視的最終大小,該功能的實現通過呼叫setMeasuredDimension()方法去設定實際的高(對應屬性:
mMeasuredHeight)和寬(對應屬性:mMeasureWidth) ;
2 、如果該View物件是個ViewGroup型別,需要重寫該onMeasure()方法,對其子檢視進行遍歷的measure()過程。
2.1 對每個子檢視的measure()過程,是通過呼叫父類ViewGroup.java類裡的measureChildWithMargins()方法去
實現,該方法內部只是簡單地呼叫了View物件的measure()方法。(由於measureChildWithMargins()方法只是一個過渡
層更簡單的做法是直接呼叫View物件的measure()方法)。
整個measure呼叫流程就是個樹形的遞迴過程
measure函式原型為 View.java 該函式不能被過載
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//....
//回撥onMeasure()方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
//more
}
為了大家更好的理解,採用“二B程式設計師”的方式利用虛擬碼描述該measure流程
//回撥View視圖裡的onMeasure過程
private void onMeasure(int height , int width){
//設定該view的實際寬(mMeasuredWidth)高(mMeasuredHeight)
//1、該方法必須在onMeasure呼叫,否者報異常。
setMeasuredDimension(h , l) ;
//2、如果該View是ViewGroup型別,則對它的每個子View進行measure()過程
int childCount = getChildCount() ;
for(int i=0 ;i<childCount ;i++){
//2.1、獲得每個子View物件引用
View child = getChildAt(i) ;
//整個measure()過程就是個遞迴過程
//該方法只是一個過濾器,最後會呼叫measure()過程 ;或者 measureChild(child , h, i)方法都
measureChildWithMargins(child , h, i) ;
//其實,對於我們自己寫的應用來說,最好的辦法是去掉框架裡的該方法,直接呼叫view.measure(),如下:
//child.measure(h, l)
}
}
//該方法具體實現在ViewGroup.java裡 。
protected void measureChildWithMargins(View v, int height , int width){
v.measure(h,l)
}
流程二、 layout佈局過程:
主要作用 :為將整個根據子檢視的大小以及佈局引數將View樹放到合適的位置上。
具體的呼叫鏈如下:
host.layout()開始View樹的佈局,繼而回調給View/ViewGroup類中的layout()方法。具體流程如下
1 、layout方法會設定該View檢視位於父檢視的座標軸,即mLeft,mTop,mLeft,mBottom(呼叫setFrame()函式去實現)
接下來回調onLayout()方法(如果該View是ViewGroup物件,需要實現該方法,對每個子檢視進行佈局) ;
2、如果該View是個ViewGroup型別,需要遍歷每個子檢視chiildView,呼叫該子檢視的layout()方法去設定它的座標值。
layout函式原型為 ,位於View.java
/* final 識別符號 , 不能被過載 , 引數為每個檢視位於父檢視的座標軸
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
public final void layout(int l, int t, int r, int b) {
boolean changed = setFrame(l, t, r, b); //設定每個檢視位於父檢視的座標軸
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
onLayout(changed, l, t, r, b);//回撥onLayout函式 ,設定每個子檢視的佈局
mPrivateFlags &= ~LAYOUT_REQUIRED;
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
同樣地, 將上面layout呼叫流程,用虛擬碼描述如下:
// layout()過程 ViewRoot.java
// 發起layout()的"發號者"在ViewRoot.java裡的performTraversals()方法, mView.layout()
private void performTraversals(){
//...
View mView ;
mView.layout(left,top,right,bottom) ;
//....
}
//回撥View視圖裡的onLayout過程 ,該方法只由ViewGroup型別實現
private void onLayout(int left , int top , right , bottom){
//如果該View不是ViewGroup型別
//呼叫setFrame()方法設定該控制元件的在父檢視上的座標軸
setFrame(l ,t , r ,b) ;
//--------------------------
//如果該View是ViewGroup型別,則對它的每個子View進行layout()過程
int childCount = getChildCount() ;
for(int i=0 ;i<childCount ;i++){
//2.1、獲得每個子View物件引用
View child = getChildAt(i) ;
//整個layout()過程就是個遞迴過程
child.layout(l, t, r, b) ;
}
}
流程三、 draw()繪圖過程
由ViewRoot物件的performTraversals()方法呼叫draw()方法發起繪製該View樹,值得注意的是每次發起繪圖時,並不
會重新繪製每個View樹的檢視,而只會重新繪製那些“需要重繪”的檢視,View類內部變數包含了一個標誌位DRAWN,當該
檢視需要重繪時,就會為該View新增該標誌位。
呼叫流程 :
mView.draw()開始繪製,draw()方法實現的功能如下:
1 、繪製該View的背景
2 、為顯示漸變框做一些準備操作(見5,大多數情況下,不需要改漸變框)
3、呼叫onDraw()方法繪製檢視本身 (每個View都需要過載該方法,ViewGroup不需要實現該方法)
4、呼叫dispatchDraw ()方法繪製子檢視(如果該View型別不為ViewGroup,即不包含子檢視,不需要過載該方法)
值得說明的是,ViewGroup類已經為我們重寫了dispatchDraw ()的功能實現,應用程式一般不需要重寫該方法,但可以過載父類
函式實現具體的功能。
4.1 dispatchDraw()方法內部會遍歷每個子檢視,呼叫drawChild()去重新回撥每個子檢視的draw()方法(注意,這個
地方“需要重繪”的檢視才會呼叫draw()方法)。值得說明的是,ViewGroup類已經為我們重寫了dispatchDraw()的功能
實現,應用程式一般不需要重寫該方法,但可以過載父類函式實現具體的功能。
5、繪製滾動條
於是,整個呼叫鏈就這樣遞迴下去了。
同樣地,使用虛擬碼描述如下: // draw()過程 ViewRoot.java
// 發起draw()的"發號者"在ViewRoot.java裡的performTraversals()方法, 該方法會繼續呼叫draw()方法開始繪圖
private void draw(){
//...
View mView ;
mView.draw(canvas) ;
//....
}
//回撥View視圖裡的onLayout過程 ,該方法只由ViewGroup型別實現
private void draw(Canvas canvas){
//該方法會做如下事情
//1 、繪製該View的背景
//2、為繪製漸變框做一些準備操作
//3、呼叫onDraw()方法繪製檢視本身
//4、呼叫dispatchDraw()方法繪製每個子檢視,dispatchDraw()已經在Android框架中實現了,在ViewGroup方法中。
// 應用程式程式一般不需要重寫該方法,但可以捕獲該方法的發生,做一些特別的事情。
//5、繪製漸變框
}
//ViewGroup.java中的dispatchDraw()方法,應用程式一般不需要重寫該方法
@Override
protected void dispatchDraw(Canvas canvas) {
//
//其實現方法類似如下:
int childCount = getChildCount() ;
for(int i=0 ;i<childCount ;i++){
View child = getChildAt(i) ;
//呼叫drawChild完成
drawChild(child,canvas) ;
}
}
//ViewGroup.java中的dispatchDraw()方法,應用程式一般不需要重寫該方法
protected void drawChild(View child,Canvas canvas) {
// ....
//簡單的回撥View物件的draw()方法,遞迴就這麼產生了。
child.draw(canvas) ;
//.........
}
關於繪製背景圖片詳細的過程,請參考我的另外的部落格:
強調一點的就是,在這三個流程中,Google已經幫我們把draw()過程框架已經寫好了,自定義的ViewGroup只需要實現
measure()過程和layout()過程即可 。
這三種情況,最終會直接或間接呼叫到三個函式,分別為invalidate(),requsetLaytout()以及requestFocus() ,接著
這三個函式最終會呼叫到ViewRoot中的schedulTraversale()方法,該函式然後發起一個非同步訊息,訊息處理中呼叫
performTraverser()方法對整個View進行遍歷。
invalidate()方法 :
說明:請求重繪View樹,即draw()過程,假如檢視發生大小沒有變化就不會呼叫layout()過程,並且只繪製那些“需要重繪的”
檢視,即誰(View的話,只繪製該View ;ViewGroup,則繪製整個ViewGroup)請求invalidate()方法,就繪製該檢視。
一般引起invalidate()操作的函式如下:
1、直接呼叫invalidate()方法,請求重新draw(),但只會繪製呼叫者本身。
2、setSelection()方法 :請求重新draw(),但只會繪製呼叫者本身。
3、setVisibility()方法 : 當View可視狀態在INVISIBLE轉換VISIBLE時,會間接呼叫invalidate()方法,
繼而繪製該View。
4 、setEnabled()方法 : 請求重新draw(),但不會重新繪製任何檢視包括該呼叫者本身。
requestLayout()方法 :會導致呼叫measure()過程 和 layout()過程 。
說明:只是對View樹重新佈局layout過程包括measure()和layout()過程,不會呼叫draw()過程,但不會重新繪製
任何檢視包括該呼叫者本身。
一般引起invalidate()操作的函式如下:
1、setVisibility()方法:
當View的可視狀態在INVISIBLE/ VISIBLE 轉換為GONE狀態時,會間接呼叫requestLayout() 和invalidate方法。
同時,由於整個個View樹大小發生了變化,會請求measure()過程以及draw()過程,同樣地,只繪製需要“重新繪製”的檢視。
requestFocus()函式說明:
說明:請求View樹的draw()過程,但只繪製“需要重繪”的檢視。
下面寫個簡單的小Demo吧,主要目的是給大家演示繪圖的過程以及每個流程裡該做的一些功能。截圖如下:
1、 MyViewGroup.java 自定義ViewGroup型別
/**
* @author http://http://blog.csdn.net/qinjuning
*/
//自定義ViewGroup 物件
public class MyViewGroup extends ViewGroup{
private static String TAG = "MyViewGroup" ;
private Context mContext ;
public MyViewGroup(Context context) {
super(context);
mContext = context ;
init() ;
}
//xml定義的屬性,需要該建構函式
public MyViewGroup(Context context , AttributeSet attrs){
super(context,attrs) ;
mContext = context ;
init() ;
}
//為MyViewGroup新增三個子View
private void init(){
//呼叫ViewGroup父類addView()方法新增子View
//child 物件一 : Button
Button btn= new Button(mContext) ;
btn.setText("I am Button") ;
this.addView(btn) ;
//child 物件二 : ImageView
ImageView img = new ImageView(mContext) ;
img.setBackgroundResource(R.drawable.icon) ;
this.addView(img) ;
//child 物件三 : TextView
TextView txt = new TextView(mContext) ;
txt.setText("Only Text") ;
this.addView(txt) ;
//child 物件四 : 自定義View
MyView myView = new MyView(mContext) ;
this.addView(myView) ;
}
@Override
//對每個子View進行measure():設定每子View的大小,即實際寬和高
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
//通過init()方法,我們為該ViewGroup物件添加了三個檢視 , Button、 ImageView、TextView
int childCount = getChildCount() ;
Log.i(TAG, "the size of this ViewGroup is ----> " + childCount) ;
Log.i(TAG, "**** onMeasure start *****") ;
//獲取該ViewGroup的實際長和寬 涉及到MeasureSpec類的使用
int specSize_Widht = MeasureSpec.getSize(widthMeasureSpec) ;
int specSize_Heigth = MeasureSpec.getSize(heightMeasureSpec) ;
Log.i(TAG, "**** specSize_Widht " + specSize_Widht+ " * specSize_Heigth *****" + specSize_Heigth) ;
//設定本ViewGroup的寬高
setMeasuredDimension(specSize_Widht , specSize_Heigth) ;
for(int i=0 ;i<childCount ; i++){
View child = getChildAt(i) ; //獲得每個物件的引用
child.measure(50, 50) ; //簡單的設定每個子View物件的寬高為 50px , 50px
//或者可以呼叫ViewGroup父類方法measureChild()或者measureChildWithMargins()方法
//this.measureChild(child, widthMeasureSpec, heightMeasureSpec) ;
}
}
@Override
//對每個子View檢視進行佈局
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
//通過init()方法,我們為該ViewGroup物件添加了三個檢視 , Button、 ImageView、TextView
int childCount = getChildCount() ;
int startLeft = 0 ;//設定每個子View的起始橫座標
int startTop = 10 ; //每個子View距離父檢視的位置 , 簡單設定為10px吧 。 可以理解為 android:margin=10px ;
Log.i(TAG, "**** onLayout start ****") ;
for(int i=0 ;i<childCount ; i++){
View child = getChildAt(i) ; //獲得每個物件的引用
child.layout(startLeft, startTop, startLeft+child.getMeasuredWidth(), startTop+child.getMeasuredHeight()) ;
startLeft =startLeft+child.getMeasuredWidth() + 10; //校準startLeft值,View之間的間距設為10px ;
Log.i(TAG, "**** onLayout startLeft ****" +startLeft) ;
}
}
//繪圖過程Android已經為我們封裝好了 ,這兒只為了觀察方法呼叫程
protected void dispatchDraw(Canvas canvas){
Log.i(TAG, "**** dispatchDraw start ****") ;
super.dispatchDraw(canvas) ;
}
protected boolean drawChild(Canvas canvas , View child, long drawingTime){
Log.i(TAG, "**** drawChild start ****") ;
return super.drawChild(canvas, child, drawingTime) ;
}
}
2、MyView.java 自定義View型別,重寫onDraw()方法 ,
//自定義View物件
public class MyView extends View{
private Paint paint = new Paint() ;
public MyView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public MyView(Context context , AttributeSet attrs){
super(context,attrs);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
//設定該View大小為 80 80
setMeasuredDimension(50 , 50) ;
}
//存在canvas物件,即存在預設的顯示區域
@Override
public void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
Log.i("MyViewGroup", "MyView is onDraw ") ;
//加粗
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
paint.setColor(Color.RED);
canvas.drawColor(Color.BLUE) ;
canvas.drawRect(0, 0, 30, 30, paint);
canvas.drawText("MyView", 10, 40, paint);
}
}
主Activity只是顯示了該xml檔案,在此也不羅嗦了。 大家可以檢視該ViewGroup的Log仔細分析下View的繪製流程以及
相關方法的使用。第一次啟動後捕獲的Log如下,網上找了些資料,第一次View樹繪製過程會走幾遍,具體原因可能是某些
View 發生了改變,請求重新繪製,但這根本不影響我們的介面顯示效果 。
總的來說: 整個繪製過程還是十分十分複雜地,每個具體方法的實現都是我輩難以立即的,感到悲劇啊。對Android提
供的一些ViewGroup物件,比如LinearLayout、RelativeLayout佈局物件的實現也很有壓力。 本文重在介紹整個View樹的繪製
流程,希望大家在此基礎上,多接觸原始碼進行更深入地擴充套件。
//==========================================================
// 本次更新於 2012-05-20 晚
//==========================================================
Al Last,關於UI繪製的這塊,我部落格裡零零散散的敘說了一些知識,建議大家都能夠去看看:
1、 詳解measure過程以及如何設定View寬高的,建議看我的另外兩篇部落格:
2、詳解DecorView以及Activity視窗對應佈局地說明
3、詳解View繪製過程中如何繪製背景圖片:
希望各位能暫停你的腳步,踏踏實實學習。Best regards for U ~~ 。
//==========================================================
// 本次更新於 2012-10-29 晚
//==========================================================
相關推薦
Android中View繪製流程以及invalidate()等相關方法分析
前言: 本文是我讀《Android核心剖析》第13章----View工作原理總結而成的,在此膜拜下作者 。同時真摯地向渴望瞭解Android 框架層的網友,推薦這本書,希望你們能夠在Android開發裡學到更多的知識 。 整個V
android-進階(3)-自定義view(2)-Android中View繪製流程以及相關方法的分析
最近正在學自定義view,這篇文章主要講view的繪製流程和一些相關的方法,淺顯易懂,寫的非常好,忍不住就轉載了。 前言: 本文是我讀《Android核心剖析》第13章----View工作原理總結而成的,在此膜拜下作者 。
Android View 繪製流程 與invalidate 和postInvalidate 分析--從原始碼角度
整個View樹的繪製流程是在ViewRootImpl.java類的performTraversals()函式展開的,該函式做的執行過程可簡單概況為 根據之前設置的狀態,判斷是否需要重新計算檢視大小(measure)、是否重新需要佈局檢視的位置(layout
Android中view繪製過程
1 背景 看見沒有,如上圖中id為content的內容就是整個View樹的結構,所以對每個具體View物件的操作,其實就是個遞迴的實現。 前面《Android觸控式螢幕事件派發機制詳解與原始碼分析一(View篇)》文章的3-1小節說過And
Android開發——View繪製流程
網上講解View的繪製流程有很多優秀的文章。主要分為三個步驟:分別是measure、layout和draw。measure根據父佈局的尺寸以及自己想要的尺寸得到最終自己的尺寸,layout用於確定子View的位置,draw負責繪製自己。View分為View和Vi
Android中View和ViewGroup事件分發攔截機制完美分析
出自:http://www.cnblogs.com/linjzong/p/4191891.html Touch事件分發中只有兩個主角:ViewGroup和View。Activity的Touch事件事實上是呼叫它內部的ViewGroup的Touch事件,可以直接當成Vie
Android系統原始碼分析--View繪製流程之-setContentView
上一篇分析了四大元件之ContentProvider,這也是四大元件最後一個。因此,從這篇開始我們分析新的篇章--View繪製流程,View繪製流程在Android開發中佔有非常重要的位置,只要有檢視的顯示,都離不開View的繪製,所以瞭解View繪製原理對於應用開發以及系統的學習至關重要。由於View
Android中View自定義XML屬性詳解以及R.attr與R.styleable的區別
為View新增自定義XML屬性 Android中的各種Widget都提供了很多XML屬性,我們可以利用這些XML屬性在layout檔案中為Widget的屬性賦值。 如下所示: <TextView android:layout_wi
Android應用層View繪製流程之DecorView與ViewRootImpl
概述 一直對Android中View的整個繪製流程不是很瞭解,View是怎麼新增到Activity當中去的?當View中的內容發生改變的時候是怎樣執行介面的重新整理的?因此,今天準備從原始碼的角度來對View的整個繪製流程來進行分析,原始碼基於API25。由於
Android GUI之View繪製流程
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested =
Android View繪製流程
或許你已經看到過很多部落格總結的View的繪製流程的.我這篇部落格是跟著原始碼去看,對自己學到的知識加以印證.水平有限,僅供參考,如有錯誤,歡迎指正 我在之前的部落格就已經說明了Activity的啟動到顯示的相關流程,現在我們來看下View具體的工作流程. 上次
Android中View的繪製過程 onMeasure方法簡述 附有自定義View例子
/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use thi
Android-UI控制元件的繪製流程以及自定義控制元件的具體操作
View檢視安卓應用中非常重要的組成部分,它不僅是構成應用介面的基本單元,還是與使用者互動的最直接物件。檢視View作為介面的基本元素,是由View System進行管理的。 在Android中,檢視控制元件主要被分成兩大類,一類是單一控制元件View,另外一類
RGB簡述以及Android中設定透明、半透明等效果
一、RGB 簡單說說RGB,RGB由Red、Green、Blue三種成分色組成,每種顏色由2位16進位制數表示。 如:紅色 FF0000 表示紅全有(最滿),綠沒有,藍沒有 綠色
Android View繪製流程原始碼淺析
Android中View的繪製是一個面試的必答題,網上他人的博文也很多,本文旨在分析出大致流程。 廢話不說,read the fucking source code! 先從ActivityThread主執行緒啟動Activity說起,當Activity初始化 Window和
Android View 繪製流程(Draw) 完全解析
前言 前幾篇文章,筆者分別講述了DecorView,measure,layout流程等,接下來將詳細分析三大工作流程的最後一個流程——繪製流程。測量流程決定了View的大小,佈局流程決定了View的位置,那麼繪製流程將決定View的樣子,一個View該顯示什麼
解決 Android 中 View 的 setPivotX 和 setPivotY 不生效的問題以及設定縮放中心的方法
背景是這樣的:有一個需求要對下方的關注按鈕實現如下動畫,動畫的最後要根據滑動位置對關注按鈕進行縮放,縮放結束時整體大小為控制元件原始大小的90%,最終效果圖如下所示(模擬器是 4.2 的系統,最上面的沉浸式有點問題,忽略之): 如圖,關注按鈕向上滑
Android應用層View繪製流程與原始碼分析
1 背景 看見沒有,如上圖中id為content的內容就是整個View樹的結構,所以對每個具體View物件的操作,其實就是個遞迴的實現。 前面《Android觸控式螢幕事件派發機制詳解與原始碼分析一(View篇)》文章的3-1小節說過And
Android View繪製流程看這篇就夠了
前言 自定義View、多執行緒、網路,被認為是Android開發者必須牢固掌握的最基礎的三大基本功。Android View的繪製流程原理又是學好自定義View的理論基礎,所以掌握好View的繪製原理是Android開發進階中無法繞過的一道坎。而關乎到原理
android 中View, Window, Activity, WindowManager,ViewRoot幾者之間的關系
line 消息傳遞 post att 顯示 增加 調用 eas window對象 (1)View:最主要的UI組件,表示屏幕上的一個矩形區域。 (2)Window: 表示一個窗體,不一定有屏幕那麽大,能夠非常大也能夠非常小;