編輯:關於Android編程
View類是android中非常重要的一個類.view是應用程序界面的直觀體現,我們看到的應用程序界面就可以看作是View(視圖)組成的.
那麼我們應用程序的界面是怎麼創建的呢,也就是應用程序的View是什麼時候創建的?
在android中與界面直接相關的就是Activity了.
我們平時在Activity的onCreate()函數中,通過調用它的setContentView()函數,將我們應用程序的界面資源設置進去.然後運行程序就可以看到我們布局文件裡描述的界面了.
從我們調用setContentView()函數將界面資源設置進去,到運行完成界面完全顯示出來,其中經過了很多過程.
這裡我主要是通過源碼來分析一下其中最終界面到底是什麼樣的View? 然後分析一下View的measure,layout,draw過程.
因為我們設置界面是setContentView()中設置的,所以就從該函數開始來分析.
我們知道Activity的onCreate()函數最先被調用.第五十步
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
....
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
....
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
....
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
...
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config);
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
mInstrumentation.callActivityOnCreate(activity, r.state);
.....
}
這裡首先創建Activity 的實例,然後mInstrumentation.callActivityOnCreate(activity, r.state)該函數最終就會調用Activity的onCreate()函數.
好了,看setContentView()函數
第一步:setContentView()
在rameworks/base/core/java/android/app/Activity.java中
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initActionBar();
}
第二步:getWindow()
在frameworks/base/core/java/android/app/Activity.java中
public Window getWindow() {
return mWindow;
}
Activity的成員變量mWindow是Window類型,它是什麼時候被賦值的呢?
....
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
...
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
...
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config);
....
}
這裡創建Activity的實例後,就通過activity.attach()函數給activity內部變量賦值,所以進attach()函數裡面看看
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) {
...
mWindow = PolicyManager.makeNewWindow(this);//
....
}
mWindow = PolicyManager.makeNewWindow(this);這裡就是給activity中mWindow賦值.那繼續看PolicyManager.makeNewWindow(this)這個函數
第四步:makeNewWindow()
在frameworks/base/core/java/com/android/internal/policyPolicyManager.java中
// The static methods to spawn new policy-specific objects
public static Window makeNewWindow(Context context) {
return sPolicy.makeNewWindow(context);
}
public final class PolicyManager {
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";
private static final IPolicy sPolicy;
static {
// Pull in the actual implementation of the policy at run-time
try {
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
} catch (ClassNotFoundException ex) {
....
}
這裡繼續調用sPolicy.makeNewWindow(context);由上面代碼可以知道這裡的sPolicy其實是Policy類型
第五步:makeNewWindow()
在frameworks/base/policy/src/com/android/internal/policy/impl/Policy.java中
public class Policy implements IPolicy {
.
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
這裡直接new PhoneWindow(context)返回,可知PhoneWindow類是Window類的子類,進入PhoneWindow類看看
第六步:PhoneWindow()
在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中
public class PhoneWindow extends Window implements MenuBuilder.Callback {
.
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
來看一下PhoneWindow類的構造函數,首先是調用了其父類(Window)的構造函數,然後創建了一個LayoutInflater對象mLayoutInflater,這個對象根據它的名字大概可以知道它是渲染布局資源的
去Window類的構造函數看看
第七步:Window()
在frameworks/base/core/java/android/view/Window.java中
public Window(Context context) {
mContext = context;
}
回到第二步中,這是我們知道getWindow()返回其實是一個PhoneWindow對象,即Activity的成員變量mWindow是PhoneWindow類型.
然後回到第一步中,那麼接著其實是調用PhoneWindow.setContentView()了
第八步:setContentView()
在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中
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();
}
}
第九步:installDecor()
在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
...
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(com.android.internal.R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
} else {
mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
if (mActionBar != null) {
mActionBar.setWindowCallback(getCallback());
if (mActionBar.getTitle() == null) {
mActionBar.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) {
mActionBar.initProgress();
}
if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
mActionBar.initIndeterminateProgress();
}
boolean splitActionBar = false;
final boolean splitWhenNarrow =
(mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0;
if (splitWhenNarrow) {
splitActionBar = getContext().getResources().getBoolean(
com.android.internal.R.bool.split_action_bar_is_narrow);
} else {
splitActionBar = getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowSplitActionBar, false);
}
final ActionBarContainer splitView = (ActionBarContainer) findViewById(
com.android.internal.R.id.split_action_bar);
if (splitView != null) {
mActionBar.setSplitView(splitView);
mActionBar.setSplitActionBar(splitActionBar);
mActionBar.setSplitWhenNarrow(splitWhenNarrow);
final ActionBarContextView cab = (ActionBarContextView) findViewById(
com.android.internal.R.id.action_context_bar);
cab.setSplitView(splitView);
cab.setSplitActionBar(splitActionBar);
cab.setSplitWhenNarrow(splitWhenNarrow);
} else if (splitActionBar) {
Log.e(TAG, "Requested split action bar with " +
"incompatible window decor! Ignoring request.");
}
.
}
}
}
}

由上圖可知,DecorView也是View,其實這個DecorView也是應用程序窗口根View.
第一次進來mDecor為null,所以會執行下面:
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);//焦點先交給mDecor的子view處理,如果子View沒有處理自己再處理
首先來看一下generateDecor()這個函數
第十步:generateDecor()
在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
這裡就直接新建了DecorView實例
回到第九步中,mContentParent == null成立,所以執行:
mContentParent = generateLayout(mDecor);進入generateLayout(mDecor)函數看看,傳進去的參數就是第十步創建的DecorView對象
第十一步:generateLayout()
在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中
protected ViewGroup generateLayout(DecorView decor) {
....
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
這個函數內容還是挺多的,不過不難,首先收集在清單配置文件中,給該activity配置的theme屬性值,然後根據這些屬性值去加載系統的布局文件,設置這些theme屬性值也可以在代碼中設置,不過要setContentView()之前,不過就不起作用了.
else {
// Embedded, so no decoration is needed.
layoutResource = com.android.internal.R.layout.screen_simple;
// System.out.println("Simple!");
}
這裡假設需要加載的布局文件id是com.android.internal.R.layout.screen_simple,系統的布局文件在frameworks/base/core/res/res/layout/目錄下,

進去screen_simple.xml看一下
有一個id為content的控件,這個很關鍵.<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>
在確定好加載哪個系統布局文件後,接下來:
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mLayoutInflater在第六步創建的,這裡將一個布局文件渲染成一個View,這個view的具體類型就是布局文件的根節點的對象類型,像上面的screen_simple.xml它的根節點就是LinearLayout.
接著將這個渲染成的LinearLayout添加到decor中,因為DecorView是ViewGroup類型,能添加子view.
接著往下看:
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
第十二步:findViewById()
在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中
public View findViewById(int id) {
return getDecorView().findViewById(id);
}
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
這個函數就去查找decorView中id為com.android.internal.R.id.content的子view,在上面的布局文件中就是一個FrameLayout了,所以說系統布局文件要有一個id為content的控件.
好了,回到第十一步,找到了這控件後就返回到第九步中,將它賦值給了mContentParent.
現在整理一下思路,mDecor賦值了,mContentParent也賦值了,它們的關系是:

回到第九步,繼續往下分析:
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(com.android.internal.R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
} else {
mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
if (mActionBar != null) {
mActionBar.setWindowCallback(getCallback());
if (mActionBar.getTitle() == null) {
mActionBar.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) {
mActionBar.initProgress();
}
if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
mActionBar.initIndeterminateProgress();
}
boolean splitActionBar = false;
final boolean splitWhenNarrow =
(mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0;
if (splitWhenNarrow) {
splitActionBar = getContext().getResources().getBoolean(
com.android.internal.R.bool.split_action_bar_is_narrow);
} else {
splitActionBar = getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowSplitActionBar, false);
}
final ActionBarContainer splitView = (ActionBarContainer) findViewById(
com.android.internal.R.id.split_action_bar);
if (splitView != null) {
mActionBar.setSplitView(splitView);
mActionBar.setSplitActionBar(splitActionBar);
mActionBar.setSplitWhenNarrow(splitWhenNarrow);
final ActionBarContextView cab = (ActionBarContextView) findViewById(
com.android.internal.R.id.action_context_bar);
cab.setSplitView(splitView);
cab.setSplitActionBar(splitActionBar);
cab.setSplitWhenNarrow(splitWhenNarrow);
} else if (splitActionBar) {
Log.e(TAG, "Requested split action bar with " +
"incompatible window decor! Ignoring request.");
}
回到第八步setContentView()函數裡,
往下接著到
mLayoutInflater.inflate(layoutResID, mContentParent);這裡的layoutResID,是在activity的onCreate()方法裡面,通過setContentView()設置的應用程序的窗口布局資源id.
這裡mLayoutInflater.inflate()方法,將應用程序的窗口布局資源渲染成一個view,然後添加到mContentParent這個ViewGroup中.
所以應用程序窗口的界面的View結構如下:

平時我們寫應用,只需要寫上圖中setContentView()的布局就可以,其他android已經實現好了.
好了,應用程序窗口的布局結構就分析完了.我們知道一個應用程序的窗口的顯示區域,其實就是DecorView及其包含的子view.
設置好應用程序的布局文件後,就要將DecorView包含內容渲染顯示到屏幕上了.
至於如何渲染不打算分析了.
在顯示出來之前,DecorView還要經過measure(測量),layout(布局),draw(繪制)三個過程.後面打算分析源碼,對這三個過程加一分析下.
Android調節屏幕亮度工具類BrightnessUtils
項目需要做了一個調節屏幕的工具類/* * Android調節屏幕亮度工具類 * by itas109 * http://blog.csdn.net
Android 百分比布局庫(percent-support-lib) 解析與擴展
一、概述周末游戲打得過猛,於是周天熬夜碼代碼,周一早上渾渾噩噩的發現android-percent-support-lib-sample這個項目,Google終於開始支持
Android開發框架之自定義ZXing二維碼掃描界面並解決取景框拉伸問題
先給大家展示下效果圖:掃描內容是下面這張,二維碼是用zxing庫生成的由於改了好幾個類,還是去年的事都忘得差不多了,所以只能上這個類的代碼了,主要就是改了這個Captur
Android學習筆記之Shared Preference
如果沒有特殊要求,我們可以使用Android提供的框架來創建系統樣式的Preference Screen,在其內部可以包含PreferenceCategory和Prefe