當前位置:首頁 » 安卓系統 » androidview子view

androidview子view

發布時間: 2022-10-08 15:59:37

❶ Android - View 繪制流程

我們知道,在 Android 中,View 繪制主要包含 3 大流程:

Android 中,主要有兩種視圖: View 和 ViewGroup ,其中:

雖然 ViewGroup 繼承於 View ,但是在 View 繪制三大流程中,某些流程需要區分 View 和 ViewGroup ,它們之間的操作並不完全相同,比如:

對 View 進行測量,主要包含兩個步驟:

對於第一個步驟,即求取 View 的 MeasureSpec ,首先我們來看下 MeasureSpec 的源碼定義:

MeasureSpec 是 View 的一個公有靜態內部類,它是一個 32 位的 int 值,高 2 位表示 SpecMode(測量模式),低 30 位表示 SpecSize(測量尺寸/測量大小)。
MeasureSpec 將兩個數據打包到一個 int 值上,可以減少對象內存分配,並且其提供了相應的工具方法可以很方便地讓我們從一個 int 值中抽取出 View 的 SpecMode 和 SpecSize。

一個 MeasureSpec 表達的是:該 View 在該種測量模式(SpecMode)下對應的測量尺寸(SpecSize)。其中,SpecMode 有三種類型:

對 View 進行測量,最關鍵的一步就是計算得到 View 的 MeasureSpec ,子View 在創建時,可以指定不同的 LayoutParams (布局參數), LayoutParams 的源碼主要內容如下所示:

其中:

LayoutParams 會受到父容器的 MeasureSpec 的影響,測量過程會依據兩者之間的相互約束最終生成子View 的 MeasureSpec ,完成 View 的測量規格。

簡而言之,View 的 MeasureSpec 受自身的 LayoutParams 和父容器的 MeasureSpec 共同決定( DecorView 的 MeasureSpec 是由自身的 LayoutParams 和屏幕尺寸共同決定,參考後文)。也因此,如果要求取子View 的 MeasureSpec ,那麼首先就需要知道父容器的 MeasureSpec ,層層逆推而上,即最終就是需要知道頂層View(即 DecorView )的 MeasureSpec ,這樣才能一層層傳遞下來,這整個過程需要結合 Activity 的啟動過程進行分析。

我們知道,在 Android 中, Activity 是作為視圖組件存在,主要就是在手機上顯示視圖界面,可以供用戶操作, Activity 就是 Andorid 中與用戶直接交互最多的系統組件。

Activity 的基本視圖層次結構如下所示:

Activity 中,實際承載視圖的組件是 Window (更具體來說為 PhoneWindow ),頂層View 是 DecorView ,它是一個 FrameLayout , DecorView 內部是一個 LinearLayout ,該 LinearLayout 由兩部分組成(不同 Android 版本或主題稍有差異): TitleView 和 ContentView ,其中, TitleView 就是標題欄,也就是我們常說的 TitleBar 或 ActionBar , ContentView 就是內容欄,它也是一個 FrameLayout ,主要用於承載我們的自定義根布局,即當我們調用 setContentView(...) 時,其實就是把我們自定義的布局設置到該 ContentView 中。

當 Activity 啟動完成後,最終就會渲染出上述層次結構的視圖。

因此,如果我們要求取得到子View 的 MeasureSpec ,那麼第一步就是求取得到頂層View(即 DecorView )的 MeasureSpec 。大致過程如下所示:

經過上述步驟求取得到 View 的 MeasureSpec 後,接下來就可以真正對 View 進行測量,求取 View 的最終測量寬/高:

Android 內部對視圖進行測量的過程是由 View#measure(int, int) 方法負責的,但是對於 View 和 ViewGroup ,其具體測量過程有所差異。

因此,對於測量過程,我們分別對 View 和 ViewGroup 進行分析:

綜上,無論是對 View 的測量還是 ViewGroup 的測量,都是由 View#measure(int widthMeasureSpec, int heightMeasureSpec) 方法負責,然後真正執行 View 測量的是 View 的 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法。

具體來說, View 直接在 onMeasure(...) 中測量並設置自己的最終測量寬/高。在默認測量情況下, View 的測量寬/高由其父容器的 MeasureSpec 和自身的 LayoutParams 共同決定,當 View 自身的測量模式為 LayoutParams.UNSPECIFIED 時,其測量寬/高為 android:minWidth / android:minHeight 和其背景寬/高之間的較大值,其餘情況皆為自身 MeasureSpec 指定的測量尺寸。

而對於 ViewGroup 來說,由於布局特性的豐富性,只能自己手動覆寫 onMeasure(...) 方法,實現自定義測量過程,但是總的思想都是先測量 子View 大小,最終才能確定自己的測量大小。

當確定了 View 的測量大小後,接下來就可以來確定 View 的布局位置了,也即將 View 放置到屏幕具體哪個位置。

View 的布局過程由 View#layout(...) 負責,其源碼如下:

View#layout(...) 主要就做了兩件事:

ViewGroup 的布局流程由 ViewGroup#layout(...) 負責,其源碼如下:

可以看到, ViewGroup#layout(...) 最終也是通過 View#layout(...) 完成自身的布局過程,一個注意的點是, ViewGroup#layout(...) 是一個 final 方法,因此子類無法覆寫該方法,主要是 ViewGroup#layout(...) 方法內部對子視圖動畫效果進行了相關設置。

由於 ViewGroup#layout(...) 內部最終調用的還是 View#layout(...) ,因此, ViewGroup#onLayout(...) 就會得到回調,用於處理 子View 的布局放置,其源碼如下:

由於不同的 ViewGroup ,其布局特性不同,因此 ViewGroup#onLayout(...) 是一個抽象方法,交由 ViewGroup 子類依據自己的布局特性,擺放其 子View 的位置。

當 View 的測量大小,布局位置都確定後,就可以最終將該 View 繪制到屏幕上了。

View 的繪制過程由 View#draw(...) 方法負責,其源碼如下:

其實注釋已經寫的很清楚了, View#draw(...) 主要做了以下 6 件事:

我們知道,在 Activity 啟動過程中,會調用到 ActivityThread.handleResumeActivity(...) ,該方法就是 View 視圖繪制的起始之處:

可以看到, ActivityThread.handleResumeActivity(...) 主要就是獲取到當前 Activity 綁定的 ViewManager ,最後調用 ViewManager.addView(...) 方法將 DecorView 設置到 PhoneWindow 上,也即設置到當前 Activity 上。 ViewManager 是一個介面, WindowManager 繼承 ViewManager ,而 WindowManagerImpl 實現了介面 WindowManager ,此處的 ViewManager.addView(...) 實際上調用的是 WindowManagerImpl.addView(...) ,源碼如下所示:

WindowManagerImpl.addView(...) 內部轉發到 WindowManagerGlobal.addView(...) :

在 WindowManagerGlobal.addView(...) 內部,會創建一個 ViewRootImpl 實例,然後調用 ViewRootImpl.setView(...) 將 ViewRootImpl 與 DecorView 關聯到一起:

ViewRootImpl.setView(...) 內部首先關聯了傳遞過來的 DecorView (通過屬性 mView 指向 DecorView 即可建立關聯),然後最終調用 requestLayout() ,而 requestLayout() 內部又會調用方法 scheleTraversals() :

ViewRootImpl.scheleTraversals() 內部主要做了兩件事:

Choreographer.postCallback(...) 會申請一次 VSYNC 中斷信號,當 VSYNC 信號到達時,便會回調 Choreographer.doFrame(...) 方法,內部會觸發已經添加的回調任務, Choreographer 的回調任務有以下四種類型:

因此, ViewRootImpl.scheleTraversals(...) 內部通過 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null) 發送的非同步視圖渲染消息就會得到回調,即回調 mTra

❷ android 開發viewpager滾動控制項建立了以後,其中子view包含其他控制項,如textview怎麼去修改子控制項內容

View mView = getLayoutInflater().inflate(R.layout.view,null);
mVIewPager.setAdapter(adapter);
適配器把兩個View裝進去,然後通過每個子View的findViewById方法找到子View的子控制項
TextView mTextView = mView1.findViewById(R.id.textView);
mTextView.setText("..........");

❸ 誰可以解釋下,android事件分發為什麼要設計成從根view到子view,而不是從子vie

Android事件傳遞流程在網上可以找到很多資料,FrameWork層輸入事件和消費事件,可以參考:
Touch事件派發過程詳解
這篇blog闡述了底層是如何處理屏幕輸,並往上傳遞的。Touch事件傳遞到Activity的DecorView時,往下走就是ViewGroup和子View之間的事件傳遞,可以參考郭神的這兩篇博客
Android事件分發機制完全解析,帶你從源碼的角度徹底理解(上)
Android事件分發機制完全解析,帶你從源碼的角度徹底理解(下)
郭神的兩篇博客清楚明白地說明了View之間事件傳遞的大方向,但是具體的一些晦暗的細節闡述較少,本文主要是總結這兩篇博客的同時,側重於兩點:
事件分發過程中一些細節到底如何實現的?
子view到底如何和父View搶事件,父View又是如何攔截事件不發送給子View,以及如果我們需要處理這種混亂的關系才能讓兩者和諧相處?。
MotionEvent抽象
要明白View的事件傳遞,很有必要先說一下Touch事件是如何在Android系統中抽象的,這主要使用的就是MotionEvent。這個類經歷了幾次重大的修改,一次是在2.x版本支持多點觸摸,一次是4.x將大部分代碼甩給native層處理。
一次簡單的事件
我們先舉個栗子來說明一次完整的事件,用戶觸屏 滑動 到手機離開屏幕,這認為是一次完整動作序列(movement traces)。一個動作序列中包含很多動作Action,比如在用戶按下時,會封裝一個MotionEvent,分發給視圖樹,我們可以通過motionevent.getAction拿到這個動作是ACTION_DOWN。同樣,在手指抬起時,我們可以接收到Action類型是Action_UP的MotionEvent。對於滑動(MOVE)這個操作,Android為了從效率出發,會將多個MOVE動作打包到一個MotionEvent中。通過getX getY可以獲取當前的坐標,如果要訪問打包的緩存數據,可以通過getHistorical**()函數來獲取。
加入多點觸摸
對於單點的操作來看,MotionEvent顯得比較簡單,但是考慮引入多點觸摸呢?我們定義一個接觸點為(Pointer)。每一個觸摸點Pointer都會有一個當次動作序列的唯一Id和Index.MotionEvent中多個手指的操作API大部分都是通過pointerindex來進行的,如:獲取不同Pointer的觸碰位置,getX(int pointerIndex);獲取PointerId等等。大部分情況下,pointerid == pointeridex。
我們從onTouch接受到一個MotionEvent,怎麼拿到多個觸碰點的信息?為了解開筆者剛開始學習這部分知識時的困惑,我們首先樹立起一種概念:一個MotionEvent只允許有一個Action(動作),而且這個Action會包含觸發這次Action的觸碰點信息,對於MOVE操作來說,一定是當前所有觸碰點都在動。只有ACTION_POINTER_DOWN這類事件事件會在Action裡面指定是哪一個POINTER按下。
在MotionEvent的底層實現中,是通過一個16位來存儲Action和Pointer信息(PointerIndex)。低8位表示Action,理論上可以表示255種動作類型;高8位表示觸發這個Action的PointerIndex,理論上Android最多可以支持255點同時觸摸,但是在上層代碼使用的時候,默認多點最多存在32個,不然事件在分發的時候會有問題。
ACTION_DOWN OR ACTION_POINTER_DOWN:
這兩個按下操作的區別是ACTION_DOWN是一個系列動作的開始,而ACTION_POINTER_DOWN是在一個系列動作中間有另外一個觸碰點觸碰到屏幕。
這部分詳細的描述,請參考:
android觸控,先了解MotionEvent
到這里,鋪墊終於結束了,我們開始直奔主題。
View的事件傳遞
Android的Touch事件傳遞到Activity頂層的DecorView(一個FrameLayout)之後,會通過ViewGroup一層層往視圖樹的上面傳遞,最終將事件傳遞給實際接收的View。下面給出一些重要的方法。如果你對這個流程比較熟悉的話,可以跳過這里,直接進入第二部分。
dispatchTouchEvent
事件傳遞到一個ViewGroup上面時,會調用dispatchTouchEvent。代碼有刪減
public boolean dispatchTouchEvent(MotionEvent ev) {

boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;

// Attention 1 :在按下時候清除一些狀態
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
//注意這個方法
resetTouchState();
}

// Attention 2:檢查是否需要攔截
final boolean intercepted;
//如果剛剛按下 或者 已經有子View來處理
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// 不是一個動作序列的開始 同時也沒有子View來處理,直接攔截
intercepted = true;
}

//事件沒有取消 同時沒有被當前ViewGroup攔截,去找是否有子View接盤
if (!canceled && !intercepted) {
//如果這是一系列動作的開始 或者有一個新的Pointer按下 我們需要去找能夠處理這個Pointer的子View
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down

//上面說的觸碰點32的限制就是這里導致
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;

final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);

//對當前ViewGroup的所有子View進行排序,在上層的放在開始
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);

// canViewReceivePointerEvents visible的View都可以接受事件
// isTransformedTouchPointInView 計算是否落在點擊區域上
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}

//能夠處理這個Pointer的View是否已經處理之前的Pointer,那麼把
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
} }
//Attention 3 : 直接發給子View
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
= true;
break;
}

}
}

}
}

// 前面已經找到了接收事件的子View,如果為NULL,表示沒有子View來接手,當前ViewGroup需要來處理
if (mFirstTouchTarget == null) {
// ViewGroup處理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {

if() {
//ignore some code
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}

}
return handled;
}

上面代碼中的Attention在後面部分將會涉及,重點注意。
這里需要指出一點的是,一系列動作中的不同Pointer可以分配給不同的View去響應。ViewGroup會維護一個PointerId和處理View的列表TouchTarget,一個TouchTarget代表一個可以處理Pointer的子View,當然一個View可以處理多個Pointer,比如兩根手指都在某一個子View區域。TouchTarget內部使用一個int來存儲它能處理的PointerId,一個int32位,這也就是上層為啥最多隻能允許同時最多32點觸碰。
看一下Attention 3 處的代碼,我們經常說view的dispatchTouchEvent如果返回false,那麼它就不能系列動作後面的動作,這是為啥呢?因為Attention 3處如果返回false,那麼它不會被記錄到TouchTarget中,ViewGroup認為你沒有能力處理這個事件。
這里可以看到,ViewGroup真正處理事件是在dispatchTransformedTouchEvent裡面,跟進去看看:
dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {

//沒有子類處理,那麼交給viewgroup處理
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}

handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}

可以看到這里不管怎麼樣,都會調用View的dispatchTouchEvent,這是真正處理這一次點擊事件的地方。
dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
//先走View的onTouch事件,如果onTouch返回True
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}

我們給View設置的onTouch事件處在一個較高的優先順序,如果onTouch執行返回true,那麼就不會去走view的onTouchEvent,而我們一些點擊事件都是在onTouchEvent中處理的,這也是為什麼onTouch中返回true,view的點擊相關事件不會被處理。

❹ Android開發 ,為什麼ViewFlipper 的子View的點擊事件會沒有效果

先得到父類,然後根據父類getchild就可以得到子類的對象了。android裡面view的關系是個樹狀結果,遍歷一下就能把你需要的抓出來

❺ Android View知識

1, View是除了Android四大組件外,最常用的東西
2,什麼是View:
View是android中所有控制項的父類,比如TextView,LinearLayout等等
其中LinearLayout繼承自控制項組ViewGroup,當然ViewGroup也是繼承自View

3,View的位置
top:左上角縱坐標
left:左上角橫坐標
right:右下角橫坐標
bottom:右下角縱坐標
如下圖:

4,view的MotionEvent和TouchSlop
4.1MotionEvent:
ACTION_DOWN:手指接觸屏幕
ACTION_MOVE:手指在屏幕上滑動
ACTION_UP:手指離開屏幕。

4.2TouchSlop
處理滑動時的過濾條件,簡單來說就是,手指在屏幕上的一次操作算不算滑動。
系統默認值:ViewConfiguration.get(context).getScaledTouchSlop()

5,getX()getY()和getRawX()和getRawY()
前兩者相對於父控制項View 後兩者相對於手機屏幕

6,VelocityTracker,GestureDetector,Scroller
6.1VelocityTracker:滑動速度,在view的ontouch事件中,查看速度
6.2 GestureDetector:手勢判斷,比如長按,點擊,雙擊等,很少用,可以用 ontouch事件來代替
6.3Scroller:彈性滑動對象,實現view的位置改變等
7,原始滑動方式
7.1:ScrollerTo和Scroller By()
實現簡單 但是只能滑動view裡面的子元素

7.2:改變view參數
實現復雜,但是如果view有交互,這種方式比較好

7.3:動畫
適用於沒有交互的,或者動畫復雜的view的滑動

8View的事件分發:
8.1:Activity-window-View
8.2:view中是從父到子,也就是從外到內,都不處理,返回給最頂級
8.3:ViewGroup默認不攔截任何事件,默認返回false
8.4:分發方法:dispatchTouchEvent,OnInterceptTouchEvent,OnTouchEvent
dispatchTouchEvent:分發
OnInterceptTouchEvent:攔截
OnTouchEvent:處理點擊事件

❻ android自定義viewGroup中怎麼獲得子view,並測量到子view的寬高

java">Viewview=viewGroup.getChildAt(0);
view.getWidth();
view.getHeight();

❼ android引用的view怎麼在裡面聲明新的子view

你這只是載入了一張圖片,listview要設置Adapter,自己寫個Adapter來裝載網路上的圖片,然後再把這個adapter設置到ListView里

❽ android自定義viewGroup中怎麼獲得子view,並測量到子view的寬高

findviewbyid就可以了,或者viewgroup。getview(index);等view繪制後就可以獲取到view的寬高了。

❾ Android寶典|View必考知識點總結

我們知道,Activity 是在 ActivityThread 的 performLaunchActivity 中進行創建的,在創建完成之後就會調用其 attach 方法,它是先於 onCreate、onStart、onResume 等生命周期函數的,因此將 attach 方法作為這篇文章主線的開頭:

attach() 方法就是 new 一個 PhoneWindow 並且關聯 WindowManager。

接下來就到了 onCreate 方法:

這一步就是把我們的布局文件解析成 View 塞到 DecorView 的一個 id 為 R.id.content 的 ContentView 中,DecorView 本身是一個 FrameLayout,它還承載了 StatusBar、NavigationBar 。

然後在 handleResumeActivity 中,通過 WindowManager 的 addView 方法把 DecorView 添加進去,實際實現是 WindowManagerImpl 的 addView 方法,它裡面再通過 WindowManagerGlobal 的實例去 addView 的,在它裡面就會 new 一個 ViewRootImpl,也就是說最後是把 DecorView 傳給了 ViewRootImpl 的 setView 方法。ViewRootImpl 是 DecorView 的管理者,它負責 View 樹的測量、布局、繪制,以及通過 Choreographer 來控制 View 的刷新。

WMS 是所有 Window 窗口的管理員,負責 Window 的添加和刪除、Surface 的管理和事件派發等等,因此每一個 Activity 中的 PhoneWindow 對象如果需要顯示等操作,就必須要與 WMS 交互才能進行。

在 ViewRootImpl 的 setView 方法中,會調用 requestLayout,並且通過 WindowSession 的 addToDisplay 與 WMS 進行交互。WMS 會為每一個 Window 關聯一個 WindowStatus。

SurfaceFlinger 主要是進行 Layer 的合成和渲染。

在 WindowStatus 中,會創建 SurfaceSession,SurfaceSession 會在 Native 層構造一個 SurfaceComposerClient 對象,它是應用程序與 SurfaceFlinger 溝通的橋梁。

經過步驟四和步驟五之後,ViewRootImpl 與 WMS、SurfaceFlinger 都已經建立起連接,但此時 View 還沒顯示出來,我們知道,所有的 UI 最終都要通過 Surface 來顯示,那麼 Surface 是什麼時候創建的呢?

這就要回到前面所說的 ViewRootImpl 的 requestLayout 方法了,首先會 checkThread 檢查是否是主線程,然後調用 scheleTraversals 方法,scheleTraversals 方法會先設置同步屏障,然後通過 Choreographer 類在下一幀到來時去執行 doTraversal 方法。簡單來說,Choreographer 內部會接受來自 SurfaceFlinger 發出的 Vsync 垂直同步信號,這個信號周期一般是 16ms 左右。doTraversal 方法首先會先移除同步屏障,然後 performTraversals 真正進行 View 的繪制流程,即調用 performMeasure、performLayout、performDraw。不過在它們之前,會先調用 relayoutWindow 通過 WindowSession 與 WMS 進行交互,即把 Java 層創建的 Surface 與 Native 層的 Surface 關聯起來。

接下來就是正式繪制 View 了,從 performTraversals 開始,Measure、Layout、Draw 三步走。

第一步是獲取 DecorView 的寬高的 MeasureSpec 然後執行 performMeasure 流程。MeasureSpec 簡單來說就是一個 int 值,高 2 位表示測量模式,低 30 位用來表示大小。策略模式有三種,EXACTLY、AT_MOST、UNSPECIFIED。EXACTLY 對應為 match_parent 和具體數值的情況,表示父容器已經確定 View 的大小;AT_MOST 對應 wrap_content,表示父容器規定 View 最大隻能是 SpecSize;UNSPECIFIED 表示不限定測量模式,父容器不對 View 做任何限制,這種適用於系統內部。接著說,performMeasure 中會去調用 DecorView 的 measure 方法,這個是 View 裡面的方法並且是 final 的,它裡面會把參數透傳給 onMeasure 方法,這個方法是可以重寫的,也就是我們可以干預 View 的測量過程。在 onMeasure 中,會通過 getDefaultSize 獲取到寬高的默認值,然後調用 setMeasureDimension 將獲取的值進行設置。在 getDefaultSize 中,無論是 EXACTLY 還是 AT_MOST,都會返回 MeasureSpec 中的大小,這個 SpecSize 就是測量後的最終結果。至於 UNSPECIFIED 的情況,則會返回一個建議的最小值,這個值和子元素設置的最小值以及它的背景大小有關。從這個默認實現來看,如果我們自定義一個 View 不重寫它的 onMeasure 方法,那麼 warp_content 和 match_parent 一樣。所以 DecorView 重寫了 onMeasure 函數,它本身是一個 FrameLayout,所以最後也會調用到 FrameLayout 的 onMeasure 函數,作為一個 ViewGroup,都會遍歷子 View 並調用子 View 的 measure 方法。這樣便實現了層層遞歸調用到了每個子 View 的 onMeasure 方法進行測量。

第二步是執行 performLayout 的流程,也就是調用到 DecorView 的 layout 方法,也就是 View 裡面的方法,如果 View 大小發生變化,則會回調 onSizeChanged 方法,如果 View 狀態發生變化,則會回調 onLayout 方法,這個方法在 View 中是空實現,因此需要看 DecorView 的父容器 FrameLayout 的 onLayout 方法,這個方法就是遍歷子 View 調用其 layout 方法進行布局,子 View 的 layout 方法被調用的時候,它的 onLayout 方法又會被調用,這樣就布局完了所有的 View。

第三步就是 performDraw 方法了,裡面會調用 drawSoftware 方法,這個方法需要先通過 mSurface lockCanvas 獲取一個 Canvas 對象,作為參數傳給 DecorView 的 draw 方法。這個方法調用的是 View 的 draw 方法,先繪制 View 背景,然後繪制 View 的內容,如果有子 View 則會調用子 View 的 draw 方法,層層遞歸調用,最終完成繪制。

完成這三步之後,會在 ActivityThread 的 handleResumeActivity 最後調用 Activity 的 makeVisible,這個方法就是將 DecorView 設置為可見狀態。

https://juejin.im/post/5c67c1e16fb9a04a05403549

https://juejin.im/post/5bf16ff5f265da6141712acc

熱點內容
shell腳本調用sql腳本 發布:2025-01-22 20:53:51 瀏覽:427
解壓洗浴 發布:2025-01-22 20:51:01 瀏覽:474
tplink雲伺服器 發布:2025-01-22 20:32:35 瀏覽:146
videots文件夾 發布:2025-01-22 20:31:40 瀏覽:312
apm編程 發布:2025-01-22 20:08:08 瀏覽:762
中乙資料庫 發布:2025-01-22 20:08:08 瀏覽:841
a8源碼網 發布:2025-01-22 20:06:42 瀏覽:181
新聞頭條源碼 發布:2025-01-22 20:06:37 瀏覽:917
社保卡的交易密碼怎麼修改密碼 發布:2025-01-22 20:05:09 瀏覽:693
如何把舊安卓機改造為游戲機 發布:2025-01-22 19:54:35 瀏覽:624