android知识点——include、merge和ViewStub的布局优化

先扯两句

上次写的部分主要还是一些封装的抽象方法,这部分只是单纯的为我这种懒汉提供了便利罢了,而本次写的内容呢,则是对Title的封装,不过这篇是我自己写的title封装,并没有使用Toolbar,也不是闲得没事干,之前使用Toolbar时UI要求title下边加上一条1px的分割线,结果Toolbar的左侧出现了16dp左右的空白无法处理,没找到解决方案就放弃了Toolbar的使用。过些时间我会好好研究一下Toolbar,毕竟除去这点还是不错的,毕竟自己封装,对于我这种懒汉来说还是太麻烦了不是。而关于上述的情况,如果大家谁知道如何解决方法也欢迎分享,小老儿不胜感激。

好了,闲言少叙,老规矩还是先上我的Git。
MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)
并给大家展示个神器,叫Android知识点——目录,好了,闲言少叙,下面进入正题。

正文

时间已经过去好久了,不知道大家还记得我之前的封装吗?好吧,反正我是忘得差不多了,只能重新查了查之前的博客。
首先是创建一个title_layout.xml存放title布局:

<?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="@dimen/title_height">

    <RelativeLayout
        android:id="@+id/base_bg"
        android:layout_width="match_parent"
        android:layout_height="@dimen/title_height"
        android:background="@color/blue">

        <ImageView
            android:id="@+id/base_back"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:padding="@dimen/size_13"
            android:src="@mipmap/back"
            android:tint="@android:color/white" />

        <TextView
            android:id="@+id/base_title"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:text="@string/title"
            android:textColor="@android:color/white"
            android:textSize="@dimen/size_20" />


        <ImageView
            android:id="@+id/base_right_icon2"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_toLeftOf="@+id/base_right_icon1"
            android:contentDescription="@string/second_function_key"
            android:padding="@dimen/size_13"
            android:src="@mipmap/add"
            android:tint="@android:color/white"
            android:visibility="gone" />

        <ImageView
            android:id="@+id/base_right_icon1"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_alignParentRight="true"
            android:contentDescription="@string/first_function_key"
            android:padding="@dimen/size_13"
            android:src="@mipmap/more"
            android:tint="@android:color/white"
            android:visibility="gone" />

        <TextView
            android:id="@+id/base_right_text"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_alignParentRight="true"
            android:gravity="center"
            android:text="@string/make_sure"
            android:textColor="@android:color/white"
            android:textSize="@dimen/size_17"
            android:visibility="gone" />
    </RelativeLayout>
</RelativeLayout>

随后在BaseActivity的布局文件activity_base.xml中添加如下布局:

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

就完成title的添加,不过在项目中,有些时候并不需要title,或者是需要根据UI设计,去创建一些比较复杂的title,这个时候,就需要把这个刻板的title隐藏起来。因此这里便给title添加了一个id

<include 
    android:id="@+id/base_title_layout"
    layout="@layout/title_layout"/>

随后在BaseActivity中添加了如下方法:

/**
 * 隐藏头布局
 */
public void hideTitle() {
    if (baseTitle == null) {
        baseTitle = getView(R.id.base_title_layout);
    }
    baseTitle.setVisibility(View.GONE);
}

这样就实现了上述的要求,可是虽然这里加了隐藏,但是大家都知道,实际上这个title的布局资源缺已经加载了,所以在这些页面中,如果依旧这么使用的话,就会造成资源的浪费。因此,在这次修改中,我将这个部分也做了修改,而使用到的控件就是ViewStub。
(其中getView方法看过我之前博客的应该知道,就是封装的findViewById方法,大家可以直接使用findViewById替换,只是需要强转一下类型即可)

布局优化

说起ViewStub就不得不先说说重用布局文件(也就是传说中的布局优化),其实网上可以查到大量的文章,而我却是其中理解的相对浅显的,所以如果大家想要具体了解的话,郭霖郭神的 Android最佳性能实践(四)——布局优化技巧
,而我这里呢,而我呢,则根据自己的应用简单说两句就好了(无奈我这话痨的毛病,大家就忍耐一下这段“狗尾续貂”吧)

include

使用

include一看就知道是英语单词,所以想知道他是做什么的,最简单的就是查查翻译

image

除了那个包住感觉有点不靠谱以外,其他的意思还都差不多,那就简单了,我就理解为它的作用就是将关联布局内的所有控件都包括在当前include的位置。而既然可以把其他布局引用过来,那样自然也就复用了控件,从而优化了布局代码。
就比如上述我之前的封装,就是单独封装了title布局,然后在BaseActivity以及BaseFragment中进行了复用。而因为这个布局文件已经包含在了这个Iinclude标签下,也就相当于其中的所有控件都在当前的这个页面中。因此,在使用其中的控件时,就与当前布局中的其他控件一样,直接根据ID获取即可。当然,在我封装的这个框架里,直接调用getView即可。

xml:

<include 
    android:id="@+id/base_title_layout"
    layout="@layout/title_layout"/>

java:

/**
 * 隐藏头布局
 */
public void hideTitle() {
    if (baseTitle == null) {
        baseTitle = getView(R.id.base_title_layout);
    }
    baseTitle.setVisibility(View.GONE);
}
注意事项

有些东西就是这样,在提供方便的同时,自然就会出现一些隐患,而在使用include的时候,自然也有需要注意的地方。

  1. include在没有设置约束参数的时候,会自动根据layout引入的去定义。而当我们需要更改的时候,则需要复写这些约束参数即可,这也就是为什么我们常用的控件必须填写“android:layout_width”与“android:layout_height”两个参数,而当使用include的时候则不需要,因为include复用布局的最外层布局已经够约束好了其长宽,至于有什么可以设置,郭神的博客中已经说明了:

非layout属性则无法在<include>标签当中进行覆写。另外需要注意的是,如果我们想要在<include>标签当中覆写layout属性,必须要将layout_width和layout_height这两个属性也进行覆写,否则覆写效果将不会生效。

  1. 在同一个布局中,大家应该都会出现过id设置重复的时候吧,当然,不会再程序运行中出问题,那是因为在程序运行之前,就AS已经告诉我们了,那样做是不对滴。可是在使用include的时候,AS还无法那么职能的判断出究竟id是否设置重复,因此只有在运行过程中才会出现错误(会调用include中的控件,而忽略当前布局中同id的控件),所以使用时一定要慎之又慎。

merge

同样,看到这英文还是查一下什么意思


这里写图片描述

在使用git或者svn做版本控制的时候呢,我们称其为“和并”,这部分我在良秋 Android 布局优化之include与merge中查到了如下介绍:

merge翻译成中文是合并的意思,在Android中通过使用merge能够减少视图的节点数,从而减少视图在绘制过程消耗的时间,达到提高UI性能的效果。

对于一个菜鸟来说,看到这么玄乎其玄的解释,第一反应就是蒙,如果是个勤恳好学的菜鸟,下一反应应该是去查一下什么是“节点”。而懒点的估计就直接放弃了。实际上,对于我来说,就是所谓android开发中的常用布局方式。
下面就开始研究这东西怎么用,说实话,就我这种菜鸟,还真没什么机会用到merge,不是不知道怎么用,其一真的是使用环境没有include或者后面要说到的ViewStub那么清晰,再者就是使用的条件比较苛刻,最后就是在该使用的时候,估计merge早不知道被我们忘哪去了。
下面先列举一下良秋列举的merge注意事项:

  1. merge必须放在布局文件的根节点上;
  2. merge并不是一个ViewGroup,也不是一个View,它相当于声明了一些视图,等待被添加。
  3. merge标签被添加到A容器下,那么merge下的所有视图将被添加到A容器下。
  4. 因为merge标签并不是View,所以在通过LayoutInflate.inflate方法渲染的时候, 第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点。
  5. 如果Activity的布局文件根节点是FrameLayout,可以替换为merge标签,这样,执行setContentView之后,会减少一层FrameLayout节点。
  6. 自定义View如果继承LinearLayout,建议让自定义View的布局文件根节点设置成merge,这样能少一层结点。
  7. 因为merge不是View,所以对merge标签设置的所有属性都是无效的。

具体的内容参见良秋的博客说,有详细的说明,这里我就不加以赘述了,只是说两点自己使用中走的弯路,算是对上述内容的一个佐证吧。

这里写图片描述

上图就是对图1的佐证,也就是在布局的时候Android Studio会给予的提示,但是对于一些开发是不那么严谨的,可能注意不到merge的黄色提示,毕竟ImageView中没有设置android:contentDescription(这个属性是方便一些生理功能有缺陷的人使用应用程序的,比如一些视力有障碍的用户,如果用户安装了辅助浏览工具比如TalkBack,TalkBack就会大声朗读出用户目前正在浏览的内容。TextView控件TalkBack可以直接读出里面的内容(contentDescription的值),告诉用户这个图片到底是什么)会出现这类提示,没有使用到的一些布局控件也会出现黄色提示(添加一个为没有子控件,且没有id的RelativeLayout)等。毕竟大多数出现这个提示是因为不建议,而不是不能用。
所以我也任性的在测试机上跑了一下,说不定能用呢,结果:

这里写图片描述

于是我不得不死心了。

作为一个“生命不息,逗逼不止”的人,在使用中自然不可能只吃这一次亏,这次我创建了一个merge_test.xml布局文件:

<?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">

    <Button
        android:id="@+id/merge_btn"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"/>
</merge>

这次是在根节点上了吧,创建之后自然要使用了:

@SuppressLint("InflateParams")
    @Override
    protected void findViews() {
        view = (Button) LayoutInflater.from(context).inflate(R.layout.merge_test, null).findViewById(R.id.merge_btn);
        view.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.merge_btn:
                view.setText(count++ + "");
                break;
        }
    }

Shift + F10,merge,是时候展示你真正的技术了:

这里写图片描述

mmp,跪求我心里的阴影面积!难倒这就进入了一条死路吗!!!好吧,作为一个行业菜鸟,这个时候不得不去翻看大神们的博客以求帮助。

<merge>标签是作为<include>标签的一种辅助扩展来使用的,它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套。

既然郭神已经在博客中这么说了,作为小菜鸟,暂时先这么用着,至于有没有其他的玩法,就看日后的使用与积累了。

ViewStub

看到这个词呢,这里就不翻译了,实在是字面理解真不知道它是做什么的。所以这次就直接说如何使用了。
其实在用法上,ViewStub与include还真有些类似,都是定义好的xml布局,然后在使用的时候,通过控件直接引用布局文件:

<ViewStub
    android:id="@+id/base_title_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout="@layout/title_layout" />

大家在看看include的布局:

<include 
    android:id="@+id/base_title_layout"
    layout="@layout/title_layout"/>

是不是区别并不大,在这最基本的引用上:

  1. include引用布局时使用的是layout="@layout/title_layout",而ViewStub使用的是android:layout="@layout/title_layout";
  2. ViewStub必须设置android:layout_width与android:layout_height,而从上面include中已经说明,include是不必须设置这两个属性的。
  3. 至于使用的环境,ViewStub是一些不常使用到的地方,这样在不使用的时候,ViewStub可以节省资源,当需要使用的时候,通过代码解析一下,便可以得到对应的布局;而include则更偏向于同一个布局的在多个页面中的复用。

而关于ViewStub的是引用方法,在良秋 Android UI布局优化之ViewStub中,关于有如下介绍:

  1. ViewStub是一个继承了View类的视图。
  2. ViewStub是不可见的,实际上是把宽高都设置为0
  3. 可以通过布局文件的android:inflatedId或者调用ViewStub的setInflatedId方法为懒加载视图的跟节点设置ID
  4. ViewStub视图在首次调用setVisibility或者inflate方法之前,一直存在于视图树中
  5. 只需要调用ViewStub的setVisibility或者inflate方法即可显示懒加载的视图
  6. 调用setVisibility或者inflate方法之后,懒加载的视图会把ViewStub从父节点中替换掉
  7. ViewStub的inflate只能被调用一次,第二次调用会抛出异常,setVisibility可以被调用多次,但不建议这么做
  8. 为ViewStub赋值的android:layout_属性会替换待加载布局文件的根节点对应的属性
  9. inflate方法会返回待加载视图的根节点

这已经基本上说明了ViewStub的用法以及相关的原理,反正至少作为懒汉的我是没办法说的更明白了。当然,想了解更具体的原理的,可以去良秋的这篇博客中仔细学习,而单纯的使用而言,则不需要那么麻烦。
上面已经给出了ViewStub在xml文件中如何引用,不过往上翻多费劲啊,还是在下面再贴一下吧:

<ViewStub
    android:id="@+id/base_title_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout="@layout/title_layout" />

总共四行属性(具体参数可以修改):

  1. android:layout="@layout/title_layout":所要引用布局的layout xml文件;
  2. android:layout_width="match_parent" android:layout_height="wrap_content":所要引用布局的长宽设置;
  3. android:id="@+id/base_title_layout":需要解析时查找对应ViewStub的id。

谁都别问我为什么四行属性,我这里只写了三点啊,至于强迫症患者的话,那我只能非常抱歉的说一句————你管我!!!
xml部分完成了,下面就该进行java部分了,也就是ViewStub的inflate,其实也很简单:

/**
 * Title ViewStub
 */
private ViewStub titleStub;

/**
 * 控件初始化
 */
protected void initBaseView() {
    if (titleStub == null) {
        titleStub = getView(R.id.base_title_layout);
        titleStub.inflate();
    }
}

想要把ViewStub解析,总共分几步?四步!

  1. 创建ViewStub对象;
  2. 判断ViewStub是否已经解析过了,若没有进行解析;
  3. 根据id找到需要解析的ViewStub;
  4. viewStub.inflate()执行解析操作;

如此,viewStub就展示到我们的界面上了,再对其中的各个控件进行操作,实际上就与include相同了,例如:

/**
 * 设置标题
 *
 * @param title    标题的文本
 * @param showBack 是否显示返回键
 */
public void setTitle(String title, boolean showBack) {
    initBaseView();
    ((TextView) getView(R.id.base_title)).setText(title);
    if (showBack) {
        baseBack = getView(R.id.base_back);
        baseBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
}

最后的最后,思考了很久怎么写结尾的我,终于还是抵不过懒汉性格,还是直接摘一段郭神的内容放到这里吧:

另外需要提醒大家一点,ViewStub所加载的布局是不可以使用<merge>标签的,因此这有可能导致加载出来的布局存在着多余的嵌套结构,具体如何去取舍就要根据各自的实际情况来决定了,对于那些隐藏的布局文件结构相当复杂的情况,使用ViewStub还是一种相当不错的选择的,即使增加了一层无用的布局结构,仍然还是利大于弊。

原本是打算将BaseActivity的封装放到这里的,不过写得篇幅过长,而且布局优化的部分总归是要写的,所以这里就将布局优化的部分单独提出来写了一篇,而BaseActivity封装的内容只能放到后一篇再去写了。

附录

《一个Android工程的从零开始》- 目录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容