Text wrapping in Constraint layout - android

Trying to make a really simple layout for a settings page using ConstraintLayout.
Simple text views to the left one below the other and a switch to the right to the center.
The layout I have works fine for newer devices, but as soon I switch to a Nexus 4 or older, the switch goes below/disappears from the view.
Here is my layout code,
<android.support.constraint.ConstraintLayout
android:id="#+id/locationLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="parent">
<TextView
android:id="#+id/locationTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="#string/location_title"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="#id/guidelineLocationLayout"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:textSize="16sp"/>
<TextView
android:id="#+id/locationDescription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/location_description"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#+id/locationTitle"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="#id/locationTitle"
app:layout_constraintRight_toLeftOf="#id/guidelineLocationLayout"
android:textSize="12sp"
/>
<Switch
android:id="#+id/locationPermissionSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="#id/guidelineLocationLayout"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="parent"/>
<View
android:id="#+id/viewLocationLayout"
android:layout_width="fill_parent"
android:layout_height="1dip"
android:background="#d6d6d6"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/locationDescription"/>
<android.support.constraint.Guideline
android:id="#+id/guidelineLocationLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="48dp" />
</android.support.constraint.ConstraintLayout>
And here are how the constraints look like for the Pixel 2XL,
Here is how they look on a smaller device,
I have used 0dp width for the long description and defined left and right constraints, anything else I could do?

You can use barriers instead of guidelines because a Barrier references multiple widgets as input, and creates a virtual guideline based on the most extreme widget on the specified side.
According to the Documentation
Similar to a guideline, a barrier is an invisible line that you can
constrain views to. Except a barrier does not define its own position;
instead, the barrier position moves based on the position of views
contained within it. This is useful when you want to constrain a view
to the a set of views rather than to one specific view.
Hope this helps you. I've used barrier instead of guideline.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/locationLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="parent">
<TextView
android:id="#+id/locationTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Save location to the song"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/locationDescription"
android:layout_width="261dp"
android:layout_height="32dp"
android:text="Your location is only requested when a new song is identified. Location details are never sent to the server"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#+id/locationTitle" />
<android.support.constraint.Barrier
android:id="#+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="locationDescription,locationTitle" />
<Switch
android:id="#+id/locationPermissionSwitch"
android:layout_width="41dp"
android:layout_height="26dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/barrier"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/viewLocationLayout"
android:layout_width="fill_parent"
android:layout_height="1dip"
android:background="#d6d6d6"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/locationDescription" />
</android.support.constraint.ConstraintLayout>

You need to change the constraints of the switch like this
<Switch
android:id="#+id/locationPermissionSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="#id/guidelineLocationLayout"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
You are constraining the bottom of the switch to the top of the parent and top of the switch to the bottom of the parent. You have to change it to layout_constraintTop_toTopOf and layout_constraintBottom_toBottomOf

Related

Dynamic view height in constraint layout with chain style packed/spread

It has been quite some time since I used XML layout for Android. I am struggling with what seems to be very simple thing, yet I cannot find a quick solution.
I have a constraintLayout with an image, title, description and a button:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstFragment">
<ImageView
android:id="#+id/image_cover"
android:layout_width="200dp"
android:layout_height="300dp"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:background="#android:color/holo_red_dark"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:textColor="#color/black"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/image_cover"
app:layout_constraintTop_toTopOf="#id/image_cover"
tools:text="Title" />
<TextView
android:id="#+id/description"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#id/title"
app:layout_constraintStart_toStartOf="#id/title"
app:layout_constraintBottom_toTopOf="#id/button"
app:layout_constraintEnd_toEndOf="#id/title"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="spread_inside"
android:text="#string/some_very_long_text"/>
<Button
android:id="#+id/button"
app:layout_constraintTop_toBottomOf="#id/description"
app:layout_constraintBottom_toBottomOf="#id/image_cover"
app:layout_constraintStart_toStartOf="#id/title"
android:layout_width="wrap_content"
android:padding="0dp"
android:layout_margin="0dp"
android:background="#color/black"
android:text="Button"
android:layout_height="36dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
I want to achieve the following result when the description is short:
Description is aligned to bottom of the title and button aligned to bottom of the image
The following result when description is long:
the button is pushed down/aligned to bottom of the description
I tried to create a chain with either packed (and vertical bias) or spread-inside, however I am only able to achieve either result 1 or 2 and not both.
The idea to do it in XML only, not in Java/Kotlin code.
Since you specify that the button is 39dp in height, you can set a Space widget 39dp from the bottom of the description using a bottom margin. Now set a barrier with a direction = bottom to the Space widget and the description. Now the barrier will float between the bottom of the Space widget and the bottom of the description and will alight on whichever is lower.
Now you can set the top of the button to the bottom of the Barrier and the button will float. The XML is below.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstFragment">
<ImageView
android:id="#+id/image_cover"
android:layout_width="200dp"
android:layout_height="300dp"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:background="#android:color/holo_red_dark"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:textColor="#color/black"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/image_cover"
app:layout_constraintTop_toTopOf="#id/image_cover"
tools:text="Title" />
<TextView
android:id="#+id/description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/some_very_long_text"
app:layout_constraintBottom_toTopOf="#id/button"
app:layout_constraintEnd_toEndOf="#id/title"
app:layout_constraintStart_toStartOf="#id/title"
app:layout_constraintTop_toBottomOf="#id/title"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="spread_inside" />
<Space
android:id="#+id/space"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="36dp"
android:padding="0dp"
app:layout_constraintBottom_toBottomOf="#id/image_cover"
app:layout_constraintStart_toStartOf="#id/title" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_margin="0dp"
android:background="#color/black"
android:padding="0dp"
android:text="Button"
app:layout_constraintStart_toStartOf="#id/title"
app:layout_constraintTop_toBottomOf="#id/barrier" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="description,space" />
</androidx.constraintlayout.widget.ConstraintLayout>
If you don't explicitly know the height of the button, you can create an invisible clone of the button with the same constraints and use the invisible button for the barrier.
Here is another version that also works, but I am not sure why. It doesn't use a Space.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstFragment">
<ImageView
android:id="#+id/image_cover"
android:layout_width="200dp"
android:layout_height="300dp"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:background="#android:color/holo_red_dark"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:text="Title"
android:textColor="#color/black"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/image_cover"
app:layout_constraintTop_toTopOf="#id/image_cover" />
<TextView
android:id="#+id/description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/some_very_long_text1"
app:layout_constraintBottom_toTopOf="#id/button"
app:layout_constraintEnd_toEndOf="#id/title"
app:layout_constraintStart_toStartOf="#id/title"
app:layout_constraintTop_toBottomOf="#id/title"
app:layout_constraintVertical_bias="0.0"/>
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_margin="0dp"
android:background="#color/black"
android:padding="0dp"
android:text="Button"
app:layout_constraintBottom_toBottomOf="#id/barrier"
app:layout_constraintStart_toStartOf="#id/title" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="image_cover,description" />
</androidx.constraintlayout.widget.ConstraintLayout>
Update: I was curious about the last layout above. Although it works, I wouldn't use it and I have come to the conclusion that it is a bug since the barrier is located below the button but should be immediately below either the description or the image cover. I am using "androidx.constraintlayout:constraintlayout:2.1.4".
More: I no longer think that this is a bug but a reasonable resolution to a, seemingly, impossible condition: The barrier is positioned to the bottom of the TextView and ImageView. The button's bottom is constrained to the barrier while the TextView's bottom is constrained to top of the button. If the barrier is positioned immediately below its referenced views then the wrap_content height of the TextView cannot be honored since the button will intrude into the TextView height (For the long text.)
What is pictured above could be a compromise. The barrier is still below its referenced views, although much farther below, and all constraints and layout sizes can be honored.
It's hard to tell if this is intentional or not or if it will continue to be the case going forward. I believe that the Space solution is the better solution.

ConstraintLayout Prevent Overlap of Top and Bottom Widgets

I am trying to create a layout with three groups of widgets. I want one group constrained to the top, one to the bottom, and one to float in the middle of the space between the two groups. The layout will be used in a fragment, so it could become squashed, especially when the keyboard is shown. I am having trouble figuring out how to prevent overlap of the two groups, while keeping the top and bottom constrained to the top and bottom of the layout.
I thought of several ways to do it, but I couldn't figure out how to implement any of the properly.
Using app:layout_constraintVertical_bias or app:layout_constraintVertical_weight to pull widgets together or spread them apart.
Using one chain, but having breaks in between the groups. I couldn't figure out how to split the chain part way through to allow the different groups to spread out.
Specify a minimum height that is dependent upon the contained widgets. (It will be contained in a ScrollView so it if the height is constrained it will work)
Create groups of packed chains where the packed chains are in a spread chain.
Or is there a better way than these to get what I want?
Note: I would prefer that the top and bottom groups aren't right against the parent layout, but I couldn't figure out how to get those groups to dynamically distance themselves from the parent layout without also spreading the group apart. I want the top and bottom groups close to the parent layout, but not necessarily right against it. It is better to have them right against the parent layout than close to the middle. Ideally I would be able to use app:layout_constraintVertical_bias or app:layout_constraintVertical_weight for this.
Code
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" >
<EditText
android:id="#+id/edit_text_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/edit_text_horizontal_margin"
android:layout_marginRight="#dimen/edit_text_horizontal_margin"
android:layout_marginTop="#dimen/edit_text_vertical_margin"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/edit_text_2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:hint="Edit Text 1"
android:selectAllOnFocus="true" />
<EditText
android:id="#+id/edit_text_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/edit_text_horizontal_margin"
android:layout_marginRight="#dimen/edit_text_horizontal_margin"
android:layout_marginTop="#dimen/edit_text_vertical_margin"
android:layout_marginBottom="#dimen/edit_text_vertical_margin"
app:layout_constraintTop_toBottomOf="#id/edit_text_1"
app:layout_constraintBottom_toTopOf="#id/button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:hint="Edit Text 2"
android:selectAllOnFocus="true" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#id/edit_text_2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:enabled="false"
android:text="Button" />
<ProgressBar
android:id="#+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/progress_bar_margin"
app:layout_constraintTop_toBottomOf="#id/button"
app:layout_constraintBottom_toTopOf="#id/clickable_text_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:visibility="gone" />
<TextView
android:id="#+id/clickable_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/text_button_horizontal_margin"
android:layout_marginRight="#dimen/text_button_horizontal_margin"
android:layout_marginTop="#dimen/text_button_vertical_margin"
android:layout_marginBottom="#dimen/text_button_vertical_margin"
android:paddingLeft="#dimen/text_button_horizontal_margin"
android:paddingRight="#dimen/text_button_horizontal_margin"
android:paddingTop="#dimen/text_button_vertical_margin"
android:paddingBottom="#dimen/text_button_vertical_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:text="Clickable Text View"
android:clickable="true"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Images
- - - -
Here is what I have, and how I want it to look. - - - - - Here is the overlapping problem.
for the textview add a constraint top to bottom of the button ie app:layout_constraintTop_toBottomOf="#+id/button". IMO always constraint all 4 angles wherever possible; start top end bottom. In that way when your view is 'squashed', it will still adhere to the constraint set
Here, try this.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="#+id/emailId"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="36dp"
android:hint="Enter email ID"
android:inputType="textEmailAddress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="36dp"
android:hint="Enter password"
android:inputType="textPassword"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/emailId" />
<Button
android:id="#+id/signInBtn"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:background="#color/colorAccent"
android:text="Sign in"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/password" />
<TextView
android:id="#+id/analyticsLog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="Click me to create a log"
android:textColor="#android:color/holo_blue_dark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
I believe that you can use Guidelines together with app:layout_constraintHeight_percent to solve your problem.
Consider this layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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">
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:enabled="false"
android:text="Button"
app:layout_constraintBottom_toTopOf="#+id/guideline3"
app:layout_constraintHeight_percent="0.1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#id/edit_text_2" />
<EditText
android:id="#+id/edit_text_2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:hint="Edit Text 2"
android:selectAllOnFocus="true"
app:layout_constraintBottom_toTopOf="#id/button"
app:layout_constraintHeight_percent="0.1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#id/edit_text_1" />
<EditText
android:id="#+id/edit_text_1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:hint="Edit Text 1"
android:selectAllOnFocus="true"
app:layout_constraintBottom_toTopOf="#id/edit_text_2"
app:layout_constraintHeight_percent="0.1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="#+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="#id/clickable_text_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#id/button" />
<TextView
android:id="#+id/clickable_text_view"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:clickable="true"
android:gravity="center"
android:text="Clickable Text View"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHeight_percent="0.1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/guideline3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent=".35" />
Every view height is equal to 10% of the screen height, that's because of the app:layout_constraintHeight_percent="0.1" attribute. This will solve the overlapping problem for you because you will be able to tell your view what height to be relative to the screen size.
I was using guidelines as well with the app:layout_constraintGuide_percent=".35" attribute to tell the guideline were to be on the screen, by doing that I can constrain different views to it and you won't have to stick your views to the top of bottom of the screen as you mentioned (I would prefer that the top and bottom groups aren't right against the parent layout)
Your layout will look like this:(this is preview image for better understanding guidelines)
for who has same problem. use app:layout_constraintVertical_chainStyle="packed" with app:layout_constraintVertical_bias="1" to attach the view to botom. if use app:layout_constraintVertical_bias="0"the view will attach to top.
<TextView
...
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/button"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintVertical_bias="1"/>

ConstraintLayout set constraints on views to grow but not touch

I have three views in a constraint layout A (textview), B(textview) and C (imageview). B have dynamic text and hence I want it to grow as much as possible but without touching C. It can have multiple lines but I don't want to fix its width and shouldn't make C off screen. How can I use constraint layout for such layout. This is what I want to achieve https://i.stack.imgur.com/EIbnz.png If the text is more, make it multiline. This is what I get https://i.stack.imgur.com/VG7Z5.png
.The last image view is pushed out of screen if text is long.
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:background="#drawable/button_background_rounded_gray"
android:id="#+id/container"
android:layout_width="wrap_content"
android:layout_height="30dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/docIcon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:textAllCaps="true"
android:gravity="center"
android:text="PDF"
android:textColor="#color/tcWhite"
android:background="#drawable/button_background_rounded_red"
android:layout_marginStart="10dp"
android:textSize="7sp"
android:layout_width="20dp"
android:layout_height="20dp"/>
<androidx.appcompat.widget.AppCompatTextView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:textSize="12sp"
android:id="#+id/docName"
tools:text="Accommodation Voucher - Circus at Disneyland Paris - Demo - 11 Oct.pdf"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
app:layout_constraintStart_toEndOf="#+id/docIcon"
android:layout_gravity="center_vertical"/>
<androidx.appcompat.widget.AppCompatImageView
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="#+id/docStatus"
app:srcCompat="#drawable/ic_picture_as_pdf_red_24dp"
android:layout_marginEnd="10dp"
android:layout_width="20dp"
android:layout_height="20dp"
app:layout_constraintStart_toEndOf="#+id/docName"/>
You don't show how the overall width for ConstraintLayout that you posted is determined. For this answer, I use match_parent but another constraint will work as well.
I key to allowing the text to expand to the constraints but still honor the constraints is to set app:layout_constrainedWidth="true" on the TextView. I have also placed the three views into a horizontal chain. See the ConstraintLayout Developer Guide.
WRAP_CONTENT : enforcing constraints (Added in 1.1)If a dimension is set to WRAP_CONTENT, in versions before 1.1 they will be treated as a literal dimension -- meaning, constraints will not limit the resulting dimension. While in general this is enough (and faster), in some situations, you might want to use WRAP_CONTENT, yet keep enforcing constraints to limit the resulting dimension. In that case, you can add one of the corresponding attribute:
app:layout_constrainedWidth=”true|false”
app:layout_constrainedHeight=”true|false”
activity_main.xml
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/docIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="10dp"
android:background="#drawable/ic_launcher_background"
android:gravity="center"
android:text="PDF"
android:textColor="#android:color/white"
android:textSize="7sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/docName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:textAllCaps="true" />
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/docName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="This is a very long name. This is a very long name. This is a very long name. "
android:textSize="12sp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/docStatus"
app:layout_constraintStart_toEndOf="#+id/docIcon"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/docStatus"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/docName"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_launcher_foreground" />
</androidx.constraintlayout.widget.ConstraintLayout>
Here is a simpler demonstration.
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="400dp"
android:layout_height="wrap_content"
android:background="#android:color/darker_gray">
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Type here..."
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="#+id/imageView"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="#+id/button"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#mipmap/ic_launcher" />
</androidx.constraintlayout.widget.ConstraintLayout>
Add those two properties to the second AppCompactTextView
app:layout_constraintRight_toRightOf="#id/docStatus"
app:layout_constraintHorizontal_bias="1"
And make the width of that textView to 0dp.

constraintlayout wraps its content width with children match_constraint

I have a simple side bar with 2 buttons vertical chained and i want my side bar to wrap largest button while smaller button expands to match largest one.
Here is a capture where i want "AAA" button to match "VALIDATE" button width
Of course what poped my mind is to use match contraint width for both buttons but when using a wrap_content container this leads to:
my layout :
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:background="?attr/colorSurface"
android:padding="#dimen/screen_padding">
<com.google.android.material.button.MaterialButton
android:id="#+id/redo_button"
style="#style/AppStyle.Button.OutlinedButton.SecondaryVariantStroke"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="#dimen/spacing_normal"
android:text="aaa"
app:layout_constrainedWidth="false"
app:layout_constraintBottom_toTopOf="#+id/validate_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<ImageView
android:layout_width="32dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:background="#drawable/shape_circle_secondary_variant"
android:padding="7dp"
android:src="#drawable/ic_camera"
android:tint="#android:color/white"
app:layout_constraintBottom_toBottomOf="#+id/redo_button"
app:layout_constraintDimensionRatio="h,1:1"
app:layout_constraintEnd_toEndOf="#+id/redo_button"
app:layout_constraintStart_toEndOf="#+id/redo_button"
app:layout_constraintTop_toTopOf="#+id/redo_button"
tools:ignore="ContentDescription" />
<com.google.android.material.button.MaterialButton
android:id="#+id/validate_button"
style="#style/AppStyle.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/global_valid"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/redo_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
If you know which button is going to be wider, then you can make that button wrap_content and constrain the other button to it as follows:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:background="#android:color/darker_gray"
android:padding="16dp">
<Button
android:id="#+id/redo_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="aaa"
app:layout_constrainedWidth="false"
app:layout_constraintBottom_toTopOf="#+id/validate_button"
app:layout_constraintEnd_toEndOf="#+id/validate_button"
app:layout_constraintStart_toStartOf="#+id/validate_button"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<Button
android:id="#+id/validate_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Validate"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/redo_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
If this solves your problem, stop here.
Things get a little more complicated if you don't know in advance which button will be wider. (Maybe due to language changes.) In that case, you can check the sizes programmatically and explicitly expand the narrower button. You may consider this less than ideal.
As for accomplishing this in XML, I have looked at this type of problem in the past and the problem always reduces to some kind of circular reference issue. (In fact, this is the problem that you are having with match_constraints widgets in a wrap_content container. The widgets are as wide as the container and the container is as wide as the widgets! What?)
If you don't know which button will be wider, you can create an invisible button that has two lines of text that correspond to the labels of your buttons (assuming single line labels.)
Now that there is a dummy button that is the right width, constrain the sides of your buttons to the sides of the dummy button and make their width match_constraints.
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:background="#android:color/darker_gray"
android:padding="16dp">
<Button
android:id="#+id/dummyForSizing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="aaa\nValidate"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/redo_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="aaa"
app:layout_constrainedWidth="false"
app:layout_constraintBottom_toTopOf="#+id/validate_button"
app:layout_constraintEnd_toEndOf="#+id/dummyForSizing"
app:layout_constraintStart_toStartOf="#+id/dummyForSizing"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<Button
android:id="#+id/validate_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Validate"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="#+id/dummyForSizing"
app:layout_constraintStart_toStartOf="#+id/dummyForSizing"
app:layout_constraintTop_toBottomOf="#+id/redo_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
You would, of course, make the dummy button invisible.

How can I push all but one view in a vertical chain to the top, and the last view to the bottom?

I have a set of three buttons in a vertical chain in a ConstraintLayout:
<?xml version="1.0" encoding="utf-8"?>
<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">
<Button
android:id="#+id/button_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 1"
app:layout_constraintBottom_toTopOf="#+id/button_2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed" />
<Button
android:id="#+id/button_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 2"
app:layout_constraintBottom_toTopOf="#+id/button_3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/button_1" />
<Button
android:id="#+id/button_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/button_2" />
</android.support.constraint.ConstraintLayout>
This looks like this:
How can I use features of the ConstraintLayout to make it appear like this, while still preserving the chain?
I have tried using the various bias settings, but they only take effect under the packed chain mode, and then only on the whole chain. Is it possible to individually bias the positions of the views within a chain?
An idea to achieve this is to add a Space helper view as a part of the chain that will take up all the remaining unused space as necessary. Example XML:
<?xml version="1.0" encoding="utf-8"?>
<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">
<Button
android:id="#+id/button_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 1"
app:layout_constraintBottom_toTopOf="#+id/button_2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed" />
<Button
android:id="#+id/button_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 2"
app:layout_constraintBottom_toTopOf="#+id/space"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/button_1" />
<android.support.v4.widget.Space
android:id="#+id/space"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#id/button_3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/button_2" />
<Button
android:id="#+id/button_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/space" />
</android.support.constraint.ConstraintLayout>
Here is your need, You should remove below from button_3
app:layout_constraintTop_toBottomOf="#+id/button_2"
and have to add below
app:layout_constraintStart_toStartOf="parent"
Set the chain with a style of packed with a vertical bias of 0. That will place the three views at the top. To move the bottom view to the bottom of the screen, you will need to set a top margin on the bottom view. Since the margin will vary by device and orientation, you will need to set it programmatically.
Unfortunately, if the middle view can expand vertically as with a multi-line EditText that you mention, then setting the margin will still work, but you will need to make adjustments as the middle view expands. This is all doable but a little involved.

Categories

Resources