編輯:關於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調節屏幕亮度工具類 * by itas109 * http://blog.csdn.net
一、概述周末游戲打得過猛,於是周天熬夜碼代碼,周一早上渾渾噩噩的發現android-percent-support-lib-sample這個項目,Google終於開始支持
先給大家展示下效果圖:掃描內容是下面這張,二維碼是用zxing庫生成的由於改了好幾個類,還是去年的事都忘得差不多了,所以只能上這個類的代碼了,主要就是改了這個Captur
如果沒有特殊要求,我們可以使用Android提供的框架來創建系統樣式的Preference Screen,在其內部可以包含PreferenceCategory和Prefe