自定义控件(一) Activity的构成(PhoneWindow、DecorView)
系列文章傳送門 (持續更新中..) :
自定義控件(二) 從源碼分析事件分發機制
自定義控件(三) 源碼分析measure流程
自定義控件(四) 源碼分析 layout 和 draw 流程
先看一張 Activity 的構成簡化圖
-
每一個Activity都包含一個Window對象,Window由它的唯一的子類PhoneWindow實現
-
PhoneWindow:將Decoriew設置為整個應用窗口的根View。它是Android中的最基本的窗口系 統,每個Activity 均會創建一個PhoneWindow對象,是Activity和整個View系統交互的接口。
-
DecorView:頂層視圖,將要顯示的具體內容呈現在PhoneWindow上. 它并不會向用戶呈現任何東西,它主要有如下幾個功能,可能不全:
-
A. Dispatch ViewRoot分發來的key、touch、trackball等外部事件;
-
B. DecorView有一個直接的子View,我們稱之為System Layout,這個View是從系統的Layout.xml中解析出的,它包含當前UI的風格,如是否帶title、是否帶process bar等。可以稱這些屬性為Window decorations。
-
C. 作為PhoneWindow與ViewRoot之間的橋梁,ViewRoot通過DecorView設置窗口屬性。可以同 View view = getWindow().getDecorView() 獲取它;
-
D. DecorView只有一個子元素為LinearLayout。代表整個Window界面,包含通知欄,標題欄,內容顯示欄三塊區域。DecorView里面TitleView:標題,可以設置requestWindowFeature(Window.FEATURE_NO_TITLE)取消掉. ContentView:是一個id為content的FrameLayout。我們平常在Activity使用的setContentView就是設置在這里,也就是在FrameLayout上
1. 從setContentView()開始
大家都知道當我們寫Activity時會調用 setContentView() 方法來加載布局, 讓我們來看一下內部實現:
public void setContentView(@LayoutRes int layoutResID) {getWindow().setContentView(layoutResID); initWindowDecorActionBar(); } 復制代碼getWindow() :
public Window getWindow() {return mWindow; } 復制代碼###Window 可以看到返回了一個 mWindow , 它的類型是 Window 類, 而 Window 是一個抽象類, setContentView() 也是一個抽象方法, 所以我們必須要找到它的實現子類
/*** Abstract base class for a top-level window look and behavior policy. An* instance of this class should be used as the top-level view added to the* window manager. It provides standard UI policies such as a background, title* area, default key processing, etc.** <p>The only existing implementation of this abstract class is* android.view.PhoneWindow, which you should instantiate when needing a* Window.* 頂級窗口視圖和行為的抽象基類。它的實例作為一個頂級View被添加到Window Manager。* 它提供了一套標準的UI策略,例如背景,標題區域等。當你需要用到Window的時候,應該使* 用它的唯一實現子類PhoneWindow。*/ public abstract class Window {...public abstract void setContentView(@LayoutRes int layoutResID);... } 復制代碼而在 attach() 中 證實了 PhoneWindow 的初始化
final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, String referrer, IVoiceInteractor voiceInteractor,Window window) {attachBaseContext(context);mFragments.attachHost(null /*parent*/);mWindow = new PhoneWindow(this, window); } 復制代碼PhoneWindow
我們繼續看一下 PhoneWindow 這個類以及實現方法 setContentView()
public class PhoneWindow extends Window implements MenuBuilder.Callback {...// This is the top-level view of the window, containing the window decor.private DecorView mDecor;...// This is the view in which the window contents are placed. It is either// mDecor itself, or a child of mDecor where the contents go.ViewGroup mContentParent;... public void setContentView(int layoutResID) {// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window// decor, when theme attributes and the like are crystalized. Do not check the feature// before this happens.if (mContentParent == null) {// 1. 初始化: 創建 DecorView 對象和 mContentParent 對象installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());transitionTo(newScene); // Activity 轉場動畫相關} else {// 2. 填充布局: 把 setContentView() 設置進來的布局, 加載到 mContentParent,也就是 DecorView 中 id = content 的 FrameLayoutmLayoutInflater.inflate(layoutResID, mContentParent); }mContentParent.requestApplyInsets(); // 讓DecorView的內容區域延伸到systemUi下方,防止在擴展時被覆蓋,達到全屏、沉浸等不同體驗效果。// 3. 通知 Activity 布局改變final Callback cb = getCallback(); if (cb != null && !isDestroyed()) {cb.onContentChanged(); // 觸發 Activity 的 onContentChanged() 方法}mContentParentExplicitlySet = true;} }復制代碼可以看到當 mContentParent = null , 即當前內容布局還沒有放置到窗口, 也就是第一次調用的時候, 會執行 installDecor(), 我們繼續去看下該方法
private void installDecor() {mForceDecorInstall = false;if (mDecor == null) {// 生成 DecorViewmDecor = generateDecor(-1); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);mDecor.setIsRootNamespace(true);if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);}} else {mDecor.setWindow(this);}if (mContentParent == null) {// 根據主題 theme 設置對應的 xml布局文件以及 Feature(包括style,layout,轉場動畫,// 屬性等)到 DecorView中。并將 mContentParent 和 DecorView 布局中的// ID_ANDROID_CONTENT(com.android.internal.R.id.content)綁定mContentParent = generateLayout(mDecor); // 省略 ... } 復制代碼可以看到先調用 genaratDecor() 生成了 mDecorView
protected DecorView generateDecor(int featureId) {...return new DecorView(context, featureId, this, getAttributes()); } 復制代碼創建完了后執行了 generateLayout() , 在這個方法中會 根據主題 theme 設置對應的 xml布局文件以及 Feature(包括style,layout,轉場動畫,屬性等)到 DecorView中, 并在 DecorView 的xml 布局中 findViewById() 獲取內容布局的應用 contentView 并返回,即 mContentParent 就是 DecorView 中的內容布局。由此我們可以知道為什么要在setContentView 之前調用 requesetFeature 的原因。
這個方法有點長,我們大致看一下
protected ViewGroup generateLayout(DecorView decor) {// Apply data from current theme. --> 獲取當前的主題, 加載默認的資源和布局/*** 下面的代碼: 根據 theme 設定, 找到對應的 Feature(包括 style, layout, 轉場動畫, 屬性等)* / TypedArray a = getWindowStyle();...// 如果你在theme中設置了window_windowNoTitle,則這里會調用到,其他方法同理,這里是根據你在theme中的設置去設置的if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {requestFeature(FEATURE_NO_TITLE); } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {// Don't allow an action bar if there is no title.requestFeature(FEATURE_ACTION_BAR);}...// 設置全屏if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags())); }// 透明狀態欄if (a.getBoolean(R.styleable.Window_windowTranslucentStatus, false)) {setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS& (~getForcedWindowFlags()));}// 其它資源的加載....../** * 下面是添加布局到 DecorView. * 在前面我們看到已經調用 new DecorView 來創建一個實例, 但是 DecorView 本身是一個* 繼承了 FrameLayout 的 ViewGroup, 創建完了后還沒有內容所以還需要對它創建相應的布* 局. 而下面的代碼則是根據用戶設置的 Feature 來創建相應的默認布局主題.* * 舉個例子:* 如果我在setContentView之前調用了requestWindowFeature(Window.FEATURE_NO_TITLE),* 這里則會通過getLocalFeatures來獲取你設置的feature,進而選擇加載對應的布局,此時則是加載* 沒有標題欄的主題,對應的就是R.layout.screen_simple* /* // Inflate the window decor.int layoutResource;int features = getLocalFeatures();if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {layoutResource = R.layout.screen_swipe_dismiss;} (// 省略各種 else if 判斷){layoutResource = ...;}else {// Embedded, so no decoration is needed.layoutResource = R.layout.screen_simple;}mDecor.startChanging();// 把相應的布局創建并添加到 DecorView 中mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); // 從布局中獲取 R.id.contentViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); ...// 配置 DecorView 完成mDecor.finishChanging(); return contentParent; }復制代碼可以看到 在 else{} 中加載的是沒有標題欄的主題,對應的就是R.layout.screen_simple,我們看下里面的布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:fitsSystemWindows="true"android:orientation="vertical"><ViewStub android:id="@+id/action_mode_bar_stub"android:inflatedId="@+id/action_mode_bar"android:layout="@layout/action_mode_bar"android:layout_width="match_parent"android:layout_height="wrap_content"android:theme="?attr/actionBarTheme" /><FrameLayoutandroid:id="@android:id/content"android:layout_width="match_parent"android:layout_height="match_parent"android:foregroundInsidePadding="false"android:foregroundGravity="fill_horizontal|top"android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout> 復制代碼可以看到xml布局中根布局是 LinearLayout, 包含兩個子元素, 因為可以 no_title , 所以第一個是 ViewStub, 第二個子元素 id : content , 則是對應之前代碼中的 mContentParent, 也就是 generateLayout() 返回的對象, 即 setContentView() 設置的內容就是添加到這個 FrameLayout 中。
我們繼續回到 setContentView() . 在方法的最后通過 cb.onContentChanged() 來通知界面改變的。Callback 是 Window 的內部接口,里面聲明了當界面更改觸摸時調用的各種方法, 并在Activity 中實現了這個接口, 并且實現的方法是空的,所以我們可以通過重寫這個方法, 來監聽布局內容的改變了
public void onContentChanged() { } 復制代碼參考文章: Android窗口機制 Android View體系(六)從源碼解析Activity的構成
如果覺得對你有幫助, 請點個贊再走吧~
總結
以上是生活随笔為你收集整理的自定义控件(一) Activity的构成(PhoneWindow、DecorView)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 系统命令
- 下一篇: 一个封装的使用Apache HttpCl