ConstraintLayout 链和右侧的文本省略号 + 图像

ConstraintLayout Chains and Text Ellipsis + Image on the Right

2020 年 7 月更新:

在下面的答案中添加了信息以更详细地解释 why/what constrainedWidth/Height 的作用以及何时适用。

2018 年 7 月更新:

如果您使用 ConstraintLayout 1.1.0,正确的 属性 是 app:layout_constrainedWidth="true" 代替旧的 app:layout_constraintWidth_default="wrap"(和对应的身高)。查看更新的答案。

2017 年 11 月更新:

如果您使用的是 ConstraintLayout 1.0.0 稳定版(或更高版本)(此时为 1.0.2),请参阅更新后的答案以获得无需嵌套布局的更简单解决方案。

原问题:

使用 2016 年 11 月 3 日发布的 ConstraintLayouts-Beta3。(source)

我正在尝试执行以下操作:

|                                        |
|<-[TextView]<->[ImageView] -----------> |
|                                        |

我用这样的布局实现了:

  <TextView
      android:id="@+id/textView"
      android:layout_height="wrap_content"
      android:layout_width="wrap_content"

      app:layout_constraintHorizontal_chainStyle="packed"

      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:layout_constraintRight_toLeftOf="@+id/caret"
      app:layout_constraintHorizontal_bias="0.0"

      android:text="Some Text"
      android:textAlignment="viewStart"
      android:gravity="start" />

  <ImageView
      android:id="@+id/caret"
      android:layout_width="wrap_content"
      android:layout_height="8dp"
      app:layout_constraintDimensionRatio="1:1"

      app:layout_constraintLeft_toRightOf="@+id/textView"
      app:layout_constraintRight_toRightOf="parent"

      app:layout_constraintTop_toTopOf="@+id/textView"
      app:layout_constraintBottom_toBottomOf="@+id/textView"


      app:layout_constraintHorizontal_bias="0.0"

      android:contentDescription=""
      app:srcCompat="@drawable/ic_selection"
      android:layout_marginStart="8dp"/>

这看起来不错,但是当文本比可用的长时space…

|                                        |
|<-[TextView Larger Than The Space Avail]|
|                                        |

文本视图的样式指定了这些:

<item name="android:lines">1</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>

所以它应该工作,但我不确定我需要什么限制才能让图像滑动到右边然后停止 并让文本视图明白没有更多 space.

我错过了什么?

注意:如果我将 textview 的宽度设置为 0dp,它可以工作,但是图像总是在右边(水平偏差似乎被忽略了)

注意 2:我也用 beta2 试过了,事实上,Beta3 似乎有一个 bug in the visual editor.

更新:我试图在Xcode/AutoLayout中复制这个:

这是短文本的样子

现在相同的布局,我只是在文本视图中键入一个长文本...

如您所见,图像视图的轨迹(右)约束表示:您距离右边距 8 或更多 点。

它也固定在标签 (textView) 的左侧。

根据我刚刚从 Twitter 了解到的情况,目前在 Android 的 ConstraintLayout 上 可能无法实现:Source

实际上,您可以通过以下方式为 TextView 执行此操作——使用 android:maxWidth:

<TextView
    android:id="@+id/textView7"
    android:maxWidth="200dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:maxLines="1"
    android:ellipsize="end"
    android:text="TextViewTextViewTextViewTextViewTextViewTextViewTextViewTextViewTextView"
    tools:layout_editor_absoluteY="72dp"
    android:layout_marginStart="16dp"
    app:layout_constraintLeft_toLeftOf="parent"
    android:layout_marginLeft="16dp" />

<Button
    android:id="@+id/button20"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    tools:layout_editor_absoluteY="56dp"
    android:layout_marginStart="8dp"
    app:layout_constraintLeft_toRightOf="@+id/textView7"
    android:layout_marginLeft="8dp" />

这与您的要求不完全相同(您需要知道并设置大小以限制宽度),但这可能对您有所帮助,直到我们在 ConstraintLayout 本身中支持它。

2020 年 7 月更新:constrainedWidth/Height 做什么?

很多人一直在问我 constrainedWidth/Height 设置为 true(默认为 false)时到底做了什么。 我终于得到了一个答案(来自 Google 员工),所以代替清除所有来到这个 post 的人的疑虑,这是我收集的(有些是我的话,有些是直接的引用 from the original Google issue quote.

ConstraintLayout 需要确定涉及的每个视图的 维度 ,并且取决于所述视图如何 受限 ,它必须执行不同的计算。

给定一个观点:

  1. 如果是固定尺寸,CL 将只使用该尺寸。
  2. 如果是match_parent,CL将使用parent
  3. 的精确维度
  4. 如果是wrap_content,CL会询问widget的尺寸,然后将其作为固定尺寸使用
  5. 如果是0dp,CL将对维度应用约束

1) 固定维度

这是一个 width/height 固定的视图,比如 24dp。在这种情况下,CL 将简单地使用该值,不需要对该小部件进行有关大小调整的其他计算。

2) match_parent

我一直认为这对 CL 无效,但事实证明它的行为与以前的版本一样,它获取 parent 的尺寸并将其用作“固定”。与上面的 #1 不同,我认为这可能在计算上更昂贵,因为 CL 现在需要确保已知 parent 维度能够在这里使用它们。我没有这方面的证据,也没有很多经验,因为我一直认为这不是真的有效,所以从未使用过它。

3) wrap_content

正如预期的那样,视图必须确定其“所需大小”,因此如果它是一个 ImageView,它将根据其来源向 imageView 询问其尺寸。获得该数字后,将其用作固定大小,如#1。

4) 0dp

这是 CL 的亮点,它通过对每个维度(宽度和高度)应用约束,并让维度的值由算法的结果来确定。

那么为什么需要这个 (constrainedWidth/Height)?

首先要了解的是 0dp 具有展开和环绕行为(和百分比);为了 wrap,引擎从视图的 wrap_content(上面的#3)的维度开始,但等待约束到 change它 if/when 需要。假设您使用 wrap 作为文本视图的宽度,并且它的约束将它固定到屏幕的边缘(start/end 到 parent)。 那些可以拉向不同的方向;文本视图可能希望小到 wrap 文本和约束将 拉动 小部件的边缘以达到 parent start/end。这里有一场战斗。 (如果文本比space大,战斗仍然存在,但方向相反)。

之所以存在这个属性,是因为一些widgets(_LiketextView),走一些捷径,当有0dp的时候,他们可能并不总是正确更新。重要的是要注意具有 0dp + 权重的 LinearLayouts 做了 同样的事情 (因此这 也是 LL 的一个问题);通过使用 constrainedWidth/Height,像 TextView 这样的小部件可以在需要时正确地使用 0dp 和 wrapping 行为;它使视图有机会正确地重新测量自身。

当你重用 TexViews 时,这个问题主要表现出来(我不知道哪些 other 视图从中受益,但我假设任何有文本的东西都容易计算 shortcuts/hacks 并且可能需要这些额外的信息才能正确触发重新测量)。像 TextView 这样重用带有文本的 Widget,这是最需要的地方,想想 RecyclerView,其中你的 ViewHolder 在 ConstraintLayout 中(很常见),当你滚动时,ViewHolder 被重用并且 re-bound 到另一个“数据” model" 并且没有这个属性,TextView will/may 无法为可能出现的 new 文本重新计算其大小。

我希望这是有道理的。

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 这个 post 正在使用没有)。

而不是包含 LinearLayoutFrameLayout,您现在可以删除所有内容并以这种方式使用:

    <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>

这完全符合我的要求,没有黑客、指南或硬编码大小。

TextViw 将使用 Constraints 提供的大小(在正常情况下这意味着它要么是错误的,要么会超出'parent'),但是由于有了新属性,这些约束可以是 bent/broken 如果内容是 smaller/larger.

我不得不说它比 iOS 优先级要好得多。 (至少对我来说更容易理解)。对这个点赞 Google :)

旧答案(如果您仍然需要它)。

根据 Nicolas Roard 的回答,我打算创建一个自定义容器,它基本上可以计算可用 space,并以编程方式在 TextView 上设置 maxWidth。我没有向项目添加另一个 class、单元测试、可能的错误集等,而是尝试了一种稍微不那么 高效 嵌套几个布局的方法;考虑到我们从黎明时代就开始嵌套布局,并且这不会出现在任何滚动列表视图上或移动太多(或根本不会移动),并且我正在使用 ConstraintLayouts 来展平大部分层次结构(前所未有!),那么我不认为有点嵌套 直到得到更好的支持 是那么糟糕。

所以我所做的基本上是使用 FrameLayout,通过设计 优化(或认为)有一个 child(它可以包含更多) .此 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 的 left/right“固定”到您想要占用的任何 space。在此示例中,您可以看到我在所有约束中都使用了 parent,但此 FrameLayout 的左右两侧可能还有其他小部件;多亏了 ConstraintLayout 的魔力,这将占据所有可用空间 space.

现在技巧的第二部分来了……因为 ConstraintLayout 保证 FrameLayout 将使用我们拥有的“所有 space”,而且不会更多(或更少),我现在可以在里面使用 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,这非常重要,因为 child TextView 的宽度可以为 0dp,weight 为 1,这意味着它将占用所有 可用的免费 space 在所有其他小部件计算出它们的宽度 .

之后

在这种特殊情况下,另一个 child (ImageView) caret 没有指定重量和固定宽度,因此 TextView 不必 share/split 免费 space 与其他人一起,它可以全部使用(但只能免费 space,记住它的宽度是 0dp)。

这种效率较低的方法,有效地实现了我想要的,尽管如果你愿意的话,使用较少的ConstraintLayoutMagic

从好的方面来说,我不必创建自定义视图、执行数学运算并在完成所有数学运算后发出 requestLayout();这种效率较低的方法 will/should 规模,直到 ConstraintLayout 提供有效的替代方案,它可能就足够了。

向在社交媒体上回复并最终花时间思考这个问题的 Google 工程师致敬。也许以后他们在写关于ConstraintLayout 1.1的任务和故事点的时候,他们会记住这一点并想出一个好的解决方案

您可以在ImageView 的负边距约束布局中使用相对布局。请关注android:layout_marginRight="26dp"android:layout_marginLeft="-26dp"

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_gravity="center_vertical"
            android:layout_marginRight="26dp"
            android:ellipsize="end"
            android:gravity="start"
            android:maxLines="1"
            android:text="Some text" />

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="-26dp"
            android:layout_toRightOf="@+id/textView"
            android:contentDescription=""
            android:src="@drawable/ic_browser" />
    </RelativeLayout>

</android.support.constraint.ConstraintLayout>

当文字很长时:

就我而言,Martin 2018 年 7 月的更新将不起作用:

If you are using ConstraintLayout 1.1.0, the correct property to use is app:layout_constrainedWidth="true" in place of the old app:layout_constraintWidth_default="wrap" (and the height counterpart)

我必须使用 android:width="wrap_content" 来查看 app:layout_constrainedWidth="true" 的文本。 android:layout_width="0dp" (match_constraint) 使文本视图在我的情况下拉伸为较短的字符串。

实现相同结果的另一种可能性是使用带有标志 app:layout_constraintWidth_max="wrap"android:layout_width="0dp"

有关约束布局标志的更多信息,请参见文档: https://developer.android.com/reference/android/support/constraint/ConstraintLayout

此回答的灵感来自 Martin Marconcini 的回答 ()。 为了减少层级,可以用简单的视图代替约束布局。

      <TextView
        android:ellipsize="end"
        android:id="@+id/some_text"
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:lines="1"
        android:maxLines="1"
        app:layout_constraintEnd_toStartOf="@+id/disclosure_arrow"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_default="wrap" /> 

      <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="@id/view_guide"
        app:layout_constraintStart_toEndOf="@id/some_text"
        app:layout_constraintTop_toBottomOf="@id/some_text"
        app:srcCompat="@drawable/your_vector_image" />

      <View
        android:id="@+id/view_guide"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

顺便说一句,我使用的是 ConstraintLayout androidx.constraintlayout:constraintlayout:2.0.0-alpha3app:layout_constrainedWidth="true" 在这种情况下按预期工作。所以我这里用了app:layout_constraintWidth_default="wrap"