前言
在日常开发中,一个项目里可能会有很多相似的布局,如果我们每一个XML文件都写一次,不说麻烦,代码也显得冗余,而且可读性也很差。这时候我们可以将相同的布局提取出来重复使用。
Tips
使用Android Studio可以直接在布局文件对应控件:
右键 -> Refactor -> Extract -> Style 抽取样式
右键 -> Refactor -> Extract -> Layout 抽取布局 include标签
include 标签
include标签常用于将布局中的公共部分提取出来供其他layout共用,以实现布局模块化,也是平常我们设计布局时用的最多的
include使用方法
1.定义要实现(抽取)的layout布局:
include_test.xml
<?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="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/textview"
android:textSize="24sp"/>
<EditText
android:id="@+id/editText"
android:hint="@string/divide"
android:layout_width="300dp"
android:layout_height="wrap_content"/>
</LinearLayout>
include_text_relative.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="wrap_content"
android:gravity="center_horizontal"
>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="TextView_Relative"
android:textSize="24sp"/>
<EditText
android:id="@+id/editText"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_below="@+id/textView"
android:hint="@string/divide"/>
</RelativeLayout>
2.Activity的XML布局文件调用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 这样写就可以,加载的布局的子View,直接findViewByID就能找到-->
<include layout="@layout/include_text"/>
<!--如果有多个include,需要添加ID属性-->
<include
android:id="@+id/include_text1"
layout="@layout/include_text"/>
<!--这个layout用RelativeLayout 实现-->
<!--如果要使用layout_margin这样的属性,要同时加上layout_w/h属性,不然没反应-->
<include
android:id="@+id/include_text2"
layout="@layout/include_text_relative"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="50dp"/>
</LinearLayout>
3.Activity中调用include标签layout中的子View:
private void initView() {
//普通include标签用法,直接拿子View属性实现
TextView textView = (TextView) findViewById(R.id.textView);
textView.setText("不加ID实现的include标签");
//多个include标签用法,添加ID,findViewByID找到layout,再找子控件
View view_include = findViewById(R.id.include_text1);
TextView view_include_textView = (TextView) view_include.findViewById(R.id.textView);
view_include_textView.setText("加了ID实现的include标签");
}
include使用注意
- 一个xml布局文件有多个include标签需要设置ID,才能找到相应子View的控件,否则只能找到第一个include的layout布局,以及该布局的控件
- include标签如果使用layout_xx属性,会覆盖被include的xml文件根节点对应的layout_xx属性,建议在include标签调用的布局设置好宽高位置,防止不必要的bug
- include 添加id,会覆盖被include的xml文件根节点ID,这里建议include和被include覆盖的xml文件根节点设置同名的ID,不然有可能会报空指针异常
- 如果要在include标签下使用RelativeLayout,如layout_margin等其他属性,记得要同时设置layout_width和layout_height,不然其它属性会没反应
merge 标签
merge标签主要用于辅助include标签,在使用include后可能导致布局嵌套过多,多余的layout节点或导致解析变慢。merge用于消除视图层次结构中的冗余视图,例如根布局是Linearlayout,那么我们又include一个LinerLayout布局就没意义了,反而会减慢UI加载速度
merge标签常用场景:
-
FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity的ContentView父元素就是FrameLayout,所以可以用merge消除只剩一个.
-
自定义View如果继承LinearLayout(ViewGroup),建议让自定义View的布局文件根布局设置成merge,这样能少一层结点。
merge使用方法
1.对于上面的情景1,在XML布局文件的根布局将FrameLayout直接改成merge即可
2.对于上面的情景2,因为为merge标签的设置的属性都不会生效,所以原来LinearLayout标签上的属性需要转移到java代码中设置。
<?xml version="1.0" encoding="utf-8"?>
<!-- 习惯性的标记一下,MergeLayout布局 android:orientation="vertical" -->
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:background="#000000"
android:gravity="center"
android:text="第一个TextView"
android:textColor="#ffffff" />
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:background="#ffffff"
android:gravity="center"
android:text="第一个TextView"
android:textColor="#000000" />
</merge>
/**
* 自定义的View,竖直方向的LinearLayout
*/
public class MergeLayout extends LinearLayout {
public MergeLayout(Context context) {
super(context);
// 设置为数值方向的布局
setOrientation(VERTICAL);
LayoutInflater.from(context).inflate(R.layout.merge_activity, this, true);
}
}
ViewStub 标签
ViewStub 标签最大的优点是当你需要时才会加载,使用它并不会影响UI初始化时的性能.各种不常用的布局像进度条、显示错误消息等可以使用ViewStub标签,以减少内存使用量,加快渲染速度
ViewStub使用场景
通常用于不常使用的控件,比如:
- 网络请求失败的提示
- 列表为空的提示
- 新内容、新功能的引导, 因为引导基本上只显示一次
- 又或者我们写了一个通用的自定义 View. 但其中部分子 View 只在部分情况下才显示.
ViewStub使用方法
<?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">
<include
android:id="@+id/titleLayout"
layout="@layout/title_back"
android:visibility="gone" />
<FrameLayout
android:id="@+id/contentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/titleLayout" />
<ViewStub
android:id="@+id/view_stub_nodata"
android:layout_width="match_parent"
android:layout_height="match_parent"
<!--android:inflateId:重写ViewStub的父布局控件的Id-->
android:inflatedId="@+id/rl_net_error_root"
<!--android:layout:设置ViewStub被inflate的布局-->
android:layout="@layout/common_stub_no_data" />
</RelativeLayout>
common_stub_net_error.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/rl_net_error_root"
android:background="#F0F0F0"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_centerInParent="true"
>
<ImageView
android:id="@+id/txt_net_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/empty_pic_net"
android:layout_centerInParent="true"
android:gravity="center_horizontal"
/>
<Button
android:id="@+id/btn_net_refresh"
android:layout_width="122dp"
android:layout_height="35dp"
android:background="@drawable/shape_round_blue"
android:layout_marginTop="16dp"
android:textSize="16sp"
android:textColor="#FFFFFF"
android:text="刷新"
android:layout_gravity="center_horizontal"
/>
</LinearLayout>
</RelativeLayout>
当您想要加载 ViewStub 指定的布局时,可通过调用 setVisibility(View.VISIBLE) 将其设为可见,或调用 inflate()。
viewStub.inflate()方法只可调用一次,多次调用会抛出异常(解决方法如下代码)。若只是进行展示/隐藏则直接调用setVisibility,无需使用inflate方法;若需要对替换后的视图进行操作则可使用inflate方法,其会返回替换后的视图。
//第一种
private View newLayout;
private ViewStub viewStub;
public void show(boolean show) {
if(newLayout == null){
viewStub = findViewById(R.id.view_stub_nodata);
newLayout = viewStub.inflate();
}
viewStub.setVisibility(show ? View.VISIBLE : View.GONE);
}
//第二种
private View newLayout;
private ViewStub viewStub;
public void show() {
viewStub = (ViewStub) findViewById(R.id.view_stub_nodata);
try {
viewStub.inflate();
} catch (IllegalStateException e) {
viewStub.setVisibility(View.VISIBLE);
}
}
ViewStub使用注意
- ViewStub标签不支持merge标签
- ViewStub的inflate只能被调用一次,第二次调用会抛出异常,setVisibility可以被调用多次,但不建议这么做(ViewStub 调用过后,可能被GC掉,再调用setVisibility()会报异常)
- 为ViewStub赋值的android:layout_XX属性会替换待加载布局文件的根节点对应的属性