Activity加载布局相关知识点
一、加载tabBar相关
1、Activity和AppCompatActivity去掉tabBar的方法
//Activity
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Activity设置无title必须使用这个
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main)
}
}
//AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//AppCompatActivity设置无title必须使用这个
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
setContentView(R.layout.activity_main)
}
}
2、为什么requestWindowFeature()要在setContentView()之前调用?
requestWindowFeature 实际调用的是 PhoneWindow.requestFeature,
在这个方法里面会判断如果变量 mContentParentExplicitlySet 为true则报错,
而这个变量会在PhoneWindow.setContentView
调用的时候设置为true。
//加载布局的时候置为true
private boolean mContentParentExplicitlySet = false;
@Override
public boolean requestFeature(int featureId) {
if (mContentParentExplicitlySet) {
//可以看到这个注释
throw new AndroidRuntimeException("requestFeature() must be called before adding content");
}
final int features = getFeatures();
final int newFeatures = features | (1 << featureId);
if ((newFeatures & (1 << FEATURE_CUSTOM_TITLE)) != 0 &&
(newFeatures & ~CUSTOM_TITLE_COMPATIBLE_FEATURES) != 0) {
// Another feature is enabled and the user is trying to enable the custom title feature
// or custom title feature is enabled and the user is trying to enable another feature
throw new AndroidRuntimeException(
"You cannot combine custom titles with other title features");
}
if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) {
return false; // Ignore. No title dominates.
}
if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) {
// Remove the action bar feature if we have no title. No title dominates.
removeFeature(FEATURE_ACTION_BAR);
}
if (featureId == FEATURE_INDETERMINATE_PROGRESS &&
getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
throw new AndroidRuntimeException("You cannot use indeterminate progress on a watch.");
}
return super.requestFeature(featureId);
}
@Override
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();
}
//加载布局之后就为true了
mContentParentExplicitlySet = true;
}
3、AppCompatActivity类中为什么 调用requestWindowFeature(Window.FEATURE_NO_TITLE);设置无效?
需要用
supportRequestWindowFeature(Window.FEATURE_NO_TITLE),
因为继承的是AppCompatActivity,这个类里面会覆盖设置。
//可以看到requestWindowFeature方法被覆盖了
public boolean supportRequestWindowFeature(int featureId) {
return getDelegate().requestWindowFeature(featureId);
}
二、LayoutInflate几个参数的作用?
调用LayoutInflater.from(this).inflate()
方法最终都会走三参的方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
这里先简单说明下三个参数:
-
resource:
需要加载的布局文件的ID,即要将哪个布局文件转换成View对象。 -
root:
生成的View对象的父View,如果传入null,则表示不需要将生成的View添加到任何父View中。 -
attachToRoot:
是否将生成的View添加到root中,如果为true,则将生成的View添加到root中,如果为false,则不添加。
public class InflateActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inflate);
LinearLayout ll = findViewById(R.id.ll);
// 方式一:将布局添加成功
// View view = LayoutInflater.from(this).inflate(R.layout.inflate_layout, ll, true);
// 方式二:报错,一个View只能有一个父亲(The specified child already has a parent.)
// View view = LayoutInflater.from(this).inflate(R.layout.inflate_layout, ll, true);
// ll.addView(view);//view已经有一个父类了,不能再addView了
// 方式三:布局成功,第三个参数为false
// 目的:想要 inflate_layout 的根节点的属性(宽高)有效,又不想让其处于某一个容器中
// View view = LayoutInflater.from(this).inflate(R.layout.inflate_layout, ll, false);
// ll.addView(view);
// 方式四:root = null,这个时候不管第三个参数是什么,显示效果一样
// inflate_layout 的根节点的属性(宽高)设置无效,只是包裹子View,
// 但是子View(Button)有效,因为Button是出于容器下的
View view = LayoutInflater.from(this).inflate(R.layout.inflate_layout, null, false);
ll.addView(view);
}
}
三、描述下include、merge、ViewStub标签的特点
1、include:
- 不能作为根元素,需要放在 ViewGroup中
- findViewById查找不到目标控件,这个问题出现的前提是在使用include时设置了id,而在findViewById时却用了被include进来的布局的根元素id。
为什么会报空指针呢?
如果使用include标签时设置了id1,这个id1就会覆盖layout根view中设置的id2,从而找不到这个id2
。
代码:
LayoutInflate.parseInclude
--》final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
--》if (id != View.NO_ID) {
view.setId(id);
}
**例子:
1、IncludeActivity :
public class IncludeActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_include);
// 使用include最常见的问题就是findViewById查找不到目标控件
noFind();
find();
// 直接查找 my_title_layout.xml 布局中的 子View 也是没问题的
// TextView titleTextView = (TextView)findViewById(R.id.title_tv) ;
// titleTextView.setText("new Title");
}
// 查找不到,会报错
private void noFind() {
//找不到根布局rl_layout
View titleView = findViewById(R.id.rl_layout);
// 此时 titleView 为空,找不到。此时空指针(File error accessing recents directory (directory doesn't exist?).)
TextView titleTextView = titleView.findViewById(R.id.title_tv);
titleTextView.setText("new Title2");
}
// 正确查找方式 -- 使用include中的id
private void find() {
// 使用include时设置的id,即R.id.include_layout
View titleView = findViewById(R.id.include_layout);
// 通过titleView找子控件
TextView titleTextView = (TextView) titleView.findViewById(R.id.title_tv);
titleTextView.setText("new Title3");
}
}
2、activity_include
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/include_layout"
layout="@layout/include_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
3、include_layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rl_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/back_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/back_btn"
android:gravity="center"
android:text="我的title"
android:textSize="18sp" />
</RelativeLayout>
2、merge:
在Android中,merge是一个布局标签,用于将多个布局文件合并为一个布局文件。它可以用于简化布局文件的层次结构,减少布局文件的嵌套层数,提高布局文件的可读性和可维护性。
- merge标签必须使用在根布局
- 因为merge标签并不是View,所以在通过LayoutInflate.inflate()方法渲染的时候,第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点.
- 由于merge不是View所以对merge标签设置的所有属性都是无效的.
1、在merge标签中添加需要合并的布局文件内容
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</merge>
2、在其他布局文件中使用include标签引用merge_layout.xml文件,例如:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/merge_layout" />
</LinearLayout>
这样,merge_layout.xml文件中的布局内容就会被合并到当前布局文件中,从而实现布局的复用和简化。
3、ViewStub使用:
ViewStub是一个轻量级的View,它可以在需要时延迟加载布局,从而提高应用程序的性能。ViewStub在布局文件中被定义为一个占位符,当需要显示它时,可以通过调用inflate()方法来加载它。
以下是使用ViewStub的步骤:
1、在布局文件中定义ViewStub,layout加载其它布局
<ViewStub
android:id="@+id/stub"
android:inflatedId="@+id/subTree"
android:layout="@layout/my_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
2、在代码中获取ViewStub的引用
ViewStub stub = findViewById(R.id.stub);
3、在需要显示ViewStub时,调用inflate()方法
View inflated = stub.inflate();
// 显示 ViewStub:使用setVisibility和inflate 都可以
viewStub.setVisibility(View.VISIBLE);
4、对inflated进行操作
TextView textView = inflated.findViewById(R.id.text_view);
textView.setText("Hello World!");
注意事项:
ViewStub只能被inflate一次,如果需要再次显示,需要重新获取ViewStub的引用。
ViewStub只能包含一个子View,如果需要显示多个View,可以使用ViewGroup来包含多个ViewStub。
ViewStub在inflate()方法调用后会被移除,如果需要再次显示,需要重新获取ViewStub的引用。
4、ViewStub源码:
- ViewStub:就是一个宽高都为0的一个View,它默认是不可见的
- 类似include,但是一个不可见的View类,用于在运行时按需懒加载资源,只有在代码中调用了
viewStub.inflate()
或者viewStub.setVisible(View.visible)
方法时才内容才变得可见。
- 类似include,但是一个不可见的View类,用于在运行时按需懒加载资源,只有在代码中调用了
- 这里需要注意的一点是,当ViewStub被inflate到parent时,ViewStub就被remove掉了,即当前view hierarchy中不再存在ViewStub,而是使用对应的layout视图代替。
1、ViewStub的
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
// 获取 InflatedId
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
// 设置不显示
setVisibility(GONE);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 设置宽高为0
setMeasuredDimension(0, 0);
}
2、inflate方法
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
// 设置ID
final View view = inflateViewNoAdd(parent);
// 替换自己和View
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
private View inflateViewNoAdd(ViewGroup parent) {
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
}
private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this);
// 移除自己
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
// 添加View
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
}