IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android布局优化三剑客:include,深度集成 -> 正文阅读

[移动开发]Android布局优化三剑客:include,深度集成

     android:id="@+id/tv_same"
    android:layout_width="match_parent"
    android:layout_height="50dp" />

在主布局中添加进去:

<?xml version="1.0" encoding="utf-8"?>

<!--include标签的使用-->
……

<include layout="@layout/layout_include2"/>

<include
    android:id="@+id/view_same"
    layout="@layout/layout_include2"/>

为了区分,这里给第二个layout_include2设置了id。也许你已经反应过来了,没错,我们就是要创建根布局的对象,然后再去初始化里面的控件:

    TextView tvSame = findViewById(R.id.tv_same);
    tvSame.setText("1.3 这里的TextView的ID是tv_same");
    FrameLayout viewSame = findViewById(R.id.view_same);
    TextView tvSame2 = viewSame.findViewById(R.id.tv_same);
    tvSame2.setText("1.3 这里的TextView的ID也是tv_same");

运行之后可以看到这样的效果:
[](https://links.jianshu.com/go?to=https%3A%2F%2Fimage-static.segmentfault.com%2F368%2F607%2F3686070456-5b9771ffaae61_articlex)

![image](//upload-images.jianshu.io/upload_images/15405197-725f843d608271e0?imageMogr2/auto-orient/strip%7CimageView2/2/w/432/format/webp)

[](https://links.jianshu.com/go?to=https%3A%2F%2Fimage-static.segmentfault.com%2F368%2F607%2F3686070456-5b9771ffaae61_articlex)

可见虽然控件的id虽然相同,但是使用起来是没有冲突的。

# 2、merge

`include`标签虽然解决了布局重用的问题,却也带来了另外一个问题:布局嵌套。因为把需要重用的布局放到一个子布局之后就必须加一个根布局,如果你的主布局的根布局和你需要include的根布局都是一样的(比如都是`LinearLayout`),那么就相当于在中间多加了一层多余的布局了。那么有没有办法可以在使用`include`时不增加布局层级呢?答案当然是有的,那就是使用`merge`标签。

使用`merge`标签要注意一点:必须是一个布局文件中的根节点,看起来跟其他布局没什么区别,但它的特别之处在于页面加载时它的不会绘制的。打个比方,它就像是布局或者控件的搬运工,把“货物”搬到主布局之后就会功成身退,不会占用任何空间,因此也就不会增加布局层级了。这正如它的名字一样,只起“合并”作用。

## 2.1 merge常规使用

我们来验证一下,首先创建一个layout_merge.xml,在根节点使用`merge`标签:

<?xml version="1.0" encoding="utf-8"?>

<TextView
    android:id="@+id/tv_merge1"
    android:text="我是merge中的TextView1"
    android:background="@android:color/holo_green_light"
    android:gravity="center"
    android:layout_width="wrap_content"
    android:layout_height="40dp" />

<TextView
    android:layout_toEndOf="@+id/tv_merge1"
    android:id="@+id/tv_merge2"
    android:text="我是merge中的TextView2"
    android:background="@android:color/holo_blue_light"
    android:gravity="center"
    android:layout_width="match_parent"
    android:layout_height="40dp" />

这里我使用了一些相对布局的属性,原因后面你就知道了。我们接着在ViewOptimizationActivity的布局添加RelativeLayout,然后使用include标签将layout_merge.xml添加进去:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <include
        android:id="@+id/view_merge"
        layout="@layout/layout_merge"/>
</RelativeLayout>

运行出来的效果图:

[](https://links.jianshu.com/go?to=https%3A%2F%2Fimage-static.segmentfault.com%2F578%2F330%2F578330931-5b98dfef5f958_articlex)

![](//upload-images.jianshu.io/upload_images/15405197-0dc6eb0426020cc8?imageMogr2/auto-orient/strip%7CimageView2/2/w/431/format/webp)

[](https://links.jianshu.com/go?to=https%3A%2F%2Fimage-static.segmentfault.com%2F578%2F330%2F578330931-5b98dfef5f958_articlex)

## 2.2 merge标签对布局层级的影响

在layout_merge.xml中,我们使用相对布局的属性`android:layout_toEndOf`将蓝色TextView设置到了绿色TextView的右边,而layout_merge.xml的父布局是`RelativeLayout`,所以这个属性是起了作用了,`merge`标签不会影响里面的控件,也不会增加布局层级。

如果你还不放心,可以用Android Studio来检查。我用的Android Studio是3.1版本的,可以通过**Layout Inspector**查看布局层级,不过记得要先在真机或者模拟器上把项目跑起来。依次点击Tools-Layout Inspector,然后选择你要查看的Activity,就可以看到如下的层级图:

[](https://links.jianshu.com/go?to=https%3A%2F%2Fimage-static.segmentfault.com%2F712%2F816%2F712816485-5b986ca75a856_articlex)

![](//upload-images.jianshu.io/upload_images/15405197-a1a593db1cacd90c?imageMogr2/auto-orient/strip%7CimageView2/2/w/650/format/webp)

[](https://links.jianshu.com/go?to=https%3A%2F%2Fimage-static.segmentfault.com%2F712%2F816%2F712816485-5b986ca75a856_articlex)

可以看到`RelativeLayout`下面直接就是两个TextView了, `merge`标签并没有增加布局层级。从这里也可以看出`merge`的局限性,即你需要明确将`merge`里面的布局和控件`include`到什么类型的布局中,才能提前设置好`merge`里面的布局和控件的位置。

## 2.3 merge的ID

在学习`include`标签时我们知道,它的`android:id`属性可以重写被include的根布局id,但如果根节点是`merge`呢?前面说了`merge`并不会作为一个布局绘制出来,所以这里给它设置id是不起作用的。我们可以在它的父布局`RelativeLayout`中再加一个TextView,使用`android:layout_below`属性把设置到layout_merge下面:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <include
        android:id="@+id/view_merge"
        layout="@layout/layout_merge"/>

    <TextView
        android:text="我不是merge中的布局"
        android:layout_below="@+id/view_merge"
        android:background="@android:color/holo_purple"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="40dp"/>
</RelativeLayout>

运行之后你会发现新加的TextView会把merge布局盖住,没有像预期那样在其下方。如果把`android:layout_below`中的id改为layout_merge.xml中任一TextView的id(比如tv_merge1),运行之后就可以看到如下效果:

[](https://links.jianshu.com/go?to=https%3A%2F%2Fimage-static.segmentfault.com%2F165%2F246%2F1652465439-5b99d53f6b8a9_articlex)

![](//upload-images.jianshu.io/upload_images/15405197-61d004b0c087153b?imageMogr2/auto-orient/strip%7CimageView2/2/w/434/format/webp)

[](https://links.jianshu.com/go?to=https%3A%2F%2Fimage-static.segmentfault.com%2F165%2F246%2F1652465439-5b99d53f6b8a9_articlex)

这也符合2.2中的情况,即父布局`RelativeLayout`下级布局就是include进去的TextView了。

# 3、ViewStub

你一定遇到这样的情况:页面中有些布局在初始化时没必要显示,但是又不得不事先在布局文件中写好,虽然设置成了`invisible`或`gone`,但是在初始化时还是会加载,这无疑会影响页面加载速度。针对这一情况,Android为我们提供了一个利器————`ViewStub`。这是一个不可见的,大小为0的视图,具有懒加载的功能,它存在于视图层级中,但只会在`setVisibility()`和`inflate()`方法调用只会才会填充视图,所以不会影响初始化加载速度。它有以下三个重要属性:

*   `android:layout`:ViewStub需要填充的视图名称,为“R.layout.xx”的形式;
*   `android:inflateId`:重写被填充的视图的父布局id。

与`include`标签不同,`ViewStub`的`android:id`属性是设置`ViewStub`本身id的,而不是重写布局id,这一点可不要搞错了。另外,`ViewStub`还提供了`OnInflateListener`接口,用于监听布局是否已经加载了。

## 3.1 填充布局的正确方式

我们先创建一个layout_view_stub.xml,里面放置一个`Switch`开关:

<?xml version="1.0" encoding="utf-8"?>




然后在Activity的布局中修改如下:

<?xml version="1.0" encoding="utf-8"?>

<!--ViewStub标签的使用-->
<TextView
    android:textSize="18sp"
    android:text="3、ViewStub标签的使用"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

<ViewStub
    android:id="@+id/view_stub"
    android:inflatedId="@+id/view_inflate"
    android:layout="@layout/layout_view_stub"
    android:layout_width="match_parent"
    android:layout_height="100dp" />
<LinearLayout
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <Button
        android:text="显示"
        android:id="@+id/btn_show"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="wrap_content" />

    <Button
        android:text="隐藏"
        android:id="@+id/btn_hide"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="wrap_content" />

    <Button
        android:text="操作父布局控件"
        android:id="@+id/btn_control"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

在ViewOptimizationActivity中监听ViewStub的填充事件:

    viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
        @Override
        public void onInflate(ViewStub viewStub, View view) {
            Toast.makeText(ViewOptimizationActivity.this, "ViewStub加载了", Toast.LENGTH_SHORT).show();
        }
    });

然后通过按钮事件来填充和显示layout_view_stub:

@Override
public void onClick(View view) {
    switch (view.getId()) {
        case R.id.btn_show:
            viewStub.inflate();
            break;
        case R.id.btn_hide:
            viewStub.setVisibility(View.GONE);
            break;
        default:
            break;
    }
}

运行之后,点击“显示”按钮,layout_view_stub显示了,并弹出"ViewStub加载了"的Toast;点击“隐藏”按钮,布局又隐藏掉了,但是再点击一下“显示”按钮,页面居然却闪退了,查看日志,发现抛出了一个异常:

java.lang.IllegalStateException: ViewStub must have a non-null ViewGroup viewParent


我们打开ViewStub的源码,看看是哪里抛出这个异常的。很快我们就可以定位到是在inflate()方法中

public View inflate() {
    final ViewParent viewParent = getParent();

    if (viewParent != null && viewParent instanceof ViewGroup) {
        if (mLayoutResource != 0) {
            final ViewGroup parent = (ViewGroup) viewParent;
            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");
    }
}

注意到if语句中有一个replaceSelfWithView()方法,听这名字就让人有一种不祥的预感了,点进去一看:

private void replaceSelfWithView(View view, ViewGroup parent) {
    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);
    }
}

果然,ViewStub在这里调用了removeViewInLayout()方法把自己从布局移除了。到这里我们就明白了,ViewStub在填充布局成功之后就会自我销毁,再次调用inflate()方法就会抛出IllegalStateException异常了。此时如果想要再次显示布局,可以调用setVisibility()方法。

为了避免inflate()方法多次调用,我们可以采用如下三种方式:

**3.1.1 捕获异常**
我们可以捕获异常,同时调用setVisibility()方法显示布局。

            try {
                viewStub.inflate();
            } catch (IllegalStateException e) {
                Log.e("Tag",e.toString());
                view.setVisibility(View.VISIBLE);
            }

**3.1.2 通过监听ViewStub的填充事件**
声明一个布尔值变量isViewStubShow,默认值为false,布局填充成功之后,在监听事件onInflate方法中将其置为true。

最后是今天给大家分享的一些独家干货:

CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》

【Android开发核心知识点笔记】

【Android思维脑图(技能树)】

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【Android高级架构视频学习资源】

CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》

【Android开发核心知识点笔记】

[外链图片转存中…(img-3sRCdTlZ-1630914864639)]

【Android思维脑图(技能树)】

[外链图片转存中…(img-yx0KGgxP-1630914864641)]

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

[外链图片转存中…(img-Ba6OTrBa-1630914864643)]

【Android高级架构视频学习资源】

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-07 10:56:29  更:2021-09-07 10:58:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 16:47:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码