先扯两句
上次写的部分主要还是一些封装的抽象方法,这部分只是单纯的为我这种懒汉提供了便利罢了,而本次写的内容呢,则是对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一看就知道是英语单词,所以想知道他是做什么的,最简单的就是查查翻译
除了那个包住感觉有点不靠谱以外,其他的意思还都差不多,那就简单了,我就理解为它的作用就是将关联布局内的所有控件都包括在当前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的时候,自然也有需要注意的地方。
- include在没有设置约束参数的时候,会自动根据layout引入的去定义。而当我们需要更改的时候,则需要复写这些约束参数即可,这也就是为什么我们常用的控件必须填写“android:layout_width”与“android:layout_height”两个参数,而当使用include的时候则不需要,因为include复用布局的最外层布局已经够约束好了其长宽,至于有什么可以设置,郭神的博客中已经说明了:
非layout属性则无法在<include>标签当中进行覆写。另外需要注意的是,如果我们想要在<include>标签当中覆写layout属性,必须要将layout_width和layout_height这两个属性也进行覆写,否则覆写效果将不会生效。
- 在同一个布局中,大家应该都会出现过id设置重复的时候吧,当然,不会再程序运行中出问题,那是因为在程序运行之前,就AS已经告诉我们了,那样做是不对滴。可是在使用include的时候,AS还无法那么职能的判断出究竟id是否设置重复,因此只有在运行过程中才会出现错误(会调用include中的控件,而忽略当前布局中同id的控件),所以使用时一定要慎之又慎。
merge
同样,看到这英文还是查一下什么意思
在使用git或者svn做版本控制的时候呢,我们称其为“和并”,这部分我在良秋的 Android 布局优化之include与merge中查到了如下介绍:
merge翻译成中文是合并的意思,在Android中通过使用merge能够减少视图的节点数,从而减少视图在绘制过程消耗的时间,达到提高UI性能的效果。
对于一个菜鸟来说,看到这么玄乎其玄的解释,第一反应就是蒙,如果是个勤恳好学的菜鸟,下一反应应该是去查一下什么是“节点”。而懒点的估计就直接放弃了。实际上,对于我来说,就是所谓android开发中的常用布局方式。
下面就开始研究这东西怎么用,说实话,就我这种菜鸟,还真没什么机会用到merge,不是不知道怎么用,其一真的是使用环境没有include或者后面要说到的ViewStub那么清晰,再者就是使用的条件比较苛刻,最后就是在该使用的时候,估计merge早不知道被我们忘哪去了。
下面先列举一下良秋列举的merge注意事项:
- merge必须放在布局文件的根节点上;
- merge并不是一个ViewGroup,也不是一个View,它相当于声明了一些视图,等待被添加。
- merge标签被添加到A容器下,那么merge下的所有视图将被添加到A容器下。
- 因为merge标签并不是View,所以在通过LayoutInflate.inflate方法渲染的时候, 第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点。
- 如果Activity的布局文件根节点是FrameLayout,可以替换为merge标签,这样,执行setContentView之后,会减少一层FrameLayout节点。
- 自定义View如果继承LinearLayout,建议让自定义View的布局文件根节点设置成merge,这样能少一层结点。
- 因为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"/>
是不是区别并不大,在这最基本的引用上:
- include引用布局时使用的是layout="@layout/title_layout",而ViewStub使用的是android:layout="@layout/title_layout";
- ViewStub必须设置android:layout_width与android:layout_height,而从上面include中已经说明,include是不必须设置这两个属性的。
- 至于使用的环境,ViewStub是一些不常使用到的地方,这样在不使用的时候,ViewStub可以节省资源,当需要使用的时候,通过代码解析一下,便可以得到对应的布局;而include则更偏向于同一个布局的在多个页面中的复用。
而关于ViewStub的是引用方法,在良秋 Android UI布局优化之ViewStub中,关于有如下介绍:
- ViewStub是一个继承了View类的视图。
- ViewStub是不可见的,实际上是把宽高都设置为0
- 可以通过布局文件的android:inflatedId或者调用ViewStub的setInflatedId方法为懒加载视图的跟节点设置ID
- ViewStub视图在首次调用setVisibility或者inflate方法之前,一直存在于视图树中
- 只需要调用ViewStub的setVisibility或者inflate方法即可显示懒加载的视图
- 调用setVisibility或者inflate方法之后,懒加载的视图会把ViewStub从父节点中替换掉
- ViewStub的inflate只能被调用一次,第二次调用会抛出异常,setVisibility可以被调用多次,但不建议这么做
- 为ViewStub赋值的android:layout_属性会替换待加载布局文件的根节点对应的属性
- 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" />
总共四行属性(具体参数可以修改):
- android:layout="@layout/title_layout":所要引用布局的layout xml文件;
- android:layout_width="match_parent" android:layout_height="wrap_content":所要引用布局的长宽设置;
- 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解析,总共分几步?四步!
- 创建ViewStub对象;
- 判断ViewStub是否已经解析过了,若没有进行解析;
- 根据id找到需要解析的ViewStub;
- 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封装的内容只能放到后一篇再去写了。