首页 > 其他分享 >setContentView与Activity初始布局

setContentView与Activity初始布局

时间:2024-06-02 15:12:50浏览次数:19  
标签:layout features setContentView 布局 FEATURE feature layoutResource Activity 初始

setContentView与Activity初始布局

我们常常在Activity中调用setContentView方法来设置自己的布局,然而其实仔细点会发现我们设置的布局并不是Activity显示的全部,有的地方可能设置一个空的布局,甚至不调用setContentView方法,但界面上是有内容的,可能上面有个标题,而且使用工具查看界面的布局,也可以发现,其布局不止调用setContentView设置的那部分,如

<activity
android:name=".DefaultInitActivity"
android:theme="@android:style/Theme.Light"
android:exported="false" />

public class DefaultInitActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_default_init);
}
}

上述代码生成的Activity界面:

image-20211213215551938

image-20211213215551938

明显的,这里并未调用setContentView,但图片中却有个有MyDemos字样的标题,其实还有状态栏和导航栏的颜色,也与这里Activity初始布局有关

再查看下其布局,显然,即使没有setContentView,其也是有个初始布局的

image-20211214220610652

image-20211214220610652

1、setContentView

setContentView是Activity中的方法:

public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

其中getWindow方法获取的是在Activity创建时创建的一个PhoneWindow对象,这里简单走读下两行代码

1.1、PhoneWindow的setContentView方法

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) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}

if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}

这里onContentChanged调用根据方法的注释,其会在contentview变更时会调用,开发者可以在代码中添加监听,如在Activity中调用如下代码:

getWindow().setCallback(callback);

即可在一些窗口相关变更时收到回调,如contentview变更、窗口焦点变更等

这里的关键逻辑在前面部分,而这里根据hasFeature(FEATURE_CONTENT_TRANSITIONS)的值不同主要有两个分支

hasFeature(FEATURE_CONTENT_TRANSITIONS)其实主要是一个场景切换动画,当然这里默认是false的,但如果在setContentView前调用了类似如下代码或者在style中有如下windowContentTransitions设置,那么hasFeature(FEATURE_CONTENT_TRANSITIONS)则为true

requestWindowFeature(Window.FEATURE_CONTENT_TRANSITIONS);

getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

<item name="windowContentTransitions">true</item>

1.1.1、installDecor()

该方法代码比较多,这里只截取与这里相关的主要部分查看下

private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = 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) {
mContentParent = generateLayout(mDecor);

// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();

final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
...

这里generateDecor方法主要是new一个DecorView对象返回

这里generateLayout方法代码也比较多,这里先只关注下其返回值:

...
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
return contentParent;

其中ID_ANDROID_CONTENT在Window.java中有定义

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

其中layoutResource是根据feature等来选择布局的,而在mDecor的onResourcesLoaded方法中主要是会解析layoutResource然后添加到控件中(一般情况下是直接添加到DecorView下,在一些多窗口场景,会在DecorView下添加一个布局,里面包含一些按钮,然后再将上面初始布局添加到该布局的根节点下)

显然generateLayout最后返回的是上面初始布局layoutResource中id为conent的view

1.1.2、hasFeature(FEATURE_CONTENT_TRANSITIONS)为false时

这里主要有如下逻辑

mLayoutInflater.inflate(layoutResID, mContentParent);

这里逻辑很简单,即解析设置的布局,然后添加到mContentParent控件下

1.1.3、hasFeature(FEATURE_CONTENT_TRANSITIONS)为true

final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);

在hasFeature(FEATURE_CONTENT_TRANSITIONS)为true时在setContentView中主要有上述代码,这里第一行代码是根据mContentParent(即初始布局中id为content的view),应用调用setContentView设置的布局layoutResID来创建了一个对应的Scene对象,然后调用transitionTo方法

private void transitionTo(Scene scene) {
if (mContentScene == null) {
scene.enter();
} else {
mTransitionManager.transitionTo(scene);
}
mContentScene = scene;
}

这里一般如果是第一次设置contentview时这里mContentScene应该是为空,即走scene.enter()的逻辑

public void enter() {

// Apply layout change, if any
if (mLayoutId > 0 || mLayout != null) {
// empty out parent container before adding to it
getSceneRoot().removeAllViews();

if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}

// Notify next scene that it is entering. Subclasses may override to configure scene.
if (mEnterAction != null) {
mEnterAction.run();
}

setCurrentScene(mSceneRoot, this);
}

如果setContentView设置的布局是有效的,这里主要即会调用如下代码,最后设置的布局会添加到前面mContentParent中

LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);

而如果不是第一次设置contentview时在transitionTo方法中mContentScene应该不为空即会调用mTransitionManager.transitionTo(scene)方法,这里会触发一些动画效果,最后仍会调用到scene.enter()的逻辑

1.1.4、小节

从上面分析可以看出,一个普通的Activity的布局至少可以分为三层

1、DecorView:这个一般是布局的根节点(对freeform的多窗口场景在DecorView下会添加几个按钮)

2、初始布局:这个一般是根据Activity所在的一些feature和style等来选取不同的布局,然后添加到根节点DecorView下面(对freeform的多窗口场景会添加到几个按钮所在的DecorCaptionView下面)

3、应用布局:即一般应用调用setConentView添加到初始布局下id为content的view下的布局,另外还有个addContentView方法,可在id为content的view下添加布局

1.2、Activity的initWindowDecorActionBar方法

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处理的,这里会做一些ActionBar相关的设置和初始化等工作,这里不细述

2、初始布局

前面有看到在PhoneWindow的generateLayout方法中会根据feature等来选择布局,代码比较多,这里就总结列举下代码中相关style和feature及对应布局的选择和影响(这里除了初始布局的选择还有一些其他设置如背景等这里就不全部列举了)

// Inflate the window decor.

int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = 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 = 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(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = 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(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}

如上代码所示,其判断基本上都是根据feature来判断,在对应activity没有父activity时,feature一般有两种设置方式,一是可在setContentView调用之前,调用requestFeature方法(或Activity的requestWindowFeature方法),一是可在style中activity对应样式添加feature对应的样式属性

2.1、FEATURE_SWIPE_TO_DISMISS(windowSwipeToDismiss)

if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);

<com.android.internal.widget.SwipeDismissLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/content"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

image-20211218211545163

image-20211218211545163

如上,在feature中包含FEATURE_SWIPE_TO_DISMISS或样式中windowSwipeToDismiss为true时,其初始布局就是一个SwipeDismissLayout(关于另外两个的控件是另外添加的,这两个控件主要是和沉浸式有关,后面再探讨),该布局可滑动退出当前activity

FEATURE_SWIPE_TO_DISMISS在Android11上已被弃用了,虽还有这个feature,但上述初始布局代码已没有了

2.2、FEATURE_LEFT_ICON或FEATURE_RIGHT_ICON

else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);

这里可以看到含FEATURE_LEFT_ICON或FEATURE_RIGHT_ICON的feature时,有分两种情况,如果是floating窗口(样式中windowIsFloating属性为true)则获取样式中dialogTitleIconsDecorLayout属性定义的布局作为初始布局,如果不是floating窗口,则使用R.layout.screen_title_icons作为初始布局,如下显示了布局图,当然没有具体代码设置,有些控件默认是不可见的,同时,这里还移除了FEATURE_ACTION_BAR的feature,说明这两种feature是互斥的

2.3、FEATURE_PROGRESS或FEATURE_INDETERMINATE_PROGRESS

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 = R.layout.screen_progress;

这里看到在feature含FEATURE_PROGRESS或FEATURE_INDETERMINATE_PROGRESS且不含FEATURE_ACTION_BAR时,初始布局为R.layout.screen_progress,不过FEATURE_PROGRESS和FEATURE_INDETERMINATE_PROGRESS看源码里注释说是不再支持了

image-20211218221005303

image-20211218221005303

2.4、FEATURE_CUSTOM_TITLE

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(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);

这里可以看到含FEATURE_CUSTOM_TITLE的feature时,有分两种情况,如果是floating窗口(样式中windowIsFloating属性为true)则获取样式中dialogCustomTitleDecorLayout属性定义的布局作为初始布局,如果不是floating窗口,则使用R.layout.screen_custom_title作为初始布局,如下显示了布局图,当然没有具体代码设置,有些控件默认是不可见的,同时,这里还移除了FEATURE_ACTION_BAR的feature,说明这两种feature是互斥的

image-20211218221943709

image-20211218221943709

2.5、FEATURE_NO_TITLE(windowNoTitle)

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(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");

在feature不含FEATURE_NO_TITLE的时候这里有几个分支,

如果是floating窗口,则获取样式中dialogTitleDecorLayout属性对应的布局,

如果feature含FEATURE_ACTION_BAR,则优先获取样式中windowActionBarFullscreenDecorLayout属性对应的布局,如果未设置该属性,则使用R.layout.screen_action_bar作为初始布局

image-20211221001815343

image-20211221001815343

如果不是floating窗口,feature也不含FEATURE_ACTION_BAR,则使用R.layout.screen_title作为初始布局

image-20211221002032807

image-20211221002032807

2.6、FEATURE_ACTION_MODE_OVERLAY(windowActionModeOverlay)

else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;

在feature含FEATURE_ACTION_MODE_OVERLAY或样式中windowActionModeOverlay属性为true时使用R.layout.screen_simple_overlay_action_mode作为初始布局

image-20211221002321188

image-20211221002321188

2.7、其它

layoutResource = R.layout.screen_simple;

在非前面情况时,则使用R.layout.screen_simple作为初始布局

image-20211221002500711

image-20211221002500711

2.8、小节

上面列举了一般的各种情况下的初始布局,当然除了初始布局外正常的布局中可能还有一些其他的控件,比如上面的statusBarBackground、navigationBarBackground等,

另外上面的列举图片中是使用android:theme=“@android:style/Theme.Light”样式的Activity,在样式中有些参数,比如颜色等,不同样式可能显示并不一致,

还有,上面列举的7中主要是从generateLayout方法中layoutResource赋值的if分支中分别截取的,所以正常判断的话是从上往下,比如第一个满足则使用第一个,否则判断下一个,这里一般都是判断包含某feature的条件,特殊的是FEATURE_NO_TITLE的if条件判断中是不包含该feature,所以后面的if会在包含FEATURE_NO_TITLE时走到

3、其他(PhoneWindow中一些样式属性定义)

虽然之前大致的布局已经了解了,但其实很多样式属性都会影响最终的显示,如下(对应的样式属性很多,这里不一一列举)

R.styleable.Window_windowIsFloating:其有调用代码setLayout(WRAP_CONTENT, WRAP_CONTENT);,添加该属性为true后其显示:(工具中有一些横线,实际手机显示没有横线,是工具勾选了一些显示项才显示的),显然,一个全屏的Activity变成了类似于Dialog弹框的显示

image-20211228223511209

image-20211228223511209

R.styleable.Window_windowNoTitle:其会添加FEATURE_NO_TITLE,表示上方没有标题栏

R.styleable.Window_windowActionBar:其会添加FEATURE_ACTION_BAR,表示上方会显示ActionBar(没有添加FEATURE_NO_TITLE的feature或且没有声明windowNoTitle样式属性为true)

标签:layout,features,setContentView,布局,FEATURE,feature,layoutResource,Activity,初始
From: https://www.cnblogs.com/luoliang13/p/18227139

相关文章

  • 【C++】初始化列表、隐式转换、static成员、友元与匿名对象
    文章目录1.初始化列表2.explicit关键字2.1隐式类型转换2.2explicit3.static成员3.1成员变量3.2成员函数4.友元4.1友元函数4.2友元类5.内部类6.匿名对象1.初始化列表在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。classDate{publ......
  • 【c++基础(四)】类和对象下--初始化列表等概念
    1.前言类和对象到这里基本已经接近尾声,本篇文章主要介绍一些与类和对象有关的相关细节,在后续使用类和对象中也有可能用的到。本章重点:本篇文章重点讲解初始化列表,友元,匿名对象和类中的static成员,以及类中的内部类的概念。 2.初始化列表 在谈论初始化列表之前就要再次......
  • 【二进制部署k8s-1.29.4】一、安装前软件准备及系统初始化
    文章目录简介一.资源及软件准备1.1.机器资源信息1.2.软件列表1.2.网络规划1.2.安装路径二.系统初始化环境2.1.内核升级2.2.软件安装2.2.系统初始化2.2.内核模块配置2.3.内核参数优化简介本章节主要讲解在安装部署k8s-1.29.4环境之前的一些软件、环境调优的准备......
  • *Python中的`__init__`方法:类的初始化与魔法
    Python中的__init__方法:类的初始化与魔法在Python的面向对象编程中,__init__方法扮演着至关重要的角色。它被称为类的构造函数或初始化方法,当创建类的新实例时,会自动被调用。__init__方法允许开发者在创建对象时设置初始状态或执行其他必要的初始化操作。本文将深入解析__in......
  • 如何初始化 FIrebase 云函数,以便使用凭据和 JSON 验证 Firebase Admin SDK 服务账户?
    我觉得我已经阅读了所有可用的资料,但我仍然无法理解这一点。我非常喜欢Google的产品,但有时其文档的简洁性令人头疼。我阅读了这个令人难以置信的雄辩答案,这个答案的作者和我一样毫无头绪,但他觉得有必要写一本循序渐进的儿童指南。不幸的是,他的回答过于针对他的项目,而不是我的项......
  • Android基础-Activity的介绍
    在Android系统中,Activity是一个重要的组件,它承载了用户与应用之间的交互界面。以下是关于Activity的功能、作用以及生命周期的详细介绍。Activity的功能和作用提供用户界面:Activity是Android应用程序中用于表示一个屏幕或用户界面的组件。它负责展示应用程序的用户界面,如......
  • 初始celery
    使用celery前的一些注意事项res=add.delay(x,y)print(res.id)#这个id才是真正的任务id#安装pipinstallcelery#安装redis(消息队列和结果存储使用redis)pipinstallredis#windows安装,mac和Linux不需要安装下面的包pipinstalleventlet#官网解释#Celeryi......
  • Nginx R31 doc-15-Live Activity Monitoring 实时活动监控
    前言大家好,我是老马。很高兴遇到你。我们为java开发者实现了java版本的nginxhttps://github.com/houbb/nginx4j如果你想知道servlet如何处理的,可以参考我的另一个项目:手写从零实现简易版tomcatminicat手写nginx系列如果你对nginx原理感兴趣,可以读一下从......
  • 科技政策查询系统--MainActivity
     所花时间(包括上课): 5 h左右代码量(行): 1000   左右搏客量(篇):1了解到的知识点: springboot+android的简单开发备注(其他): packagecom.example.policyquery;importandroid.content.Intent;importandroid.os.Bundle;importandroid.util.Log;......
  • A申请共享内存并对信号量进行初始化,然后进程B与C实现互斥
    练习:设计一个程序,作为进程A,进程A专门创建一个信号量集,要求信号量集中有1个信号量,对信号量集合中的信号量进行设置,要求集合中的信号量的初值为1,然后再设计2个程序,分别是进程B和进程C,要求进程B和进程C使用进程A创建的信号量集合中的信号量实现互斥访问。提示:进程A、进程B、进......