include
引入:
include是 Android 布局复用的利器,它让你能够将常用的布局组件封装成独立的 XML 文件,然后在多个地方重复使用;比如应用中的统一标题栏、通用底部按钮等,通过include只需定义一次,就能在任何需要的界面中嵌入,极大提高了代码的复用性和维护性;
- include是为了实现布局的复用;
- 使用:
在include_title1文件中写我们需要复用的文件;
java
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Button
android:layout_marginTop="5dp"
android:backgroundTint="#000000"
app:layout_constraintTop_toTopOf="parent"
android:id = "@+id/bt_titles"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="dianji">
</Button>
</androidx.constraintlayout.widget.ConstraintLayout>
在我们正式的布局中,通过Include写入我们的布局;
java
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<include
android:id = "@+id/bt_title"
android:layout_width="100dp"
android:layout_height="100dp"
layout="@layout/include_title">
</include>
<include
android:id = "@+id/bt_titles"
layout="@layout/include_title1">
</include>
</androidx.constraintlayout.widget.ConstraintLayout>
-
如果需要找到子项,我们需要找到对应的include的布局,然后用布局.
findviewbyid查找对应的Id; -
注意事项:
- 建议为每个include设置不同的id,因为如果一个布局中重复两个相同的layout或者有重复的Id,此时只会找到第一个被引入的layout中的元素;
- 后面修改只能修改布局(除背景颜色等等),而且修改布局的前提也必须重新定义宽高
- 如果layout的根布局设置了id,include里也设置了Id,建议保持一致,否则可能返回Null;
merge
引入:
merge是优化布局层级的精妙工具,它可以自动消除多余的ViewGroup层级。当使用include引入布局时,如果外层容器与父布局类型相同,merge会直接将其中的子视图融合到父容器中,避免产生不必要的嵌套,从而简化视图结构、提升测量和绘制效率,让布局更加高效;
-
为了减少视图层级以优化布局,提升UI渲染的性能;
-
使用:
其实很简单,就直接把根布局用Merge替换了就行;
java<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <Button app:layout_constraintTop_toTopOf="parent" android:id = "@+id/bt_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="dsf"> </Button> </merge> -
其他:什么时候用
Merge,就是当前layout的父容器和layout的根容器相同的类型,那么layout的根容器可以使用merge来代替,减少布局嵌套,增加UI渲染效果; -
注意事项:
- 因为Activity的默认
contentview外层就是一个framelayout,所以当前布局如果是framelayout而且无背景,边距其他属性的设置,就可以用merge来代替; - 等等。。。为什么还有边距?这是因为使用
Merge没有根布局,所以设置边距或者宽高默认无效; merge的使用范围:必须作为根结点使用,否则就会报错;- 在使用
inflate时候,必须指定父容器,而且第三个参数设置为true立刻加入到父视图中; - 因为
merge无法独立生成视图对象,但是viewStub是动态生成视图,所以viewstub中禁止使用merge;
- 因为Activity的默认
ViewStub
-
使用情况: 页面初始化时,可能会隐藏一些不可见的
veiw,就算隐藏了,但是在初始化时还是会创建,为了减少开销,有一个轻量级的方案--viewstub -
使用:
写一个布局是自己想加载的布局然后在总布局布局中使用
viewstub控件,但是必须设置宽高,并且指定layout,最后的宽高也就是这个viewstub内所定义的宽高,所以一定要保持一致,否则就有可能出现布局偏差;java<ViewStub android:id = "@+id/ljx" android:layout="@layout/viewstub_01" android:layout_width="200dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/bt_titles" app:layout_constraintLeft_toLeftOf="parent"> </ViewStub> -
注意事项:
-
(布局嵌套问题)
Viewstub是一个动态加载的布局,所以根布局不可以使用merge,所以它可能会存在一些布局嵌套; -
(一次
inflate)初始化使用--viewstub懒加载机制决定在第一次inflate之后或者设置可见性,就不能再inflate了,因为inflate会把view从自身替换到布局文件中的目标视图中,并从布局树中移除;设置可见性是因为,第一次设置的时候会执行
inflate的过程,后面设置的时候,就是对已经在目标视图中的view进行可见性的设置,不会inflate了;如果想调用多次呢,保存
inflate的引用,设置可见性; -
(目标布局的占位符--宽高)
viewstuB自身不参与绘制占用空间,但是它仍然是一个有效的占位符,所以布局文件中需要写宽高,也是最终显示出来的宽高;
源码分析:
主要解析都写在注释了,大家自行看
inflate()
java
public View inflate() {
final ViewParent viewParent = getParent();
//前置条件检查,当前viewstub必须添加到父容器,才能inflate;
if (viewParent != null && viewParent instanceof ViewGroup) {
//而且必须设置有效的布局资源;
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
// 创建替换到viewstub位置的view;
final View view = inflateViewNoAdd(parent);
// 开始替换
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");
}
}
- 这里保存的是弱引用,但是内存紧张的时候可能会被GC机制回收,所以我们应该自己保存强引用;
inflateViewNoAdd()
java
private View inflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
//获得layoutInfalter的实例
if (mInflater != null) {
//viewstub可以设置了自定义的布局加载器
factory = mInflater;
} else {
//使用默认的布局加载器
factory = LayoutInflater.from(mContext);
}
// 把我们的布局资源,方到未来父容器中(不是立即添加到父容器)
final View view = factory.inflate(mLayoutResource, parent, false);
// 这个就是替换后的view的Id;
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
return view;
}
这个方法的目的就是创建view对象,解析布局参数,设置视图的属性;
主要过程就是得到布局加载器,然后设置视图,最后设置Id;
replaceSelfWithView()
java
private void replaceSelfWithView(View view, ViewGroup parent) {
// 获得当前viewstub在父容器中的位置
final int index = parent.indexOfChild(this);
// 移除这个viewstub
parent.removeViewInLayout(this);
// 获得这个viewstub的布局参数
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
// 添加新的视图
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
//使用默认的布局参数
parent.addView(view, index);
}
}
- 调用一次
inflate之后viewstub就会从布局树中被移除,所以这里inflate只能调用一次;
过程就是:得到位置,删除viewstub, 最后添加新的视图;
setVisbility()
java
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
//如果inflate过,通过弱引用从取出来这个view设置可见性;
如果被gc机制回收,那么会爆出异常,所以简易我们使用强引用;
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
//没有Inflate过的话,调用;
super.setVisibility(visibility);
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
- 设置为
GONE时,不会触发Inflate,这是为了性能考虑;所以不能通过设置GONE来触发Inflate;
源码总结:inflate的过程其实是判断条件(如果viewstub添加到布局树中而且是有效的布局资源)->通过布局加载器加载这个布局得到view(但是不加入到父布局中,因为你需要加到原来的位置)->得到原来的位置,删除这个viewstub,并且把这个布局加载进去,最后保存弱引用触发回调;
知道了源码剩下的就是我们需要注意的点的总结:
- 在创建新
view时,我们需要注意我们需要设置InflatedId,这样我们才能得到新布局;- 需要自己保存强引用,否则会有异常;
- 因为会移除,所以只能调用一次;
- 设置可见性,第一次不能设置为
gone,否则不会触发inflate;