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.
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.
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.
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
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:
match_parent
, CL will use the exact dimension of parentwrap_content
, CL will ask the widget for its size, but then use it as a fixed dimension0dp
, CL will apply constraints to the dimensionThis 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.
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.
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.
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.
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. :)
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)
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 :)
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