2020 年 7 月更新:受约束的宽度/高度有什么作用?
很多人一直问我constrainedWidth/Height 设置为 true(默认为 false)时到底做了什么。
我终于有了一个答案(来自 Google 员工),所以我收集到的信息(有些是我的话,有些是直接引用 from the original Google issue quote)来代替消除人们对这篇文章的所有疑虑。
ConstraintLayout 需要确定所涉及的每个视图的维度,并且根据所述视图的约束方式,它必须执行不同的计算。
给出一个观点:
- 如果是固定维度,CL 将只使用该维度。
- 如果是
match_parent,CL 将使用父级的确切尺寸
- 如果是
wrap_content,CL 将询问小部件的大小,然后将其用作固定尺寸
- 如果是
0dp,CL 将对维度应用约束
1) 固定尺寸
这是一个宽度/高度固定的视图,例如24dp。在这种情况下,CL 将简单地使用该值,不需要对该小部件进行其他关于大小的计算。
2) match_parent
我一直认为这对 CL 无效,但事实证明它的行为与以前的版本一样,它抓取父级的尺寸并将其用作“固定”。与上面的 #1 不同,我认为这可能在计算上更昂贵,因为 CL 现在需要确保已知父维度能够在此处使用它们。我没有这方面的证据,也没有太多经验,因为我一直认为这不是真的有效,所以从未使用过。
3) wrap_content
正如预期的那样,视图必须确定其“所需大小”,因此如果它是 ImageView,它将根据其来源向 imageView 询问其尺寸。获得该数字后,将其用作固定大小,如#1。
4) 0dp
这是 CL 的亮点,通过对每个维度(宽度和高度)应用约束,并让维度的值由算法的结果来确定。
那么为什么需要这个(约束宽度/高度)?
首先要了解的是0dp 具有展开和环绕行为(和百分比);为了wrap,引擎从视图的wrap_content(上面的#3)的维度开始,但在需要时等待约束更改它。假设您使用 wrap 作为文本视图的宽度,并且它的约束将其固定到屏幕的边缘(开始/结束到父级)。
那些可以拉向不同的方向;文本视图可能希望小到 wrap 文本,并且约束将 拉 小部件的边缘以到达父开始/结束。这里有一场战斗。 (如果文本比空格大,战斗仍然存在,但方向相反)。
这个属性之所以存在,是因为一些小部件 (_Like textView) 采用了一些快捷方式,当有 0dp 时,它们可能并不总是正确更新。需要注意的是,具有 0dp + 权重的 LinearLayouts 做了同样的事情(因此 这也是 LL 的问题);通过使用constrainedWidth/Height,像 TextView 这样的小部件可以在需要时正确使用具有 wrapping 行为的 0dp;它使视图有机会正确地重新测量自身。
当您重用 TexViews 时,这个问题主要表现出来(我不确切知道哪些 其他 视图从中受益,但我认为任何有文本的东西都容易计算 快捷方式/黑客 并且可能需要这些额外的信息才能正确触发重新测量)。重用一个像TextView这样的带有文本的小部件,这是最需要的地方,想想一个RecyclerView,你的ViewHolder在一个ConstraintLayout中(很常见),当你滚动时,ViewHolder被重用并重新绑定到另一个“数据模型”如果没有此属性,TextView 将/可能无法为可能出现的新文本重新计算其大小。
我希望这是有道理的。
tl;dr:这是解决某些小部件在重用时无法重新计算其尺寸的潜在问题的解决方法,特别是在 RecyclerView 中,但很可能不限于此。
你有它。 :)
2018 年 7 月更新:
如果您使用的是ConstraintLayout 1.1.0,则要使用的正确属性是app:layout_constrainedWidth="true",而不是旧的app:layout_constraintWidth_default="wrap"(以及对应的高度)
2017 年 11 月更新
我正在使用 Constraint Layouts 1.0.2,我发现使用 app:layout_constraintWidth_default="wrap"(在 1.0.0 中引入的属性,但本文使用的 Beta 没有)的嵌套较少的解决方案。
您现在可以删除所有这些内容,而不是包含 LinearLayout 的 FrameLayout:
<android.support.constraint.ConstraintLayout
android:id="@+id/new_way_container"
android:layout_height="wrap_content"
android:layout_width="0dp" // THIS GUY USES ALL THE WIDTH.
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:ellipsize="end"
android:id="@+id/some_text"
android:layout_height="wrap_content"
android:layout_width="0dp" //NO WRAP CONTENT, USE CONSTRAINTS
android:lines="1"
android:maxLines="1"
app:layout_constraintEnd_toStartOf="@+id/disclosure_arrow"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed" //CHAIN IT for biasing.
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap" /> //THIS IS THE KEY THAT WILL CAUSE THIS TO WORK
<ImageView
android:id="@+id/disclosure_arrow"
android:layout_height="wrap_content"
android:layout_width="10dp"
app:layout_constraintBottom_toTopOf="@id/some_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/some_text"
app:layout_constraintTop_toBottomOf="@id/some_text"
app:srcCompat="@drawable/your_vector_image" />
</android.support.constraint.ConstraintLayout>
这实际上完全符合我的要求,没有任何技巧、指南或硬编码大小。
TextView 将使用约束提供的大小(在正常情况下,这意味着它要么是错误的,要么会超出“父”),但由于有了新属性,这些约束允许弯曲/如果内容更小/更大,则会损坏。
我不得不说它比 iOS 优先级要好得多。 (至少它对我来说更容易掌握)。在这一点上为谷歌竖起大拇指:)
旧答案(以防您仍然需要它)。
根据 Nicolas Roard 的回答,我将创建一个自定义容器,它基本上可以计算可用空间,并以编程方式在 TextView 上设置 maxWidth。我没有在项目中添加另一个类、单元测试、可能的错误集等,而是尝试了一种效率稍低的方法来嵌套几个布局;考虑到我们从黎明开始就一直在嵌套布局,并且这不会出现在任何滚动列表视图上或移动太多(或根本不移动),并且我正在使用 ConstraintLayouts 来展平大部分层次结构(前所未有!),那么我不认为嵌套一点直到得到更好的支持是那么糟糕。
所以我所做的基本上是使用 FrameLayout,按设计优化(或认为)有一个孩子(但它可以包含更多)。这个 FrameLayout 是这样应用 ConstraintLayout 规则的:
<FrameLayout
android:id="@+id/hostTextWithCaretContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent">
<!-- MY CONTENT GOES HERE -->
</FrameLayout>
所以在我的 real 应用程序中,这个 FrameLayout 位于另一个 ConstraintLayout 中,它的左侧有一个图标和其他一些东西,但是为了这个示例,假设您必须“固定”这个 FrameLayout 的左/右到您想要占用的任何空间。在这个例子中,你可以看到我在所有约束中都使用了parent,但是这个 FrameLayout 的左右可能还有其他小部件;多亏了 ConstraintLayout 的魔力,这将占据所有可用空间。
现在到了技巧的第二部分......因为 ConstraintLayout 保证 FrameLayout 将使用我们拥有的“所有空间”并且永远不会更多(或更少),我现在可以在内部使用 LinearLayout......就像这样......
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/textView"
android:layout_height="wrap_content"
android:layout_width="0dp"
tools:text="Some Text"
android:text="Some Text"
android:textAlignment="viewStart"
android:layout_gravity="center_vertical"
android:gravity="start"
android:ellipsize="end"
android:maxLines="1"
android:layout_weight="1"/>
<ImageView
android:id="@+id/caret"
android:layout_width="8dp"
android:layout_height="8dp"
app:srcCompat="@drawable/ic_selection"
android:contentDescription=""
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" />
</LinearLayout>
精明的读者会注意到,LinearLayout 的宽度有 wrap_content,这非常重要,因为子 TextView 可以有 0dp 的宽度和 1 的 weight,这意味着它将占用所有可用的在所有其他小部件计算出它们的宽度之后的空间。
在这种特殊情况下,另一个子 (ImageView) caret 没有指定权重和固定宽度,因此 TextView 不必与其他任何人共享/分割可用空间,它可以全部占用(但是只有空闲空间,记住它的宽度是 0dp)。
这种效率较低的方法可以有效地达到我想要的效果,尽管如果你愿意的话,可以使用较少的 ConstraintLayout Magic。
从好的方面来说,我不必在完成所有数学运算后创建自定义视图、执行数学运算并发出requestLayout();这种效率较低的方法将/应该扩展,并且在 ConstraintLayout 提供有效的替代方案之前,它可能就足够了。
向在社交媒体上回复并最终花时间思考这个问题的 Google 工程师致敬。或许在未来,他们在写关于 ConstraintLayout 1.1 的任务和故事点时,会记住这一点并想出一个很好的解决方案