androidview
Ⅰ Android UI繪制之View繪制的工作原理
這是AndroidUI繪制流程分析的第二篇文章,主要分析界面中View是如何繪制到界面上的具體過程。
ViewRoot 對應於 ViewRootImpl 類,它是連接 WindowManager 和 DecorView 的紐帶,View的三大流程均是通過 ViewRoot 來完成的。在 ActivityThread 中,當 Activity 對象被創建完畢後,會將 DecorView 添加到 Window 中,同時會創建 ViewRootImpl 對象,並將 ViewRootImpl 對象和 DecorView 建立關聯。
measure 過程決定了 View 的寬/高, Measure 完成以後,可以通過 getMeasuredWidth 和 getMeasuredHeight 方法來獲取 View 測量後的寬/高,在幾乎所有的情況下,它等同於View的最終的寬/高,但是特殊情況除外。 Layout 過程決定了 View 的四個頂點的坐標和實際的寬/高,完成以後,可以通過 getTop、getBottom、getLeft 和 getRight 來拿到View的四個頂點的位置,可以通過 getWidth 和 getHeight 方法拿到View的最終寬/高。 Draw 過程決定了 View 的顯示,只有 draw 方法完成後 View 的內容才能呈現在屏幕上。
DecorView 作為頂級 View ,一般情況下,它內部會包含一個豎直方向的 LinearLayout ,在這個 LinearLayout 裡面有上下兩個部分,上面是標題欄,下面是內容欄。在Activity中,我們通過 setContentView 所設置的布局文件其實就是被加到內容欄中的,而內容欄id為 content 。可以通過下面方法得到 content:ViewGroup content = findViewById(R.android.id.content) 。通過 content.getChildAt(0) 可以得到設置的 view 。 DecorView 其實是一個 FrameLayout , View 層的事件都先經過 DecorView ,然後才傳遞給我們的 View 。
MeasureSpec 代表一個32位的int值,高2位代表 SpecMode ,低30位代表 SpecSize , SpecMode 是指測量模式,而 SpecSize 是指在某種測量模式下的規格大小。
SpecMode 有三類,如下所示:
UNSPECIFIED
EXACTLY
AT_MOST
LayoutParams需要和父容器一起才能決定View的MeasureSpec,從而進一步決定View的寬/高。
對於頂級View,即DecorView和普通View來說,MeasureSpec的轉換過程略有不同。對於DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同確定;
對於普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同決定;
MeasureSpec一旦確定,onMeasure就可以確定View的測量寬/高。
小結一下
當子 View 的寬高採用 wrap_content 時,不管父容器的模式是精確模式還是最大模式,子 View 的模式總是最大模式+父容器的剩餘空間。
View 的工作流程主要是指 measure 、 layout 、 draw 三大流程,即測量、布局、繪制。其中 measure 確定 View 的測量寬/高, layout 確定 view 的最終寬/高和四個頂點的位置,而 draw 則將 View 繪制在屏幕上。
measure 過程要分情況,如果只是一個原始的 view ,則通過 measure 方法就完成了其測量過程,如果是一個 ViewGroup ,除了完成自己的測量過程外,還會遍歷調用所有子元素的 measure 方法,各個子元素再遞歸去執行這個流程。
如果是一個原始的 View,那麼通過 measure 方法就完成了測量過程,在 measure 方法中會去調用 View 的 onMeasure 方法,View 類裡面定義了 onMeasure 方法的默認實現:
先看一下 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 方法的源碼:
可以看到, getMinimumWidth 方法獲取的是 Drawable 的原始寬度。如果存在原始寬度(即滿足 intrinsicWidth > 0),那麼直接返回原始寬度即可;如果不存在原始寬度(即不滿足 intrinsicWidth > 0),那麼就返回 0。
接著看最重要的 getDefaultSize 方法:
如果 specMode 為 MeasureSpec.UNSPECIFIED 即未指定模式,那麼返回由方法參數傳遞過來的尺寸作為 View 的測量寬度和高度;
如果 specMode 不是 MeasureSpec.UNSPECIFIED 即是最大模式或者精確模式,那麼返回從 measureSpec 中取出的 specSize 作為 View 測量後的寬度和高度。
看一下剛才的表格:
當 specMode 為 EXACTLY 或者 AT_MOST 時,View 的布局參數為 wrap_content 或者 match_parent 時,給 View 的 specSize 都是 parentSize 。這會比建議的最小寬高要大。這是不符合我們的預期的。因為我們給 View 設置 wrap_content 是希望View的大小剛好可以包裹它的內容。
因此:
如果是一個 ViewGroup,除了完成自己的 measure 過程以外,還會遍歷去調用所有子元素的 measure 方法,各個子元素再遞歸去執行 measure 過程。
ViewGroup 並沒有重寫 View 的 onMeasure 方法,但是它提供了 measureChildren、measureChild、measureChildWithMargins 這幾個方法專門用於測量子元素。
如果是 View 的話,那麼在它的 layout 方法中就確定了自身的位置(具體來說是通過 setFrame 方法來設定 View 的四個頂點的位置,即初始化 mLeft , mRight , mTop , mBottom 這四個值), layout 過程就結束了。
如果是 ViewGroup 的話,那麼在它的 layout 方法中只是確定了 ViewGroup 自身的位置,要確定子元素的位置,就需要重寫 onLayout 方法;在 onLayout 方法中,會調用子元素的 layout 方法,子元素在它的 layout 方法中確定自己的位置,這樣一層一層地傳遞下去完成整個 View 樹的 layout 過程。
layout 方法的作用是確定 View 本身的位置,即設定 View 的四個頂點的位置,這樣就確定了 View 在父容器中的位置;
onLayout 方法的作用是父容器確定子元素的位置,這個方法在 View 中是空實現,因為 View 沒有子元素了,在 ViewGroup 中則進行抽象化,它的子類必須實現這個方法。
1.繪制背景( background.draw(canvas); );
2.繪制自己( onDraw );
3.繪制 children( dispatchDraw(canvas) );
4.繪制裝飾( onDrawScrollBars )。
dispatchDraw 方法的調用是在 onDraw 方法之後,也就是說,總是先繪制自己再繪制子 View 。
對於 View 類來說, dispatchDraw 方法是空實現的,對於 ViewGroup 類來說, dispatchDraw 方法是有具體實現的。
通過 dispatchDraw 來傳遞的。 dispatchDraw 會遍歷調用子元素的 draw 方法,如此 draw 事件就一層一層傳遞了下去。dispatchDraw 在 View 類中是空實現的,在 ViewGroup 類中是真正實現的。
如果一個 View 不需要繪制任何內容,那麼就設置這個標記為 true,系統會進行進一步的優化。
當創建的自定義控制項繼承於 ViewGroup 並且不具備繪制功能時,就可以開啟這個標記,便於系統進行後續的優化;當明確知道一個 ViewGroup 需要通過 onDraw 繪制內容時,需要關閉這個標記。
參考:《Android開發藝術探索》
Ⅱ Android中View,SurfaceView的繪圖和GLSurfaceView繪圖有區別嗎
Android游戲當中主要的除了控制類外就是顯示類View。SurfaceView是從View基類中派生出來的顯示類。android游戲開發中常用的三種視圖是:
view、SurfaceView和GLSurfaceView的區別如下:
View:顯示視圖,內置畫布,提供圖形繪制函數、觸屏事件、按鍵事件函數等;必須在UI主線程內更新畫面,速度較慢。
SurfaceView:基於view視圖進行拓展的視圖類,更適合2D游戲的開發;是view的子類,類似使用雙緩機制,在新的線程中更新畫面所以刷新界面速度比view快。
GLSurfaceView:基於SurfaceView視圖再次進行拓展的視圖類,專用於3D游戲開發的視圖;是SurfaceView的子類,openGL專用。
在2D游戲開發中,大致可以分為兩種游戲框架,View和SurfaceView。
View和SurfaceView區別:
View:必須在UI的主線程中更新畫面,用於被動更新畫面。
surfaceView:UI線程和子線程中都可以。在一個新啟動的線程中重新繪制畫面,主動更新畫面。
UI的主線程中更新畫面 可能會引發問題,比如你更新畫面的時間過長,那麼你的主UI線程會被你正在畫的函數阻塞。那麼將無法響應按鍵,觸屏等消息。
當使用surfaceView 由於是在新的線程中更新畫面所以不會阻塞你的UI主線程。但這也帶來了另外一個問題,就是事件同步,涉及到線程同步。
所以基於以上,根據游戲特點,一般分成兩類。
1 被動更新畫面的。比如棋類,這種用view就好了。因為畫面的更新是依賴於 onTouch 來更新,可以直接使用 invalidate。 因為這種情況下,這一次Touch和下一次的Touch需要的時間比較長些,不會產生影響。
2 主動更新。比如一個人在一直跑動。這就需要一個單獨的thread不停的重繪人的狀態,避免阻塞main UI thread。所以顯然view不合適,需要surfaceView來控制。
Ⅲ Android中View的創建過程
我們知道在onCreate裡面View還是沒有測繪完成的。那麼什麼時候測繪完成了?答案是onResume。
通過查看源碼 我們可以看到在onCreate方法裡面調用了getWindow()方法然後在將我們的頁面塞到這個window裡面。這個window也就是PhonwWindow.
那PhoneWindow是什麼時候被創建的?
這就引出了Activity的創建流程。
那Activity是怎麼被創建的呢?
由於Activity是一個組件他是由系統使用 ActivityThread 方法去創建的。
現在我來分析下:
先來到ActivityThread類的handleLaunchActivity方法。
可以看到他去調用了Activity的performCreate方法。
現在我們終於看到onCreate方法被調用了。
這里還有個重點,在performLaunchActivity裡面去調用Activity的onCreate方法之前還去做了一件很重要的事情,這個事情在第3224行:調用了Activity的attach方法。
現在跟到Activity的attach方法:找到了我們一直找的PhoneWindow的創建。
Ⅳ Android基礎學習-View概述
在Android應用開發中,View是構建用戶界面的核心組件,它是所有控制項的基類,可以理解為UI界面中的矩形區域,比如TextView、Button、ImageView等基本控制項,以及能容納多個View的容器,如LinearLayout、RelativeLayout、ListView、RecyclerView等,它們共同構成了Android的視圖層次結構。
View的位置和大小通過四個頂點坐標決定,這些坐標對應於getLeft(), getRight(), getTop(), 和 getBottom() 方法,進而可以計算出寬度(right - left)和高度(bottom - top)。理解並設置好這些屬性,能精確控制控制項在屏幕上的布局。
View與用戶的交互主要通過MotionEvent事件來實現,當手指接觸屏幕時,會觸發ACTION_DOWN,手指移動則觸發ACTION_MOVE,手指離開屏幕則為ACTION_UP。通過設置onTouch事件,可以捕獲並響應這些動作,為用戶提供豐富的交互體驗。
在Android中,View的表示方式有兩種:一是通過XML布局文件,比如在一個垂直排列的LinearLayout中,你可以放置一個TextView和一個Button。二是通過Java代碼動態創建和管理,例如創建一個LinearLayout,設置其子控制項方向為垂直,然後添加文本框和按鈕實例。這兩種方法都可以達到相同的效果,開發者可以根據項目需求靈活選擇。
View是UI設計的基礎,無論是簡單的文本顯示還是復雜的交互界面,都離不開View的構建。後續的內容將深入探討更多View控制項及其自定義方法,敬請關注。