【问题标题】:ConstraintLayout Chains and Text Ellipsis + Image on the RightConstraintLayout 链和文本省略号 + 右侧的图像
【发布时间】:2017-03-17 14:03:36
【问题描述】:

2020 年 7 月更新:

在下面的答案中添加了信息,以更详细地解释constrainedWidth/Height 为何/做什么以及何时适用。

2018 年 7 月更新:

如果您使用ConstraintLayout 1.1.0,则要使用的正确属性是app:layout_constrainedWidth="true",而不是旧的app:layout_constraintWidth_default="wrap"(以及对应的高度)。查看更新的答案。

2017 年 11 月更新:

如果您使用的是ConstraintLayout 1.0.0 stable(或更高版本)(此时为 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"/>

这看起来不错,但是当文本长于可用空间时……

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

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

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

所以它应该工作,但我不确定我需要什么约束才能让图像滑动到右边然后停止那里让文本视图理解没有更多空间了。

我错过了什么?

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

注2:我也用beta2试过这个,其实beta3好像有bug in the visual editor

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

这就是短文本的外观

现在同样的布局,我只是在文本视图中输入一个长文本……

您可以看到图像视图的轨迹(右)约束表明:您距离右边距 8 或更多 点。

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

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

【问题讨论】:

    标签: android android-studio android-constraintlayout constraint-layout-chains


    【解决方案1】:

    2020 年 7 月更新:受约束的宽度/高度有什么作用?

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

    ConstraintLayout 需要确定所涉及的每个视图的维度,并且根据所述视图的约束方式,它必须执行不同的计算。

    给出一个观点:

    1. 如果是固定维度,CL 将只使用该维度。
    2. 如果是match_parent,CL 将使用父级的确切尺寸
    3. 如果是wrap_content,CL 将询问小部件的大小,然后将其用作固定尺寸
    4. 如果是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 没有)的嵌套较少的解决方案。

    您现在可以删除所有这些内容,而不是包含 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>
    

    这实际上完全符合我的要求,没有任何技巧、指南或硬编码大小。

    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 的任务和故事点时,会记住这一点并想出一个很好的解决方案

    【讨论】:

    • @lgdroid57 不需要任何代码。 ConstraintLayout 可以做到这一点,而无需观察视图树。链接的解决方案以“我有一个高度会变化的 TextView...”开头。这个问题没有 textView 高度变化。
    • 我正在使用 ConstraintLayout 1.1.2 并且仍然需要过时的 app:layout_constraintWidth_default="wrap" 才能工作。不知道为什么...无法让它与较新的 app:layout_constrainedWidth="true" 一起使用
    • 哦,伙计,这是一个非常有用的答案。我流了几个小时的血,试图了解这一切是如何运作的。
    • @VitaliOlshevski 很高兴听到它帮助您理解 :) 如果您不确定,请随时提出更多问题!祝你好运!
    【解决方案2】:

    在我的情况下,来自 Martin 的 2018 年 7 月更新不起作用:

    如果您使用的是 ConstraintLayout 1.1.0,则要使用的正确属性是 app:layout_constrainedWidth="true" 代替旧的 app:layout_constraintWidth_default="wrap"(和身高对应)

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

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

    更多关于约束布局的标志可以在文档中找到: https://developer.android.com/reference/android/support/constraint/ConstraintLayout

    【讨论】:

    • layout_constraintWidth_default="wrap" 已弃用。请改用 layout_width="WRAP_CONTENT" 和 layout_constrainedWidth="true"。
    【解决方案3】:

    实际上,您可以通过以下方式为 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 本身中支持它。

    【讨论】:

    • 谢谢 Nicolas,这似乎是一个选择;在我不知道宽度的情况下使用起来有点困难,但我想我可以以编程方式设置它并请求一个新的布局......不确定这是否可行(除了第二次布局传递的效率低下,测量,等)
    • 是的,如果 ConstraintLayout 处于 wrap content 模式,那会有点烦人。但是如果它是一个设定的宽度(即固定宽度或 match_parent)并且你知道图像视图的大小,那应该相对容易。我们会想出一个更好的方法,但现在...... :)
    【解决方案4】:

    您可以在 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>
    

    文本较长时:

    【讨论】:

    • 有点 hacky 方面,但肯定是一个不错且聪明的选择。感谢分享。
    【解决方案5】:

    此答案的灵感来自 Martin Marconcini 的答案 (https://stackoverflow.com/a/40491128/1151701)。 为了减少层次结构,可以将约束布局替换为简单的视图。

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

    【讨论】:

      猜你喜欢
      • 2012-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-06
      • 2016-03-11
      • 1970-01-01
      相关资源
      最近更新 更多