當前位置:首頁 » 安卓系統 » android添加view

android添加view

發布時間: 2023-09-12 01:55:16

⑴ Android 自定義View之Layout過程

系列文章:

在上篇文章: Android 自定義View之Measure過程 ,我們分析了Measure過程,本次將會掀開承上啟下的Layout過程神秘面紗,
通過本篇文章,你將了解到:

在上篇文章的比喻里,我們說過:

該ViewGroup 重寫了onMeasure(xx)和onLayout(xx)方法:

同時,當layout 執行結束,清除PFLAG_FORCE_LAYOUT標記,該標記會影響Measure過程是否需要執行onMeasure。

該View 重寫了onMeasure(xx)和onLayout(xx)方法:

MyViewGroup里添加了MyView、Button兩個控制項,最終運行的效果如下:

可以看出,MyViewGroup 里子布局的是橫向擺放的。我們重點關注Layout過程。實際上,MyViewGroup里我們只重寫了onLayout(xx)方法,MyView也是重寫了onLayout(xx)方法。
接下來,分析View Layout過程。

與Measure過程類似,連接ViewGroup onLayout(xx)和View onLayout(xx)之間的橋梁是View layout(xx)。

可以看出,最終都調用了setFrame(xx)方法。

對於Measure過程在onMeasure(xx)里記錄了尺寸的值,而對於Layout過程則在layout(xx)里記錄了坐標值,具體來說是在setFrame(xx)里,該方法兩個重點地方:

View.onLayout(xx)是空實現
從layout(xx)和onLayout(xx)聲明可知,這兩個方法都是可以被重寫的,接下來看看ViewGroup是否重寫了它們。

ViewGroup.layout(xx)雖然重寫了layout(xx),但是僅僅做了簡單判斷,最後還是調用了View.layout(xx)。

這重寫後將onLayout變為抽象方法,也就是說繼承自ViewGroup的類必須重寫onLayout(xx)方法。
我們以FrameLayout為例,分析其onLayout(xx)做了什麼。

FrameLayout.onLayout(xx)為子布局Layout的時候,起始坐標都是以FrameLayout為基準,並沒有記錄上一個子布局佔了哪塊位置,因此子布局的擺放位置可能會重疊,這也是FrameLayout布局特性的由來。而我們之前的Demo在水平方向上記錄了上一個子布局的擺放位置,下一個擺放時只能在它之後,因此就形成了水平擺放的功能。
由此類推,我們常說的某個子布局在父布局裡的哪個位置,決定這個位置的即是ViewGroup.onLayout(xx)。

上邊我們分析了View.layout(xx)、View.onLayout(xx)、ViewGroup.layout(xx)、ViewGroup.onLayout(xx),這四者什麼關系呢?
View.layout(xx)

View.onLayout(xx)

ViewGroup.layout(xx)

ViewGroup.onLayout(xx)

View/ViewGroup 子類需要重寫哪些方法:

用圖表示:

通過上述的描述,我們發現Measure過程和Layout過程里定義的方法比較類似:

它倆的套路比較類似:measure(xx)、layout(xx)一般不需要我們重寫,measure(xx)里調用onMeasure(xx),layout(xx)為調用者設置坐標值。
若是ViewGroup:onMeasure(xx)里遍歷子布局,並測量每個子布局,最後將結果匯總,設置自己測量的尺寸;onLayout(xx)里遍歷子布局,並設置每個子布局的坐標。
若是View:onMeasure(xx)則測量自身,並存儲測量尺寸;onLayout(xx)不需要做什麼。

Measure過程雖然比Layout過程復雜,但仔細分析後就會發現其本質就是為了設置兩個成員變數:

而Layout過程雖然比較簡單,其本質是為了設置坐標值

將Measure設置的變數和Layout設置的變數聯系起來:

此外,Measure過程通過設置PFLAG_LAYOUT_REQUIRED 標記來告訴需要進行onLayout,而Layout過程通過清除 PFLAG_FORCE_LAYOUT來告訴Measure過程不需要執行onMeasure了。
這就是Layout的承上作用

我們知道View的繪制需要依靠Canvas繪制,而Canvas是有作用區域限制的。例如我們使用:

Cavas繪制的起點是哪呢?
對於硬體繪制加速來說:正是通過Layout過程中設置的RenderNode坐標。
而對於軟體繪制來說:

關於硬體繪制加速/軟體繪制 後續文章會分析。

這就是Layout的啟下作用
以上即是Measure、Layout、Draw三者的內在聯系。
當然Layout的"承上"還需要考慮margin、gravity等參數的影響。具體用法參見最開始的Demo。

getMeasuredWidth()/getMeasuredHeight 與 getWidth/getHeight區別
我們以獲取width為例,分別來看看其方法:

getMeasuredWidth():獲取測量的寬,屬於"臨時值"
getWidth():獲取View真實的寬
在Layout過程之前,getWidth() 默認為0
何時可以獲取真實的寬、高

下篇將分析Draw()過程,我們將分析"一切都是draw出來的"道理

本篇基於 Android 10.0

⑵ Android:窗口、自定義view、bitmap

1、ViewRoot 對應於 ViewRootImpl 類,它是連接 WindowManager 和 DecorView 的紐帶,View 的三大流程均是通過 ViewRoot 來完成的。在 ActivityThread 中,當 Activity 對象被創建完畢後,會將 DecorView 添加到 Window 中,同時會創建 ViewRootImpl 對象,並將 ViewRootImpl 對棚彎象和 DecorView 建立關聯

2、 自定義View-繪制流程概述

4、 Android Handler
6、 Android Bitmap

2、MeasureSpec:

3、一般來說,使用多進程會造成以下幾個方面的問題:

5、Window 概念與分類:
Window 是一個抽象類,它的具體實現是 PhoneWindow。WindowManager 是外界訪問 Window 的入口,Window 的具體實現位於 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一個 IPC 過程。Android 中所有的視圖都是通過 Window 來呈現,因此 Window 實際是 View 的直接管理者。

6、window的三大操作:addView、upView、removeView
7、 Bitmap 中有兩個內部枚舉類:

保存圖片資源:

圖片壓縮

基本使用:

8、Context 本身是一個抽象類,是對一系列系統服務介面的封裝,包括:內部伏叢資源、包、類載入、I/O操作、許可權、主線程、IPC 和組件啟動等操作的管理。ContextImpl, Activity, Service, Application 這些都是 Context 的直接或間接子類

9、SharedPreferences 採用key-value(鍵值對)形式, 主要用於輕量級的數據存儲, 尤其適合保存應用的配置參數, 但不建議使用 SharedPreferences 來存儲大規模的數據, 可能會降低性能

10、SharedPreferences源碼有用synchronize進行加鎖同步

11、Handler 有兩個主要用途:
(1)安排 Message 和 runnables 在將來的某個時刻執行;
(2)將要在不同鏈廳悶於自己的線程上執行的操作排入隊列。(在多個線程並發更新UI的同時保證線程安全。)
只有主線程能對UI進行操作,所以在對UI進行跟改之前,ViewRootImpl 對UI操作做了驗證,這個驗證工作是由 ViewRootImpl的 checkThread 方法完成:

12、ThreadLocal 是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,其他線程則無法獲取。Looper、ActivityThread 以及 AMS 中都用到了 ThreadLocal。當不同線程訪問同一個ThreadLocal 的 get方法,ThreadLocal 內部會從各自的線程中取出一個數組,然後再從數組中根據當前 ThreadLcoal 的索引去查找對應的value值:

13、Android 提供了幾種途徑來從其他線程訪問 UI 線程:

Android單線程模式必須遵守的規則:

14、HandlerThread 集成了 Thread,卻和普通的 Thread 有顯著的不同。普通的 Thread 主要用於在 run 方法中執行一個耗時任務,而 HandlerThread 在內部創建了消息隊列,外界需要通過 Handler 的消息方式通知 HanderThread 執行一個具體的任務。

15、IntentService 可用於執行後台耗時的任務,當任務執行後會自動停止,由於其是 Service 的原因,它的優先順序比單純的線程要高,所以 IntentService 適合執行一些高優先順序的後台任務。在實現上,IntentService 封裝了 HandlerThread 和 Handler。IntentService 第一次啟動時,會在 onCreatea 方法中創建一個 HandlerThread,然後使用的 Looper 來構造一個 Handler 對象 mServiceHandler,這樣通過 mServiceHandler 發送的消息最終都會在 HandlerThread 中執行。每次啟動 IntentService,它的 onStartCommand 方法就會調用一次,onStartCommand 中處理每個後台任務的 Intent,onStartCommand 調用了 onStart 方法。可以看出,IntentService 僅僅是通過 mServiceHandler 發送了一個消息,這個消息會在 HandlerThread 中被處理。mServiceHandler 收到消息後,會將 Intent 對象傳遞給 onHandlerIntent 方法中處理,執行結束後,通過 stopSelf(int startId) 來嘗試停止服務。(stopSelf() 會立即停止服務,而 stopSelf(int startId) 則會等待所有的消息都處理完畢後才終止服務)。

16、RecyclerView 優化

⑶ 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

熱點內容
電腦配置夠但為什麼打lol掉幀 發布:2025-02-01 01:37:08 瀏覽:315
21款朗逸哪個配置比較劃算 發布:2025-02-01 01:35:32 瀏覽:975
建築動畫片腳本 發布:2025-02-01 01:35:21 瀏覽:468
管家婆如何用阿里雲伺服器 發布:2025-02-01 01:29:09 瀏覽:648
解壓耳放 發布:2025-02-01 01:20:18 瀏覽:175
cars演算法 發布:2025-02-01 01:02:26 瀏覽:176
資料庫超載 發布:2025-02-01 00:57:15 瀏覽:32
fgo安卓如何玩日服 發布:2025-02-01 00:49:40 瀏覽:715
sql2000服務管理器 發布:2025-02-01 00:48:02 瀏覽:677
榮耀暢玩什麼配置 發布:2025-02-01 00:36:35 瀏覽:458