布局优化技巧笔记

时间:2022-02-20 10:14:55

在实际开发中如何尽可能的减少层级、减少控件数量,并达到同样的视觉效果?本文记录开发过程中的实践。

1. 减少 ImageView 的数目

1.1 使用 TextView.drawableXXX

如大众点评APP的首页:


布局优化技巧笔记
注意红色框内的部分

红色框住的部分,由一张图片和它下面的文字组成,至少有两种实现方式:

  1. ImageView + TextView,图在上文字在下;
  2. TextView,设置 TextView.drawableTop=”xxx”;

显然第2种方式能够省掉 ImageView。

还有一种情况,还是拿我常用的大众点评 APP 中的页面作为例子,见下图:


布局优化技巧笔记
注意最右边的箭头

我们可以用如下的布局实现:

    <TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableRight="@drawable/arrow"
android:gravity="center_vertical"
android:text="查看全部网友点评"
android:textSize="16sp" />

除非 android:layout_width = “wrap_content”,否则即使把 android:gravity 属性改成 center,箭头仍然是居右的,这是很恶心的地方,导致我们无法将文本和 drawable 一起居中(解决方法见 自定义控件让TextView、Button的drawableLeft和drawableRight与文本一起居中显示),但是我们却可以利用这一点实现上述布局。

1.2 使用 layer-list 画线

经常用到横线,最直观的实现方法是放一张图片,其实这张图片可以省掉。

同样以大众点评APP的页面为例:


布局优化技巧笔记
注意“网友推荐”下面横线(右边的小手可以用1.1的方法实现哦)

当然可以用 ImageView 实现。
或者换种方法:将“网友推荐”所在的 layout 的 background 属性设置为:

background="@drawale/bg_line"

其中 bg_line.xml :

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="#DADADA" />
</shape>
</item>
<item android:bottom="1px">
<shape>
<solid android:color="@color/white" />
</shape>
</item>
</layer-list>

2 个 item 画了 2 个层叠的矩形,下层矩形填充色是 #DADADA,上层矩形的填充色是白色,而且上层矩形的下边框比下层矩形的下边框高 1px,于是就有宽 1px、颜色为 #DADADA 的矩形区域显示出来,看起来就是一条横线。

2. 使用 ViewStub

3. 使用 merge

4. 使用 ClickableSpan


布局优化技巧笔记
注意红色框中不同颜色的文本

使用 ClickableSpan 富文本实现在同一个 TextView 中的文本的颜色、大小、背景色等属性的多样化和个性化。如下图红色框内是一个 TextView(也可能是多个 TextView),但是却有两种不同的颜色,这种效果就可以用 Spannale 实现:

Spannable richText = new Spannale("<font color=#E3E5F3>Alan海波</font>回复<font color=#E3E5F3>大赞</font>:你走开···");
textView.setText(richText);

如果不仅颜色不同,还要对某些文字添加响应事件(如跳转链接等),可以使用如下方式:

String username = "Alan 海波";
String content = "你走开……";
SpannableString spannableString = new SpannableString(username);
ClickableSpan span = new ClickableSpan() {
@Override
public void onClick(View widget) {
// do sth.
}

@Override
public void updateDrawState(TextPaint ds) {
ds.setColor(getResources().getColor(R.color.link_color));
ds.setUnderlineText(false);
}
};
spannableString.setSpan(span, 0, username.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);

Spanned replyText = Html.fromHtml("<font color=" + getColor(R.color.deep_gray) + ">回复</font>");
Spanned colon = Html.fromHtml("<font color=" +getColor(R.color.link_color) + ">:</font>");
Spanned body = Html.fromHtml("<font color=" + getColor(R.color.text_color) + ">" + content + "</font>");
Spanned richText = (Spanned) android.text.TextUtils.concat(spannableString, replyText, spannableString, colon, body);
textView.setText(richText);
tv.setMovementMethod(LinkMovementMethod.getInstance());

注意:

  • Html.fromHtml(string) 会将 string 中的 ‘\r’ 和 ‘\n’ 替换成空格,需要显式的将 ‘\r’(早期 Mac 系统)和 ‘\n’ (Unix 和 Max OS X)和 ‘\r\n’(Windows) 替换成 html 识别的 ‘< br>’,不替换的话,如果 string 中出现 “xx&xx\r” 形式的子串,会发生 IOException:
Html.fromHtml(string.replace("\r\n", "< br>").replace("\n", "< br>")).replace("\r", "< br>"));
<a href="...">
<b>
<big>
<blockquote>
<br>
<cite>
<dfn>
<div align="...">
<em>
<font size="..." color="..." face="...">
<h1>
<h2>
<h3>
<h4>
<h5>
<h6>
<i>
<img src="...">
<p>
<small>
<strike>
<strong>
<sub>
<sup>
<tt>
<u>

有支持所有的 HTML tag 标签的库,具体见 https://github.com/NightWhistler/HtmlSpanner

5. 代码格式化

使用 Cmd + Alt + L(Mac),Ctrl+Alt+L (Windows)快捷键格式化 layout 代码,使得属性排列更加规范。

6. 资源文件命名

更多信息请参考 该网站

控件类型 前缀 示例
Action bar ab_ ab_stacked.9.png
Button btn_ btn_send_pressed.9.png
Dialog dialog_ dialog_top.9.png
Divider divider_ divider_horizontal.9.png
Icon ic_ ic_star.png
Menu menu_ menu_submenu_bg.9.png
Notification notification_ notification_bg.9.png
Tabs tab_ tab_pressed.9.png


图标资源的命名:

资源类型 前缀 示例
Icons ic_ ic_star.png
Launcher icons ic_launcher ic_launcher_calendar.png
Action bar icons ic_menu iic_menu_archive.png
Status bar icons ic_stat_notify ic_stat_notify_msg.png
Tab icons ic_tab ic_tab_recent.png
Dialog icons ic_dialog ic_dialog_info.png
PopupWindow icons ic_pop ic_dialog_like.png


按压态的命名:

状态 后缀 示例
Normal _normal btn_order_normal.9.png
Pressed _pressed btn_order_pressed.9.png
Focused _focused btn_order_focused.9.png
Disabled _disabled btn_order_disabled.9.png
Selected _selected btn_order_selected.9.png


简单的工程建议使用上述前缀来区别文件的类型;
复杂的工程,建议以 module_ (如,个人中心模块的前缀为 account_,account_ic_launcher_calendar.png)作为前缀命名,公用的资源可以使用 base_ 或 common_ 作为前缀,这样命名有如下好处:

  • 格式整齐,易读性强;
  • 清理废弃资源文件时,比如使用 lint 来检查废弃资源时,方便划分责任范围;
  • 防止出现引用非本module资源文件的发生;

7. 自定义 View

如果使用现有的控件特别是 layout 不能很多好的满足需求,我们可以自定义 View 完成。

比如微信头像墙的这个效果:


布局优化技巧笔记

我们可以用 LinearLayout 实现,每一行都是一个 LinearLayout,但是这样层级较多(当然,盲目的减少层级也是不推荐的,因为 view 的绘制过程是由 meassure、layout、draw 三个过程完成的,层级会减少 draw 的时间,但是可能会增加 meassure 的时间,甚至得不偿失)。

我们也可以自定义一个 layout,在水平方向上放置子 view,空间不足时自动换行。