浅谈Activity内部页面布局结构
转载请标明出处:http://blog.csdn.net/u014800493/article/details/52104304
主要就是结合案列和源码分析一下activity的内部布局结构,先看下例子:
structure_activity.xml文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/structure_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮" />
</RelativeLayout>
布局很简单,一个RelativeLayout中包含一个Button
ActivityStructureActivity.java:**
* @author Gordon
* @since 2016/8/3
* do()
*/
public class ActivityStructureActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.structure_activity);
View root_view = findViewById(R.id.structure_button);
forView(root_view);
}
private void forView(View root_view) {
while (root_view != null) {
Log.i("ActivityStructureActivity", root_view.getClass().toString());
if (root_view instanceof ViewGroup) {
int child_count = ((ViewGroup) root_view).getChildCount();
for (int i = 0; i < child_count; i++) {
View v = ((ViewGroup) root_view).getChildAt(i);
Log.i("ActivityStructureActivity", "\t child:" + v.getClass().toString());
}
Log.i("ActivityStructureActivity", "\n");
}
root_view = (View) root_view.getParent();
}
}
}
很简单这里主要是log出布局,先看下log:
当然如果你使用的是Android Studio,你也可以用自定的工具去查看页面布局
首先你要连接手机,并且打开你的Demo到相应的页面,我这里就是打开手机进去ActivityStructureActivity页面中。
如下图中显示的自己手机型号以及右边的demo的包名。
要保证这两个显示正确了以后才能进行下面的操作.
第一步:
第二步:点击如下图的按钮
点进去进入txt文件中,找到View Hierarchy, 如下图:
显示的和Log出的层级机构一样。
转换成图形更直观一些,如下:
简单的说明一下
虚线区域1:内部结构
虚线区域2:不一定是这种结构根据Activity的各种不同设置,布局可能不同。
其他区域是用可以自定义的Layout。为什么是这样的呢?看下setContentView()源码:
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
先来看下initWindowDecorActionBar()方法:
/**
* Creates a new ActionBar, locates the inflated ActionBarView,
* initializes the ActionBar with the view, and sets mActionBar.
*/
private void initWindowDecorActionBar() {
Window window = getWindow();
// Initializing the window decor can change window feature flags.
// Make sure that we have the correct set before performing the test below.
window.getDecorView();
if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
}
mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}
其实就是创建ActionBar。这里就不多说了
主要看下getWindow()。其实就是PhoneWindow.看下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) {
cb.onContentChanged();
}
}
这个mContentParent默认为空,看下installDecor方法:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
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);
}
}
}
}
第一个if语句进去generateDecor()方法.如下:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
是一个DecorView类,这个DecorView是PhoneWindow的一个内部类。主要是处理一些window的Event事件
看下PhoneWindow中对mDecor的定义:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
也就是PhoneWindow的top-level view.
继续看installDecor()方法第二个语句进去generateLayout(mDecor)方法,看看mContentParent是什么:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
if (false) {
System.out.println("From style:");
String s = "Attrs:";
for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) {
s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "="
+ a.getString(i);
}
System.out.println(s);
}
mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
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()));
}
if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {
setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
}
WindowManager.LayoutParams params = getAttributes();
if (!hasSoftInputMode()) {
params.softInputMode = a.getInt(
com.android.internal.R.styleable.Window_windowSoftInputMode,
params.softInputMode);
}
if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled,
mIsFloating)) {
/* All dialogs should have the window dimmed */
if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
}
params.dimAmount = a.getFloat(
android.R.styleable.Window_backgroundDimAmount, 0.5f);
}
if (params.windowAnimations == 0) {
params.windowAnimations = a.getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
}
// The rest are only done if this window is not embedded; otherwise,
// the values are inherited from our container.
if (getContainer() == null) {
if (mBackgroundDrawable == null) {
if (mBackgroundResource == 0) {
mBackgroundResource = a.getResourceId(
com.android.internal.R.styleable.Window_windowBackground, 0);
}
if (mFrameResource == 0) {
mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0);
}
if (false) {
System.out.println("Background: "
+ Integer.toHexString(mBackgroundResource) + " Frame: "
+ Integer.toHexString(mFrameResource));
}
}
mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000);
}
// 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) {
layoutResource = com.android.internal.R.layout.dialog_title_icons;
} else {
layoutResource = com.android.internal.R.layout.screen_title_icons;
}
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 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) {
layoutResource = com.android.internal.R.layout.dialog_custom_title;
} else {
layoutResource = com.android.internal.R.layout.screen_custom_title;
}
} 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) {
layoutResource = com.android.internal.R.layout.dialog_title;
} else {
layoutResource = com.android.internal.R.layout.screen_title;
}
// System.out.println("Title!");
} else {
// Embedded, so no decoration is needed.
layoutResource = com.android.internal.R.layout.screen_simple;
// System.out.println("Simple!");
}
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);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
if (getContainer() == null) {
Drawable drawable = mBackgroundDrawable;
if (mBackgroundResource != 0) {
drawable = getContext().getResources().getDrawable(mBackgroundResource);
}
mDecor.setWindowBackground(drawable);
drawable = null;
if (mFrameResource != 0) {
drawable = getContext().getResources().getDrawable(mFrameResource);
}
mDecor.setWindowFrame(drawable);
// System.out.println("Text=" + Integer.toHexString(mTextColor) +
// " Sel=" + Integer.toHexString(mTextSelectedColor) +
// " Title=" + Integer.toHexString(mTitleColor));
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
if (mTitle != null) {
setTitle(mTitle);
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}
简单的分析一下:
3-68行主要就是如果用户设置了相应的Window属性。以background为例,用户可以两种方式设置:
1,自定义style:
<activity android:name=".pagemain.activity.ActivityStructureActivity"
android:theme="@style/Theme.WindowSet"></activity>
<style name="Theme.WindowSet" parent="android:Theme">
<item name="android:windowBackground">@color/red</item>
<item name="android:windowNoTitle">true</item>
</style>
这里的windowBackgroud也可以设置为一张图片。
2,代码设置:
在Activity的onCreate() 中
getWindow().getDecorView().setBackgroundColor(Color.BLUE);
就可以设置啦。继续看代码
70-106行这里是根据用户设置的window属性。比如:
Window.FEATURE_LEFT_ICON,window.FEATURE_RIGHT_ICON,window.FEATURE_PROGRESS
window.FEATURE_INDETERMINATE_PROGRESS,window.FEATURE_CUSTOM_TITLE
window.FEATURE_NO_TITLE等等,而设置不同的layoutResource。这里都没有设置,所以为:
layoutResource=com.android.internal.R.layout.screen_simple;进去看看XML布局是什么样子的:
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/layout/screen_simple.xml
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
This is an optimized layout for a screen, with the minimum set of features
enabled.
-->
<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" />
<FrameLayout
android: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>
一个LinearLayout内部有一个ViewStub和一个FramLayout.和上面的流程图相对应了。是不是这样的呢?
继续看上面的代码。108行:
- View in = mLayoutInflater.inflate(layoutResource, null);
- decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
看看findViewById()。
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
果然contentParent为布局中的FrameLayout。回头再看看最上面的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) {
cb.onContentChanged();
}
}
用户自定义的layoutResId被加载在mContentParent也就是Framelayout中了。
到此也验证了log和流程图所示的样子。当然这里只是默认的没有设置window属性的布局。
如果设置了不同的属性,流程图中虚线2区域内会加载不同的布局,大同小异。
总结
想要知道Activity的内部结构,还是要看源码,根据源码一步步解析。