情景描述
在安卓组件 XML 布局中,有时候会遇到较复杂的约束关系。比方说,对于下面这个消息对话框中的消息文本,希望它可以文本框大小自适应,且在各组件先后位置确定的情况下,各组件都不超出父组件。限定只能使用 XML 来完成这一功能。
如下图所示,当文本只有一行时,文本框的宽度自动调整至正好包裹文本。
如下图所示,当文本有多行时,文本框的宽度存在一个最大值,且不会将其它组件挤出屏幕外。文本框的高度则伸长至正好包裹文本。
有些读者可能很敏感,没错,这就是高仿微信的消息对话框。当然,实际上的对话框,其左边的背景是白色的,这里为了方便演示,设置成了绿色的。
问题建模
上面的情景可以抽象为以下问题,且为了便于说明,笔者作了一张图如下。
已知父组件 P 中有子组件 a、b、c。其中,a、c 的尺寸是固定的,b 的尺寸不是固定的。需要实现的是:
- 如图所示,保持 a、b、c 之间的顺序不变,且保持以下横向间距不变:
- a 左侧与父组件 P 的左侧的间距
- a、b、c 之间的间距
- b 的尺寸不固定,它的尺寸会自动根据 b 中的内容作调整,但它的最大横向尺寸会受如下约束:
- a、c 均不能超出父组件 P
- c 右侧与父组件 P 的右侧的间距不能小于某个给定的值
- 限定只能使用 XML 来实现本功能。
问题解决
下面给出了实现上述情形中的代码。
【注意】
以下的实现方案是错误的:
-
方案 1:父组件 P 使用 LinearLayout 布局,然后在 b 中设置
android:layout_weight="1"
来自动调整 b 的尺寸,其它组件不设置此项。错误理由:这会导致 b 的尺寸固定为 b 的最大尺寸,而上述情景希望当 b 的原始尺寸没有达到最大尺寸时是不固定的。
-
方案 2:父组件 P 使用 RelativeLayout 布局,然后在 b 中设置
android:layout_width="0dp"
来自动调整 b 的尺寸,其它组件不设置此项。错误理由:同方案 1。
-
方案 3:父组件使用 ConstraintLayout 布局,然后简单设置父组件 P 内各组件的约束关系。
错误理由:这会导致 a、b、c 分散,中间会留很大的空隙。而上述情景希望 a、b、c 之间的间距是固定的。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/dialog_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!--
app:layout_constraintHorizontal_chainStyle="packed" 是为了保证以本组件开头的后续的组件可以贴在一起,
不然它们就会分散开,中间会留很大的空隙。此代码只能在第一个组件上加
-->
<RelativeLayout
android:id="@+id/avatar_layout"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/msg_layout"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0"
app:layout_constraintHorizontal_chainStyle="packed">
<ImageView
android:id="@+id/avatar"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:scaleType="centerInside"
android:src="@drawable/my_default_avatar" />
</RelativeLayout>
<!--
app:layout_constrainedWidth="true" 是为了保证文本框在扩大时,所有组件都不会超出屏幕之外。
如果不加本代码,在 app:layout_constraintHorizontal_chainStyle="packed" 下,
就算有其它代码来约束边界,一样会超出屏幕之外。此代码只能在面积会变化的组件上加
-->
<RelativeLayout
android:id="@+id/msg_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginEnd="12dp"
app:layout_constraintStart_toEndOf="@+id/avatar_layout"
app:layout_constraintEnd_toStartOf="@+id/msg_status_layout"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.5"
app:layout_constrainedWidth="true"
android:background="@drawable/msg_box_me_bg">
<TextView
android:id="@+id/msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="测试文本"
android:textColor="@color/text_color_black"
android:textSize="16sp"
android:textIsSelectable="true"
android:longClickable="true"
android:layout_centerInParent="true" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/msg_status_layout"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginTop="0dp"
android:layout_marginStart="0dp"
android:layout_marginEnd="20dp"
app:layout_constraintStart_toEndOf="@+id/msg_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0"
android:clickable="true">
<ImageView
android:id="@+id/error_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:scaleType="centerInside"
android:src="@drawable/error_icon" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>