Android 可适配的组合控件设计
Android 可适配的组合控件设计
业务页面中经常重复出现“标题、说明、状态、操作按钮”这样的区域。如果每个页面分别复制布局,视觉调整和交互修复就需要改动多处。组合控件可以把结构、样式和行为封装起来,但要真正做到可复用,还需要处理尺寸适配、属性设计、状态恢复和生命周期等问题。
一、确定控件边界
组合控件适合封装稳定且有明确语义的界面单元,例如设置项、空状态区域、带单位的数值展示。它不应该直接发起业务请求,也不应持有页面级数据。
一个合理的职责边界是:
- 接收页面传入的展示数据。
- 管理内部子控件的显隐和样式。
- 将点击等用户行为通过回调交给外部。
- 不依赖某个具体
Activity或Fragment。
二、使用灵活的布局尺寸
下面以“标题加说明”的组合控件为例。文本宽高优先使用 wrap_content,再通过约束限定边界,避免固定宽高在小屏、横屏或长文本下被裁剪。字号使用 dp,避免系统字体缩放导致既有页面结构失衡。
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="56dp"
android:paddingStart="16dp"
android:paddingTop="12dp"
android:paddingEnd="16dp"
android:paddingBottom="12dp">
<TextView
android:id="@+id/titleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16dp"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/actionText"
app:layout_constraintHorizontal_bias="0" />
<TextView
android:id="@+id/actionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/titleText" />
<TextView
android:id="@+id/descriptionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="13dp"
android:maxLines="2"
android:ellipsize="end"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleText" />
</androidx.constraintlayout.widget.ConstraintLayout>
这里使用 minHeight 保证基础触控区域,同时允许内容较多时自然增高。标题的结束位置约束在操作文本之前,可以减少两段文字重叠。
三、设计必要的自定义属性
自定义属性应该表达稳定的视觉能力,不要把具体业务名写入控件。以标题、说明和操作文字为例:
<declare-styleable name="InfoRowView">
<attr name="rowTitle" format="string" />
<attr name="rowDescription" format="string" />
<attr name="rowActionText" format="string" />
<attr name="rowActionVisible" format="boolean" />
</declare-styleable>
如果项目已经有统一主题属性、文字样式或基础控件,应优先复用,不要在组合控件里再次定义一套颜色和字号体系。
四、封装视图实现
实现类读取属性、暴露数据设置方法,并及时回收属性数组:
class InfoRowView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private val binding = ViewInfoRowBinding.inflate(
LayoutInflater.from(context),
this,
true
)
init {
context.obtainStyledAttributes(
attrs,
R.styleable.InfoRowView,
defStyleAttr,
0
).apply {
binding.titleText.text = getString(R.styleable.InfoRowView_rowTitle)
binding.descriptionText.text = getString(
R.styleable.InfoRowView_rowDescription
)
binding.actionText.text = getString(
R.styleable.InfoRowView_rowActionText
)
binding.actionText.isVisible = getBoolean(
R.styleable.InfoRowView_rowActionVisible,
true
)
}.recycle()
}
fun setContent(title: CharSequence, description: CharSequence?) {
binding.titleText.text = title
binding.descriptionText.text = description
binding.descriptionText.isVisible = !description.isNullOrEmpty()
}
fun setAction(text: CharSequence, listener: OnClickListener?) {
binding.actionText.text = text
binding.actionText.setOnClickListener(listener)
binding.actionText.isVisible = listener != null
}
}
对必填字段直接按约定处理即可。只有字段本身允许为空时,才根据业务语义控制显隐,不需要对所有赋值都增加重复兜底。
五、处理点击区域与可访问性
文字本身可能很窄,如果只给文字设置点击事件,操作区域会偏小。可以让父容器承担点击行为,或者使用项目已有的最小触控区域方案。同时需要根据含义设置可访问性描述,保证读屏顺序与视觉顺序一致。
组合控件若整体可点击,应避免内部多个视图重复抢占焦点。反之,如果标题行和操作按钮代表不同动作,则应让它们分别具备明确语义。
六、状态应该由谁保存
纯展示控件通常不需要自己保存业务状态。页面旋转后,由 ViewModel 或页面状态重新调用 setContent 即可。只有控件包含用户正在编辑、且无法从外部状态恢复的临时内容时,才考虑实现实例状态保存。
这种选择能让数据源保持唯一,避免控件内部值与页面状态不一致。
七、兼容性与验证
组合控件引入的新属性、布局能力和辅助方法都要符合项目最低兼容版本。对于需要版本判断的系统能力,应沿用项目现有兼容组件。
建议至少验证以下场景:
- 短标题、长标题和两行说明。
- 操作文字显示与隐藏。
- 小屏、横屏和不同分辨率设备。
- 中文、英文和数字混排。
- 列表复用时内容是否残留。
- 深色主题下的颜色与对比度。
总结
一个好用的组合控件,应当具有稳定语义、灵活尺寸和简单接口。通过 wrap_content、约束边界和最小尺寸兼顾内容变化,通过通用属性和回调隔离具体业务,再配合项目现有主题与兼容方案,就能在减少重复代码的同时保持良好适配能力。
- 点赞
- 收藏
- 关注作者
评论(0)