需求背景 在做一些账号相关的功能时,我们经常有以下场景,需要展示一个人的头像、名称,名称后要跟一个小标签,比如勋章/成就图标、定位图标、等级图标等,大致如下:
用常规方式实现上面这种布局,如下:(为了简化代码,尽可能看到关键的约束条件和参数,我写了点 style 把不太相关的参数统一声明了,后面的代码示例也是这样,不做赘述)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <androidx.constraintlayout.widget.ConstraintLayout android:id ="@+id/cl_container_0" android:layout_width ="match_parent" android:layout_height ="wrap_content" app:layout_constraintTop_toTopOf ="parent" > <ImageView android:id ="@+id/iv_avatar_0" style ="@style/ConstraintLayoutTips.Avatar" app:layout_constraintStart_toStartOf ="parent" /> <TextView android:id ="@+id/tv_name_0" style ="@style/ConstraintLayoutTips.Name" app:layout_constraintStart_toEndOf ="@id/iv_avatar_0" /> <ImageView android:id ="@+id/iv_position_0" style ="@style/ConstraintLayoutTips.Position" android:layout_marginStart ="10dp" app:layout_constraintStart_toEndOf ="@id/tv_name_0" /> </androidx.constraintlayout.widget.ConstraintLayout >
但是名称是用户自己定义的,有长有短,遇到太长的名字时,name 控件会穿出屏幕边界,把右侧的 position 挤得不可见,如下:
实际上我们想要 position 控件跟着 name 控件的长度改变位置,但是最右只能去到屏幕边界,不能被挤出去,同理 name 控件既要根据文字内容来展示,又要在宽度即将超出时进行折叠,末尾显示为 … 的形式,效果如下:
要达到这种效果,直接沿用上面的三个控件的结构,暂时我还没有办法做到,这时需要加入第四个控件(如果需求里跟在名称后的控件有两个,就省了这个操作,如果没有,直接加一个 1dp 宽高的占位 View 即可),然后分两种方式达到我们想要的效果
解决方案 第一种方案 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <androidx.constraintlayout.widget.ConstraintLayout android:id ="@+id/cl_container_1" android:layout_width ="match_parent" android:layout_height ="wrap_content" app:layout_constraintTop_toBottomOf ="@id/cl_container_0" > <ImageView android:id ="@+id/iv_avatar_1" style ="@style/ConstraintLayoutTips.Avatar" app:layout_constraintStart_toStartOf ="parent" /> <TextView android:id ="@+id/tv_name_1" style ="@style/ConstraintLayoutTips.Name" app:layout_constrainedWidth ="true" app:layout_constraintEnd_toStartOf ="@id/placeholder_view_1" app:layout_constraintStart_toEndOf ="@id/iv_avatar_1" /> <View android:id ="@+id/placeholder_view_1" style ="@style/ConstraintLayoutTips.Placeholder" android:layout_marginStart ="10dp" app:layout_constraintStart_toEndOf ="@+id/tv_name_1" /> <ImageView android:id ="@+id/iv_position_1" style ="@style/ConstraintLayoutTips.Position" app:layout_constraintEnd_toEndOf ="parent" app:layout_constraintHorizontal_bias ="0.0" app:layout_constraintStart_toEndOf ="@id/placeholder_view_1" /> </androidx.constraintlayout.widget.ConstraintLayout >
与原始布局的不同点在于:
name 控件多了 app:layout_constrainedWidth=”true” 属性,而且右侧约束 placeholder_view
多了一个 placeholder_view 占位控件,placeholder_view 左侧约束 name 控件
position 控件多了 app:layout_constraintHorizontal_bias=”0.0” 属性,而且左侧约束 placeholder_view,右侧约束父布局
这个方案的基本原理在于 “推动”,name 控件推动 placeholder_view,placeholder_view 控件推动 position,推到边界时,position 控件右侧约束父布局,再也推不动了,理论上来说 position 会被限制在边界,但事实上还要再给 name 加上 app:layout_constrainedWidth=”true” 属性,对 name 控件进行强制约束,这时才能达到效果
同时还要注意,这里的 name、placeholder_view 和 position 不能形成链,形成链的话要方案二的一些处理才能达到最终效果,讲方案二时会再展开
第二种方案 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <androidx.constraintlayout.widget.ConstraintLayout android:id ="@+id/cl_container_2" android:layout_width ="match_parent" android:layout_height ="wrap_content" app:layout_constraintTop_toBottomOf ="@id/cl_container_1" > <ImageView android:id ="@+id/iv_avatar_2" style ="@style/ConstraintLayoutTips.Avatar" app:layout_constraintStart_toStartOf ="parent" /> <TextView android:id ="@+id/tv_name_2" style ="@style/ConstraintLayoutTips.Name" android:layout_width ="0dp" android:layout_height ="wrap_content" app:layout_constraintEnd_toStartOf ="@id/placeholder_view_2" app:layout_constraintHorizontal_bias ="0.0" app:layout_constraintHorizontal_chainStyle ="packed" app:layout_constraintStart_toEndOf ="@id/iv_avatar_2" app:layout_constraintWidth_default ="wrap" /> <View android:id ="@+id/placeholder_view_2" style ="@style/ConstraintLayoutTips.Placeholder" android:layout_marginStart ="10dp" app:layout_constraintEnd_toStartOf ="@id/iv_position_2" app:layout_constraintStart_toEndOf ="@+id/tv_name_2" /> <ImageView android:id ="@+id/iv_position_2" style ="@style/ConstraintLayoutTips.Position" app:layout_constraintEnd_toEndOf ="parent" app:layout_constraintStart_toEndOf ="@id/placeholder_view_2" /> </androidx.constraintlayout.widget.ConstraintLayout >
与原始布局的不同点在于:
name 控件 android:layout_width=”0dp”,多了 app:layout_constraintWidth_default=”wrap” 属性,设置了 app:layout_constraintHorizontal_bias=”0.0”、app:layout_constraintHorizontal_chainStyle=”packed”,而且右侧约束 placeholder_view
多了一个 placeholder_view 占位控件,placeholder_view 左侧约束 name 控件,右侧约束 position 控件
position 控件左侧约束 placeholder_view,右侧约束父布局
与方案一的不同点在于:
三个控件构成了一条链
主要处理的控件是 name 控件
这个方案的基本原理在于 “链”,三个控件构成了一条链,且把它们 packed 打包到一起,用 app:layout_constraintHorizontal_bias=”0.0” 控制居左显示,最后用 android:layout_width=”0dp” 配合 app:layout_constraintWidth_default=”wrap”,处理了 name 控件字数不多时没有根据字数动态展示宽度的问题(不设置这个参数,它会把控件占满,因为 width=”0dp”)
需要注意
layout_constraintHorizontal_bias 用于控制布局相对水平约束空间的位置百分比,在约束主体只有一个时才有效(不知道这样说是不是合理,大概是这个意思吧),也就是说分两块时,要把 layout_constraintHorizontal_chainStyle 设置为 packed,将其绑成一块才行,否则不会生效(vertical 同理)
layout_constraintWidth_default 需要在 layout_width 为 0dp 时才能生效,因为它就是用来控制 layout_width 为 0dp 时的布局状态的(height 同理)
参考文章 看了这位哥们的文章 学到的,加了些自己的理解,整理归纳