2020 年 7 月更新:受约束的宽度/高度有什么作用?
constrainedWidth/Height
很多人一直问我,当设置为 true(默认为 false)时,它到底做了什么。我终于有了一个答案(来自谷歌员工),所以为了代替澄清所有来这篇文章的人一直有的疑虑,这就是我收集的内容(有些是我的话,有些是来自原始谷歌问题报价的直接引用。
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作为文本视图的宽度,并且它的约束将其固定到屏幕的边缘(开始/结束到父级)。那些可以拉向不同的方向;文本视图可能希望很小以包裹文本,并且约束将拉动小部件的边缘以到达父开始/结束。这里有一场战斗。(如果文字大于空格,战斗仍然存在,但方向相反)。
存在此属性的原因是因为某些小部件 (_Like textView
) 采用了一些快捷方式,并且当存在 时0dp
,它们可能并不总是正确更新。重要的是要注意具有 0dp + 权重的 LinearLayouts 做了同样的事情(因此这也是 LL 的问题);通过使用constrainedWidth/Height
,像 TextView 这样的小部件可以在需要时正确使用 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 没有)。
而不是FrameLayout
包含 aLinearLayout
您现在可以删除所有这些并以这种方式拥有它:
<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 Priorities 好得多。(至少它对我来说更容易掌握)。为谷歌竖起大拇指:)
旧答案(以防您仍然需要它)。
根据 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>
所以在我的真实应用程序中,这个 FrameLayout 在另一个 ConstraintLayout 里面,它的左边有一个图标和一些其他的东西,但是为了这个例子,想象你必须把这个 FrameLayout 的左/右“固定”到你的任何空间想占领。在这个例子中,你可以看到我parent
在所有约束中都使用了,但是这个 FrameLayout 的左右可能还有其他小部件;由于 ConstraintLayout 的魔力,这将占据所有可用空间。
现在到了技巧的第二部分......因为 ConstraintLayout 保证 FrameLayout 将使用我们拥有的“所有空间”并且永远不会更多(或更少),我现在可以在内部使用 LinearLayout......就像这样......</p>
<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 的宽度和weight
1 的宽度,这意味着在所有其他小部件都计算出它们的宽度后它将占用所有可用的空闲空间。
在这种特殊情况下,另一个孩子(ImageView)caret
没有指定权重和固定宽度,因此 TextView 不必与其他任何人共享/分割可用空间,它可以全部占用(但只有可用空间,记住它宽度为 0dp)。
这种效率较低的方法,有效地实现了我想要的,尽管如果你愿意的话,可以使用更少的 ConstraintLayout Magic 。
从好的方面来说,我不必在requestLayout()
完成所有数学运算后创建自定义视图、执行数学运算并发出 a;这种效率较低的方法将/应该扩展,并且在 ConstraintLayout 提供有效的替代方案之前,它可能就足够了。
向在社交媒体上回复并最终花时间思考这个问题的 Google 工程师大喊大叫。或许在未来,他们在写关于 ConstraintLayout 1.1 的任务和故事点时,会记住这一点并想出一个好的解决方案