ConstraintLayout 链和文本省略号 + 右侧的图像 [英] ConstraintLayout Chains and Text Ellipsis + Image on the Right

查看:44
本文介绍了ConstraintLayout 链和文本省略号 + 右侧的图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

2020 年 7 月更新:

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

2018 年 7 月更新:

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

2017 年 11 月更新:

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

原始问题:

使用 2016 年 11 月 3 日发布的 ConstraintLayouts-Beta3.(

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

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

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

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

解决方案

UPDATE JULY 2020: 约束宽度/高度有什么作用?

很多人一直问我这个 constrainedWidth/Height 在设置为 true(默认为 false)时到底做了什么.我终于有了答案(来自 Google 员工),因此为了消除人们对这篇文章的所有疑问,我收集了以下内容(有些是我的话,有些是直接引用 来自原始的 Google 问题引用.

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 作为文本视图的宽度,并将其限制在屏幕边缘(开始/结束到父视图).那些可以拉向不同的方向;文本视图可能希望很小以包裹文本,并且约束将小部件的边缘以到达父级开始/结束.这里有一场战斗.(如果文本大于空间,战斗仍然存在,但方向相反).

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

这个问题主要在你重用 TexViews 时表现出来(我不知道哪些 其他 视图从中受益,但我认为任何有文本的东西都容易有计算快捷键/黑客 并且可能需要这些额外的信息才能正确触发重新测量).像 TextView 一样重用带有文本的小部件,这是最需要的地方,想想 RecyclerView,其中您的 ViewHolder 位于 ConstraintLayout(很常见),当您滚动时,ViewHolder 被重用并重新绑定到另一个数据模型";如果没有此属性,TextView 将/可能无法为可能出现的文本重新计算其大小.

我希望这是有道理的.

tl;dr:这是一种解决一些小部件在重用时无法重新计算尺寸的潜在问题的解决方法,特别是在 RecyclerView 中,但很可能不限于此.

给你.:)

2018 年 7 月更新:

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

2017 年 11 月更新

我正在使用 Constraint Layouts 1.0.2 并且我发现使用 app:layout_constraintWidth_default=wrap"(一个在 1.0.0 中引入的属性,但 Beta 版)的嵌套较少的解决方案帖子正在使用没有).

代替包含 LinearLayoutFrameLayout,您现在可以删除所有内容并以这种方式使用它:

 <文本视图android:ellipsize="结束"android:id="@+id/some_text";android:layout_height="wrap_content";android:layout_width=0dp"//没有包裹内容,使用限制android:lines="1"机器人:maxLines =1"app:layout_constraintEnd_toStartOf=@+id/disclosure_arrow"app:layout_constraintHorizo​​ntal_bias=0.0";app:layout_constraintHorizo​​ntal_chainStyle=打包"//CHAIN IT 以进行偏置.app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf=父级"app:layout_constraintWidth_default="wrap";/>//这是导致它起作用的关键<图像视图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 优先级要好得多.(至少对我来说更容易掌握).在这一点上为 Google 点赞 :)

旧答案(以防您仍然需要它).

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

所以我所做的基本上是使用 FrameLayout,这是按设计优化(或认为)有一个孩子(它可以包含更多).这个 FrameLayout 是应用了 ConstraintLayout 规则的那个:

 <!-- 我的内容在这里--></FrameLayout>

所以在我的真实应用中,这个 FrameLayout 在另一个 ConstraintLayout 里面,它的左边有一个图标和其他一些东西,但是为了这个例子,假设你必须固定"此 FrameLayout 的左侧/右侧到您想要占据的任何空间.在此示例中,您可以看到我在所有约束中都使用了 parent,但此 FrameLayout 的左侧和右侧可能还有其他小部件;感谢 ConstraintLayout 的魔法,这将占据所有可用空间.

现在是技巧的第二部分……因为 ConstraintLayout 保证 FrameLayout 将使用所有空间";我们拥有并且永远不会更多(或更少),我现在可以在里面使用 LinearLayout ......就像这样......

 <文本视图android:id="@+id/textView";android:layout_height="wrap_content";android:layout_width=0dp"工具:文本=一些文本"android:text="一些文字"android:textAlignment="viewStart"android:layout_gravity=center_vertical"机器人:重力=开始"android:ellipsize="结束"机器人:maxLines =1"android:layout_weight=1"/><图像视图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() ;这种效率较低的方法将/应该扩展,直到 ConstraintLayout 提供有效的替代方案,它可能就足够了.

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

UPDATE JULY 2020:

Added information in the answer below to explain in more detail why/what does constrainedWidth/Height do and when it's applicable to use them.

UPDATE JULY 2018:

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). See updated answer.

UPDATE NOVEMBER 2017:

If you are using ConstraintLayout 1.0.0 stable (or above) (1.0.2 at this time), see the updated Answer for a simpler solution without the need to nest layouts.

Original Question:

Using ConstraintLayouts-Beta3 released on Nov 3rd 2016. (source)

I'm trying to do the following:

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

Which I have achieved with a layout like so:

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

This looks ok, but when the text is longer than the available space…

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

The text view has a style that specifies these:

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

So it should work, but I'm not sure what constraints I need to have the image slide until the right and then stop there and let the text view understand there's no more space.

What am I missing?

Note: If I set the textview's width to the 0dp, it works, but then the image is always on the right (horizontal Bias seems to be ignored for it)

Note2: I have tried this with beta2 as well, in fact, it seems like Beta3 has a bug in the visual editor.

UPDATE: I tried to replicate this in Xcode/AutoLayout:

This is how it looks with a Short Text

Now the same layout, I just type a long text in the text view…

As you can see the trail (right) constraint for the image view says: you're 8 or more points from the right margin.

It's also pinned to the left to the label (textView).

From what I have just learned from Twitter, this may not be possible on Android's ConstraintLayout at the moment: Source

解决方案

UPDATE JULY 2020: What does constrainedWidth/Height do?

A lot of people kept asking me what exactly does this constrainedWidth/Height do when set to true (defaults to false). I finally have an answer (from a Google employee), so in lieu of clearing up all the doubts people coming to this post keep having, here's what I gathered (some are my words, some are direct quotes from the original Google issue quote.

ConstraintLayout needs to determine the dimension of every view involved, and depending on how said view is constrained, it has to perform different calculations.

Given a view:

  1. if it's a fixed dimension, CL will just use that dimension.
  2. if it's match_parent, CL will use the exact dimension of parent
  3. if it's wrap_content, CL will ask the widget for its size, but then use it as a fixed dimension
  4. if it's 0dp, CL will apply constraints to the dimension

1) Fixed Dimension

This is a view whose width/height are fixed, say 24dp. In this case, CL will simply use that value, no other calculation needed for that widget in regards of sizing.

2) match_parent

I always thought this was not valid for CL, but turns out that it behaves like it used to in previous versions, it grabs the dimensions of the parent and uses that as "fixed". Unlike #1 above, I assume this may be more computationally expensive since CL now needs to ensure the parent dimensions are known to be able to use them here. I don't have proof of this nor a lot of experience, since I always thought this wasn't really valid so never used it.

3) wrap_content

As expected, the view has to determine its "required size", so if it's say an ImageView it will ask the imageView for its dimensions based on its source. After said number is obtained, it's used as a Fixed Size, like #1.

4) 0dp

This is where CL shines, by applying the constrains to each dimension (width, and height), and letting the dimension's value be determined by the outcome of the algorithm.

So why is this needed (constrainedWidth/Height)?

The first thing to understand is that 0dp has a spread and wrap behavior (and percent); in order to wrap, the engine starts with the dimension of the view's wrap_content (#3 above) but waits for constrains to change it if/when needed. Say you use wrap for the width of a text view, and its constrains pin it to the edges of the screen (start/end to parent). Those can be pulling in different directions; the text view will likely want to be small to wrap the text and the constrains will pull the edges of the widget to reach the parent start/end. There's a battle here. (if the text is larger than the space, the battle still exists, but in the opposite direction).

The reason why this attribute exists, is because some widgets (_Like textView), take some shortcuts and when there's a 0dp, they may not always correctly update. It's important to note that LinearLayouts with 0dp + weights did the same thing (hence why this was an issue with LL too); by using constrainedWidth/Height, a widget like a TextView can correctly use 0dp with wrapping behavior when needed; it gives the view a chance to correctly remeasure itself.

This problem mostly manifests when you reuse TexViews (I don't know exactly which other views benefit from this, but I assume anything that has text is prone to have calculation shortcuts/hacks and may need this extra bit of info to correctly trigger a remeasure). Reusing a Widget with Text like a TextView, is where this is mostly needed, think of a RecyclerView where your ViewHolder is in a ConstraintLayout (quite common), when you scroll, the ViewHolder is reused and re-bound to another "data model" and without this attribute, the TextView will/may fail to recalculate its size for the new text that may be coming.

I hope this makes sense.

tl;dr: it's a workaround to fix potential issues with some widgets that fail to recompute their dimensions when reused, notably in a RecyclerView, but most likely not limited to it.

There you have it. :)

UPDATE JULY 2018:

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)

UPDATE NOVEMBER 2017

I’m using Constraint Layouts 1.0.2 and I have found a less nested solution using app:layout_constraintWidth_default="wrap" (a property that got introduced in 1.0.0 but the Beta this post was using didn’t have).

Instead of the FrameLayout that contains a LinearLayout you can now remove all that and have it this way:

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

This effectively does exactly what I want, without hacks or guidelines or hardcoded sizes.

The TextView will use the size provided by the Constraints (which under normal circumstances would mean it would either be wrong or will grow beyond the ‘parent’), but thanks to the new attribute, those constraints are allowed to be bent/broken if the content is smaller/larger.

I have to say it works much better than iOS Priorities. (At least it’s a lot easier to grasp to me). Thumbs up for Google on this one :)

OLD ANSWER (in case you still need it).

Based upon Nicolas Roard's answer, I was going to create a custom container that would basically calculate the available space, and programmatically set the maxWidth on the TextView. Instead of adding another class, unit test, possible set of bugs, etc., to the project, I tried a slightly less efficient method of nesting a couple of layouts; considering we've been nesting layouts since the age of dawn and that this is not going to be on any scrolling list view or moving too much (or at all) and that I am using ConstraintLayouts to flatten most of the hierarchy (like never before!), then I don't think a little nesting until this is better supported is that bad.

So what I did was basically, use a FrameLayout, which is by design optimized (or thought) to have one child (tho it can contain more). This FrameLayout is the one that has the ConstraintLayout rules applied like so:

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

So in my real app, this FrameLayout is inside another ConstraintLayout that has an icon to its left and some other stuff, but for the sake of this example, imagine you have to "pin" the left/right of this FrameLayout to whatever space you want to occupy. In this example you can see I'm using parent in all constraints, but there could be other widgets left and right of this FrameLayout; thanks to ConstraintLayout's magic, this will occupy all that available space.

Now here comes the 2nd part of the trick… since ConstraintLayout guarantees that the FrameLayout will use "all the space" we have and never more (or less), I can now use a LinearLayout inside… like so…

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

Astute readers will notice that the LinearLayout has wrap_content in its width, that's very important for then the child TextView can have a width of 0dp and a weight of 1, meaning it will take all available free space after all the other widgets have calculated their width.

In this particular case the other child (ImageView) caret has no weight specified and a fixed width, therefore the TextView doesn't have to share/split the free space with anybody else and it can take it all (but only free space, remember its width is 0dp).

This less efficient approach, effectively achieves exactly what I wanted, albeit with less ConstraintLayout Magic if you will.

On the plus side, I didn't have to create a custom view, perform math and issue a requestLayout() after all my math was done; this less efficient approach will/should scale and until ConstraintLayout offers a valid alternative, it may be enough as it is.

Shoutout to the Google engineer(s) who replied on social media and eventually took the time to think about this. Maybe in the future, when they are writing tasks and story points about ConstraintLayout 1.1, they remember this and come up with a good solution

这篇关于ConstraintLayout 链和文本省略号 + 右侧的图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆