作者:李旺成
时间:2016年5月7日
在这个 Hack 中将详细介绍 <include /> 标签的使用,以及一些注意事项。
一、每个页面添加页脚
假设:现在有这样一个需求,为应用中的每个页面都添加一个页脚。这里简单处理,要添加的页脚就是一个显示应用名称的文本(使用 TextView 来显示),如上图所示。大多数的应用都是由多个 Activity 组成的,多个 Activity 一般对应多个布局文件。那是不是要把这个页脚 TextView 一个个拷贝到每个布局文件中?
听过这么一句话:当你的程序中出现大量的重复代码时你得小心了。(不是我杜撰的,大致是这个意思,没有找到出处...)
确实是这样,不要把一段相同的代码到处“复制/粘贴”,有一个很明显的弊端摆在眼前,如果以后需要修改这段代码就悲剧了。
所以,要实现上述需求“复制/粘贴”不是我们要的解决方案。解决该问题最简单的方法是使用 <include /> 标签。当然,使用 style 来保证这些页脚都使用同一个样式也是可以的,但没有 <include /> 标签简便,再说 style 的主要功能可不是用来解决重复代码问题的。
好了需求明确的,看看怎么实现。
使用 <include /> 标签为页面添加页脚
首先,创建一个页脚布局 footer_app_name.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:gravity="center"
android:text="AndroidHacksLayout"
android:textColor="@android:color/holo_red_dark"/>
然后,使用 <include /> 为布局添加页脚 activity_hack2_1.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:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.diygreen.androidhackslayout.Hack2Activity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="演示使用 <include /> 标签避免代码重复"/>
<!-- 插入页脚 -->
<include
layout="@layout/footer_app_name"/>
</RelativeLayout>
上面的示例代码很简单,为 <include /> 标签的 layout 属性指定一个表示页脚的布局即可。仔细一点的同学可能看到了:页脚布局中设置了 android:layout_alignParentBottom="true" 属性,那么问题来了,这个属性是 RelativeLayout 中子控件才有用,如果把 activity_hack2_1.xml 的根布局修改为 FrameLayout 那不就不行了吗?
上图中可以看到,将根布局修改为 FrameLayout 之后,“页脚”就不是在该页面的底部了,那要怎么做了?不急,下面会给出解决方案,且继续往下阅读。
二、<include /> 标签使用详解
<include /> 标签使用虽然挺简单的,但也有一些细节要注意,这里做个简单的总结供大家参考。
include 属性有两种设置方式
1、在子布局中设置
就是上面的示例代码中使用的方式,直接在子布局中设置好所有的属性,使用的时候只需要在主布局中为 include 设置 layout 属性即可。
这样用很方便,但是 Android 中提供了很多布局技术,你在子布局中设置的属性可能只适用与某一个/某一类布局。上面的示例中就是这样,只有主布局为 RelativeLayout 的时候页面才能在指定位置显示,如果替换为 FrameLayout 就显示不正确了。这就反映了一个问题 —— 不灵活。那怎么解决呢?include 属性还有另外一种设置方式。
2、在 <include /> 标签里设置
说到这里,那先把示例中遗留的问题解决了,就是将 RelativeLayout 替换为 FrameLayout 之后页面位置的问题。
先看解决后的效果:
解决方案代码:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
...
>
...
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
layout="@layout/footer_app_name"/>
</FrameLayout>
上面的示例代码中与根布局为 RelativeLayout 对比的主要修改处是在 <include /> 标签中添加了 android:layout_gravity 属性,并将其属性值设置为了 "bottom" ,这就可以保证页脚会在页面的底部显示了。
说明:其实和 style 的使用非常类似,你可以直接使用 style,也可以覆盖 style 中的一些属性。
在 <include /> 标签里设置属性一般是这样用的,下面使用添加一个页眉作为示例:
- 创建页面 header_app_name.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center"
android:text="我是-页眉-"
android:textColor="@android:color/holo_blue_dark"/>
- 使用 <include /> 标签添加页眉
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
layout="@layout/header_app_name"/>
效果如下:
注意:在示例中页面布局的宽度高度都设置为 0dp 了,这么做的目的就是由 header_app_name.xml 文件的使用者在 <include /> 标签中指定 layout_width 和 layout_height 属性。如果使用者不指定这两个属性,它们的默认值都是 0dp,那便看不到页眉。
三、<include /> 标签注意事项
这里罗列了一些我在使用 <include /> 标签的时候踩过的一些坑,希望大家以后可以绕过去。
findViewById 出现 NullPointerException
具体情况是这样的,如果 include 一个布局时,并没有给 <include /> 标签设置 id 属性,那么你直接使用 findViewById 来找 include 指定布局中控件是没有问题的。
但是,一旦你为 <include /> 标签设置了 id ,就不能直接把它里面的控件当成主布局文件中的控件来直接获取了,必须先获得这个 <include /> 标签指定的布局文件,再通过该布局文件 findViewById 来获得其子控件。
<include /> 标签失效了
Android 的缺陷(Issue)跟踪系统中报告过一个缺陷,缺陷的标题是:“<include /> 标签失效了,如果想通过 <include /> 标签的属性覆盖被包含的布局所指定的属性是行不通的。”。
这个 issue 描述的问题在一定程度上是正确的,问题出在如果想在 <include /> 标签中覆盖被包含布局所指定的任何 android:layout_* 属性,必须在 <include /> 标签中同时指定 android:layout_width 和 android:layout_height 这两个属性。(参考自:《50 Android Hacks》)
PS:话说如果你是用 AndroidStudio 的话,可能根本就不会遇到这个问题,因为,如果你在指定其他的 android:layout_* 属性时,如果没有同时指定 android:layout_width 和 android:layout_height 这两个属性,AndroidStudio 是会报错的:
自定义控件与 <include />
如果是一些比较简单,include 进来之后不需要有过多操作的,使用 <include /> 完全可以胜任了。对于一些比较复杂,而且要添加很多响应事件等的使用场景,建议使用自定义控件。如果还是使用 <include /> 标签,那么不可避免在 Java 代码中又得写一堆类似的代码来添加相应逻辑。
项目地址
AndroidHacks合集
布局篇
个人博客
示例用到代码见:
Hack2Activity.java
activity_hack2_1.xml
activity_hack2_2.xml
header_app_name.xml
footer_app_name.xml