編輯:關於Android編程
當我們用XML中寫完一個布局文件,想在某個Activity中顯示的時候,往往通過setContentView方法加載。在上一篇文章中,我們知道如何通過LayoutInflater將一個布局文件加載到指定的父布局中。如果Activity也提供一個布局,那將xml顯示出來想必離不開LayoutInflater。
Activity中調用setContentView的代碼如下:
public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); initActionBar(); }
setContentView調用了getWindow方法裡面的setContentView方法。
public Window getWindow() { return mWindow; }
而mWindow初始化為了PhoneWindow的對象實例
mWindow = new PhoneWindow(this);
我們知道,PhoneWindow是抽象類Window的實現類。所以Activity中的setContentView方法其實調用的是PhoneWindow類的setContentView。PhoneWindow屬於framework層。
再看PhoneWindow中的setContentView方法
@Override public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } }
找到關鍵了mLayoutInflater.inflate(layoutResID, mContentParent),設置的布局id,被添加到了mContentParent父布局中。是不是覺得setContentView的機制你已經清楚了呢?如果你清楚了LayoutInflater的加載機制,那setContentView你就清楚了一大半了。但是還有一點不明白,Activity的界面到底由哪些部分組成,mContentParent又是啥。我們接著把代碼看完,想必心中就有譜了。
上述代碼首先是對mContentParent判空,如果空就調用installDecor()。想必mContentParent的初始化就是在該方法體裡面。
private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } if (mContentParent == null) { //根據窗口的風格修飾,選擇對應的修飾布局文件,並且將id為content的FrameLayout賦值給mContentParent mContentParent = generateLayout(mDecor); //...... //初始化一堆屬性值 } }
可以看到mContentParent的初始化借助了mDecor,mDecor在初始化mContentParent之前借助generateDecor()完成。mDecor是DecorView的對象,DecorView是FrameLayout的子類,如下代碼所示。
// This is the top-level view of the window, containing the window decor. private DecorView mDecor; //...... private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { //......
調用generateLayout創建mContentParent對象之後,就可以調用findViewById生成一些標題、ActionBar對象。那在generateLayout中應該進行了放入了一些布局控件等。打開源碼瞅一瞅。
protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. TypedArray a = getWindowStyle(); //...Window_windowIsFloating,Window_windowNoTitle,Window_windowActionBar... //首先通過WindowStyle中設置的各種屬性,對Window進行requestFeature或者setFlags if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) { requestFeature(FEATURE_NO_TITLE); } //... if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) { setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags())); } //...根據當前sdk的版本確定是否需要menukey WindowManager.LayoutParams params = getAttributes(); //通過a中設置的屬性,設置 params.softInputMode 軟鍵盤的模式; //如果當前是浮動Activity,在params中設置FLAG_DIM_BEHIND並記錄dimAmount的值。 //以及在params.windowAnimations記錄WindowAnimationStyle //Inflate the window decor. int layoutResource;//將要選擇的窗口根部局 int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); /*是根據窗口的風格修飾類型為該窗口選擇不同的窗口根布局文件*/ if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = com.android.internal.R.layout.screen_title_icons; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); // System.out.println("Title Icons!"); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { // Special case for a window with only a progress bar (and title). // XXX Need to have a no-title version of embedded windows. layoutResource = com.android.internal.R.layout.screen_progress; // System.out.println("Progress!"); } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { // Special case for a window with a custom title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = com.android.internal.R.layout.screen_custom_title; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { // If no other features and not embedded, only need a title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( com.android.internal.R.attr.dialogTitleDecorLayout, res, true); layoutResource = res.resourceId; } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { layoutResource = com.android.internal.R.layout.screen_action_bar; } else { layoutResource = com.android.internal.R.layout.screen_title; } // System.out.println("Title!"); } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode; } else { // Embedded, so no decoration is needed. layoutResource = com.android.internal.R.layout.screen_simple; // System.out.println("Simple!"); } //根部局文件inflate到decor中 View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); //加入mDecor(布局)中的id為content的View返回給contentParent ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); //... return contentParent; } }
總的來說generateLayout流程就是,mDecor是一個FrameLayout(子類),根據theme選用系統布局文件(一般包含title,actionbar和id 為content的FrameLayout),並inflate為View,加入到mDecor中。而布局文件中包含的id為content的FrameLayout將返回給mContentParent。一旦我們有了mContentParent,就把我們setContentView的布局添加到mContentParent上了。
經過上面的Activity setContentView調用到PhoneWindow setContentView;再利用PhoneWindow中的installDecor初始化mDecor,PhoneWindow的generatelayout完成mDecor對mContentParent的初始化。
給大家看一個例子
AndroidManifest.xml設置如下
......
generateLayout方法中的layoutResource變量值為R.layout.screen_simple,所以我們看下系統這個screen_simple.xml布局文件,如下:
<framelayout android:foreground="?android:attr/windowContentOverlay" android:foregroundgravity="fill_horizontal|top" android:foregroundinsidepadding="false" android:id="@android:id/content" android:layout_height="match_parent" android:layout_width="match_parent"> </framelayout>
布局中,一般會包含ActionBar,TitleBar,和一個id為content的FrameLayout,這個布局是NoTitle的,所以在HierarchyViewer沒有顯現。
再來看下上面這個App的hierarchyviewer圖譜,如下:
根部局是DecorView(FrameLayout子類),LinearLayout是根據Theme設置選擇的系統布局文件,並添加到DecorView中,返回其中id為content的FrameLayout給mContentParent,用來承接setContentView傳進來的布局文件。至此大家對setContentView的布局加載流程應該有了一個比較清楚的認識了。一個完整的Activity默認視圖結構如圖
默認情況下的Android模擬器就是下面的這個樣子: 看到這個屏幕截圖最顯眼的問題顯然它的丑陋的界面。模擬器窗口占據了屏幕巨大的空間,而且毫無緣由的放著一個屏幕鍵盤。如果
引言盡管Android Studio已經越來越流行了,但很多人還是習慣於Eclipse或源碼環境下開發JNI應用。筆者是從以前在學校參加谷歌大學學術合作項目的時候接觸JN
概述:一般情況下,我們知道View類有個View.OnTouchListener內部接口,通過重寫他的onTouch(View v, MotionEvent event)
時間過得真快,又到了寫博客的時候了(/▽╲)。這次按照計劃記錄一個簡單的自定義ViewGroup:流布局FlowLayout的實現過程,將View的繪制流程和Layout