自定义控件实践——流式布局

来源:互联网 发布:在家学英语软件 编辑:程序博客网 时间:2024/04/20 06:09

1 Preface

在这篇文章中,我们将实现一个自定义控件,类似水平方向的 LinearLayout,区别是:当水平方向上空间不足时,子 View 自动从下一行开始放置。

这种控件有个统称:流式布局(FlowLayout)。

2 Situation

先来看一个微信朋友圈详情页的照片墙效果:



我们通过 View Hierarchy 来看下这些头像的布局:



可以看到,每一行头像都是一个水平方向的 LinearLayout。外面也是一个 LinearLayout。

当然,这是最容易想到的实现方式。
好处是思路简单,代码容易维护,即使是新手也能维护。
坏处是出现层级嵌套,影响性能。

3 Target

从减少层级的角度,对这个布局做下优化。
这种布局的本质是:有一个 ViewGroup,对其添加子 View 的时候,从左至右水平摆放,当第 n 行的水平空间不足时从第 n+1 行最左侧开始摆放,即一个会自动换行的 ViewGroup。

这种方式的好处很明显:布局层级明显减少。

由于子 View 从上一行末尾换到下一行首时的轨迹像 Z,,我们姑且称之为 ZLayout。

4 Action

继承 ViewGroup,重写 onMeasure()、onLayout()方法。

5 Result

5.1 ZLayout

该控件已经开源到 Github 上,具体代码见链接。

5.2 使用场景

ZLayout 除了可以实现 Situation 中的照片墙,还有下面一种用法。
截取大众点评 app 个人空间的一个效果:





注意昵称后面 lv5 和 vip 图标


如果用户昵称后面除了 lv5 和 vip 之外,还有其他头衔,很可能因为空间不够而导致一行放不下,我们假设空间不足是不显示剩余的头衔图标。这种情况该怎么办呢?
在昵称后面放一个 LinearLayout 行吗?我们试验下。
用 200px x 100px 的飞机图来代表头衔,分别使用不同的 LayoutParams 参数,看能否达到上述目标。




ico_aereo.png

实验代码:

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        LinearLayout container = new LinearLayout(getApplicationContext());        container.setOrientation(LinearLayout.VERTICAL);        container.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));        // 第1行        LinearLayout line1 = new LinearLayout(getApplicationContext());        line1.setOrientation(LinearLayout.HORIZONTAL);        LinearLayout.LayoutParams lpWrapContent = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);        // 第2行        LinearLayout line2 = new LinearLayout(getApplicationContext());        line2.setOrientation(LinearLayout.HORIZONTAL);        LinearLayout.LayoutParams lpExactDimension = new LinearLayout.LayoutParams(200, 100);        for (int i = 0; i < 200; i++) {            ImageView img1 = new ImageView(getApplicationContext());            img1.setImageDrawable(getResources().getDrawable(R.mipmap.ico_aereo));            line1.addView(img1, lpWrapContent);            ImageView img2 = new ImageView(getApplicationContext());            img2.setImageDrawable(getResources().getDrawable(R.mipmap.ico_aereo));            line2.addView(img2, lpExactDimension);        }        container.addView(line1, lpWrapContent);        container.addView(line2, lpWrapContent);        setContentView(container);    }}

运行结果如下:




从图可以看出,将 ImageView 的 LayoutParams 设置为 WRAP_CONTENT 和精确值,效果是不同的。具体来说,当剩余水平空间不足以放下一个原始尺寸的 ImageView 时,前者等比例缩小显示,后者则截断显示。

注意,上述结果仅仅是对于 ImageView 而言的,如果是其他类型,如 TextView,则不一定成立。换做 TextView 的实验结果如下:

第1行的 TextView 的 LayoutParams 全部是 WRAP_CONTENT;第2行的 TextView 的 width=127px,height=WRAP_CONTENT。

试验代码:

@Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        LinearLayout container = new LinearLayout(getApplicationContext());        container.setOrientation(LinearLayout.VERTICAL);        container.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));        // 第1行        LinearLayout line1 = new LinearLayout(getApplicationContext());        line1.setOrientation(LinearLayout.HORIZONTAL);        LinearLayout.LayoutParams lpWrapContent = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);        // 第2行        LinearLayout line2 = new LinearLayout(getApplicationContext());        line2.setOrientation(LinearLayout.HORIZONTAL);        LinearLayout.LayoutParams lpExactDimension = new LinearLayout.LayoutParams(127, ViewGroup.LayoutParams.WRAP_CONTENT);        for (int i = 0; i < 200; i++) {            TextView view1 = new TextView(getApplicationContext());            view1.setText("love u...");            view1.setTextColor(getResources().getColor(R.color.colorAccent));            line1.addView(view1, lpWrapContent);            TextView view2 = new TextView(getApplicationContext());            view2.setText("love u...");            view2.setTextColor(getResources().getColor(R.color.colorAccent));            line2.addView(view2, lpExactDimension);        }        container.addView(line1, lpWrapContent);        container.addView(line2, lpWrapContent);        setContentView(container);    }

显然,LinearLayout 对 ImageView 的 layout 策略是无法满足我们的需求的。

ZLayout 可以做到。只需将设置其一个属性即可:

z:maxLines = "1"

z.setMaxLines(1);

即,ZLayout 最多只有1行子控件,其余的不做处理。

6 References

  • ZLayout
  • 安卓自定义控件原理及实践
0 0