1 include
视图引入,可以配合merge使用
重用布局
2.merge
<merge/>标签在UI的结构优化中起着非常重要的作用,它可以删减多余的层级,优化UI。<merge/>多用于替换FrameLayout或者当一个布局包含另一个时,<merge/>标签消除视图层次结构中多余的视图组。例如你的主布局文件是垂直布局,引入了一个垂直布局的include,这是如果include布局使用的LinearLayout就没意义了,使用的话反而减慢你的UI表现。这时可以使用<merge/>标签优化。
减少视图层级
步骤如下:
创建merge视图
<?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"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="123" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="123" />
</merge>
视图引入
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent"
tools:context="com.example.ticker.myapplication.MainActivity">
<include layout="@layout/merge_layout"></include>
</LinearLayout>
3.ViewStub
当我们需要根据某个条件控制某个View的显示或者隐藏的时候,通常是把可能用到的View都写在布局上,然后设置可见性为View.GONE或View.InVisible ,之后在代码中根据条件动态控制可见性。虽然操作简单,但是耗费资源,因为即便该view不可见,仍会被父窗体绘制,仍会创建对象,仍会被实例化,仍会被设置属性。
引入一个外部布局,与上面不同的是,viewstub引入的布局默认不会扩张,即既不会占用显示也不会占用位置,从而在解析layout时节省cpu和内存。ViewStub是一个不可见的,大小为0的视图,只有给他设置成了View.Visible或调用了它的inflate()之后才会填充布局资源。可以在运行过程中延时加载布局资源,inflate 方法只能被调用一次,因为调用后viewStub对象就被移除了视图树;
注:ViewStub目前有个缺陷就是还不支持 <merge /> 标签。
使用步骤如下:
1、创建需要加载的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:text="button01"
android:layout_height="wrap_content" />
<Button
android:layout_width="match_parent"
android:text="button02"
android:layout_height="wrap_content" />
</LinearLayout>
在需要引入的xml中引入
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.ticker.myapplication.MainActivity">
<Button
android:id="@+id/btn_vs_showView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示ViewStub"/>
<ViewStub
android:id="@+id/viewstub"
android:layout_width="match_parent"
android:layout="@layout/viewstub_layout"
android:layout_height="match_parent" />
</RelativeLayout>
3、使用
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewStub viewStub = (ViewStub) findViewById(R.id.viewstub);
View view = viewStub.inflate();
Button btn_show = (Button) findViewById(R.id.btn_vs_showView);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_vs_showView:
//inflate 方法只能被调用一次,因为调用后viewStub对象就被移除了视图树;
// 所以,如果此时再次点击显示按钮,就会崩溃,错误信息:ViewStub must have a non-null ViewGroup viewParent;
// 所以使用try catch ,当此处发现exception 的时候,在catch中使用setVisibility()重新显示
try {
View iv_vsContent = viewStub.inflate(); //inflate 方法只能被调用一次,
hintText = (TextView) iv_vsContent.findViewById(R.id.tv_vsContent);
// hintText.setText("没有相关数据,请刷新");
} catch (Exception e) {
viewStub.setVisibility(View.VISIBLE);
} finally {
hintText.setText("没有相关数据,请刷新");
}
break;
}
}
}
4、使用总结
1)ViewStub 引用布局时使用layout 属性,取值@layout/xxxxx
2)InflateId 表示给被引用的布局的id。也就是被控制显示或隐藏的布局的id.
3)如果不写InflateId ,如果需要的话也可以直接在被引用的布局中给出id属性
4)inflate() 方法只能被调用一次,如果再次调用会报异常信息 ViewStub must have a non-null ViewGroup viewParent。
这是因为,当ViewStub 调用inflate() 将其引用的 布局/view 展示出来之后,ViewStub本身就会从视图树中被移除,此时viewStub 就获取不到他的 父布局, 而 inflate() 方法中,上来就需要获取它的父布局,然后根据父布局是否为空再去执行具体的填充逻辑,如果为空就报上面的错,所以,inflate() 之后如果还想再次显示ViewStub 引用的布局/view 就需要 在调用inflate() 的时候try catch,当 catch 到异常的时候,调用setVisibility()设置viewStub 的View.Visible即可。ViewStub类中Inflate() 的具体逻辑如下:
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
final View view = factory.inflate(mLayoutResource, parent,
false);
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
final int index = parent.indexOfChild(this);
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
mInflatedViewRef = new WeakReference<View>(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");
}
}
- ViewStub的setVisibility()中也调用了inflate(),但是为什么多次调用setVisibility()不会导致崩溃呢?
ViewStub 的setVisibility() 方法中,会先判断 WeakReference 类型的成员变量 mInflatedViewRef 是否为空。第一次调用setVisibility()的时候,mInflatedViewRef并没有初始化,也就是说是null,那么这时候就会走inflate(),在inflate() 方法中给被填充起来的布局/view创建一个WeakReference弱引用,并赋值给mInflatedViewRef,从而完成mInflatedViewRef的初始化。当第二次走setVisibility() 的时候,mInflatedViewRef已经不再是null,就会调用 WeakReference 的父类Reference 中的get() 方法获取该引用指向的实体对象,也就是说通过get() 拿到 被填充的view对象,然后再走View类的setVisibility()。ViewStub类中的setVisibility()具体实现如下:
@Override
@android.view.RemotableViewMethod
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
super.setVisibility(visibility);
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
7)ViewStub 的inflate() 只能被调用一次, 如果想控制/修改 被填充布局中的内容并重复显示被填充的view,就用try 将viewStub.inflate() 以及修改内容的代码包裹起来,并在catch 中setVisibility.
- 在xml 中定义ViewStub 节点时,内部不能包含其他节点,也就是说,ViewStub 是一个自闭合节点,如果一个布局/view如果想通过ViewStub显示,只能定义在单独的xml 文件中。