I'm building a timeline that looks like this
The center circle can be moved using horizontal bias, and can be on top of the first or last circle, or anywhere in between.
It's build using a ConstraintLayout, and the the date belonging to the center node is centered below it using start and end of the circle as constraints.
If the center node is moved to the far right, it looks like this
My issue is the fact that the TextView have been moved outside the parent layout. Is it possible to constraint it to the center node as I have, but force it to be 1 line and always within the parent layout?
EDIT
Here's the XML. It seemed like a pretty generic question, so I didn't expect the XML would be needed.
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/timeline_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/goal_chart_inner_layout"
app:layout_constraintTop_toTopOf="parent">
<!-- START -->
<TextView
android:textSize="12sp"
android:id="#+id/fast_started_datetime_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="#+id/timeline_start_node"
app:layout_constraintStart_toStartOf="#+id/timeline_start_node"
toots:text="STARTED" />
<ImageView
android:layout_marginStart="15dp"
android:id="#+id/timeline_start_node"
android:layout_width="15dp"
android:layout_height="15dp"
android:background="#drawable/circle_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 1 EDGE -->
<ImageView
android:id="#+id/imageView3"
android:layout_width="0dp"
android:layout_height="5dp"
android:layout_marginStart="#dimen/timeline_edge_padding"
android:layout_marginEnd="#dimen/timeline_edge_padding"
android:background="#drawable/circle_background"
app:layout_constraintBottom_toBottomOf="#+id/timeline_start_node"
app:layout_constraintEnd_toStartOf="#id/timeline_split_node"
app:layout_constraintStart_toEndOf="#id/timeline_start_node"
app:layout_constraintTop_toTopOf="#+id/timeline_end_node" />
<!-- SPLIT -->
<ImageView
android:id="#+id/timeline_split_node"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:background="#drawable/circle_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.84"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/fast_goal_datetime_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="#+id/timeline_split_node"
app:layout_constraintStart_toStartOf="#+id/timeline_split_node"
app:layout_constraintTop_toBottomOf="#+id/timeline_split_node"
toots:text="GOAL - TEST - LONG" />
<!-- 2 EDGE -->
<ImageView
android:id="#+id/imageView4"
android:layout_width="0dp"
android:layout_height="5dp"
android:layout_marginStart="#dimen/timeline_edge_padding"
android:layout_marginEnd="#dimen/timeline_edge_padding"
android:background="#drawable/circle_background"
app:layout_constraintBottom_toBottomOf="#+id/timeline_start_node"
app:layout_constraintEnd_toStartOf="#id/timeline_end_node"
app:layout_constraintStart_toEndOf="#id/timeline_split_node"
app:layout_constraintTop_toTopOf="#+id/timeline_end_node" />
<!-- END -->
<TextView
android:textSize="12sp"
android:id="#+id/fast_ended_datetime_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="#+id/timeline_end_node"
app:layout_constraintEnd_toEndOf="#+id/timeline_end_node"
toots:text="ENDED" />
<ImageView
android:layout_marginEnd="15dp"
android:id="#+id/timeline_end_node"
android:layout_width="15dp"
android:layout_height="15dp"
android:background="#drawable/circle_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
When you move the circle change the constraints of the textview programmatically to the end of the parent layout instead:
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.connect(R.id.your_textview,ConstraintSet.END,R.id.the_parent_layout,ConstraintSet.END,0);
constraintSet.applyTo(yourConstraintLayout);
check the documentation for more details.
#lukas hansen
If you want to align the TextView to center, you could do something like this:
<TextView
android:textSize="12sp"
android:id="#+id/fast_ended_datetime_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:text="ENDED" />
Note: This will constraint the TextView to parent on both edges.
Also, assign a top_to_bottom_of one of the view above the TextView in the hierarchy.
Related
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.
I have layout with ConstraintLayout as parent. Inside my constraint layout there are three views: ImageView and two TextView. I need, depending on whether the visibility of the ImageView and the TextView (1) is visible or not, the margin top of the second TextView changes. This is a visual example of what I want to implement:
I tried using goneMarginTop = 32 for my second TextView. However, in this case, as soon as the visibility of the first TextView is gone, I get margin between ImageView and TextView = 32 dp but expected 12 dp. I need margin = 32 dp only when ImageView and first TextView are gone. Here is my code:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/ivImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<TextView
android:id="#+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="14dp"
android:gravity="center"
android:textColor="#color/black"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/ivImage"
app:layout_goneMarginTop="32dp" />
<TextView
app:layout_goneMarginTop="32dp"
android:id="#+id/tvDesc"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="14dp"
android:textColor="#color/black"
android:textSize="17sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/tvTitle" />
</androidx.constraintlayout.widget.ConstraintLayout>
Is it possible to implement the behavior as in the image using ConstraintLayout from layout (not programmatically).
Please, help me.
I don't think there is a way to do what you ask just with XML and gone margins. You can do it programmatically like it is suggested, but you can also get the spacing you want by using a Barrier and a Space widget. See the comments in the sample XML below. (I added background colors and a visible horizontal marker at 32dp to show the extent of the widgets)
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!--
Space is set to the top of the layout with a height of 20dp.
-->
<Space
android:id="#+id/space"
android:layout_width="wrap_content"
android:layout_height="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!--
A barrier is set to the bottom of the Space above and the bottom TextView (tvDesc). The
vertical placement of this barrier will vary from the bottom of tvTitle when
tvTitle is visible to 20dp (the bottom of the Space widget) when tvTitle is gone.
-->
<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="space,tvTitle" />
<ImageView
android:id="#+id/ivImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/holo_blue_light"
android:visibility="gone"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<TextView
android:id="#+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="14dp"
android:background="#android:color/holo_red_light"
android:gravity="center"
android:textColor="#color/black"
android:textSize="18sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/ivImage"
app:layout_goneMarginTop="20dp"
tools:visibility="visible" />
<!--
The top constraint of tvDesc is set to the barrier and the top margin is set to 12dp. If
tvTitle and the ImageView are gone, the top margin will still be 12dp. Since the
minimum vertical placement of the barrier is 20dp (the height of the Space widget) the
effective top margin for tvDesc will be 32dp (20dp + 12dp) as desired.
-->
<TextView
android:id="#+id/tvDesc"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="14dp"
android:background="#android:color/holo_green_light"
android:textColor="#color/black"
android:textSize="17sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/barrier"
tools:visibility="visible" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginTop="32dp"
android:background="#android:color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
All views visible:
tvTitle gone
ImageView and tvTitle gone
you can do it programmatically :
val layoutParams = imageView.layoutParams as RelativeLayout.LayoutParams
layoutParams.setMargins(100, 100, 100, 100)
I am trying to make my TextView has a dynamic width and an ImageView pinned to the end of it, inside of a ConstraintLayout. When the text is very long, I want to use an ellipsis and have the ImageView pin to the view to the right of it.
Here is an example of what I am trying to achieve visually:
With my code, I am able to achieve the first image with thisIsShortText but when I have long text, it looks bad, like this:
The TextView and ImageView just go off to the right to infinity. Can anyone help me fix this? This is the XML code I have. If you just put it into your Android Studio it will look like my images. Thank you:
<?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="70dp">
<!-- This view does not move or change size. -->
<View
android:id="#+id/firstView"
android:layout_width="35dp"
android:layout_height="35dp"
android:background="#android:color/holo_red_dark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</View>
<!-- This view's width is dynamic and changes size based on it's text length.
If the length of text is too long to fit, it uses an ellipsize. -->
<TextView
android:id="#+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:text="thisIsVeryLongTextThatIsTooLongAndIsLong"
android:textColor="#color/primary_text_dark"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#id/firstView"
app:layout_constraintTop_toTopOf="parent" />
<!-- This view is pinned to the end of textView. -->
<ImageView
android:id="#+id/imageview"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:background="#android:color/holo_purple"
android:src="#android:drawable/ic_dialog_info"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#id/textview"
app:layout_constraintTop_toTopOf="parent" />
<!-- This view does not move or change size. -->
<View
android:id="#+id/secondView"
android:layout_width="80dp"
android:layout_height="35dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="#android:color/holo_blue_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/thirdView"
app:layout_constraintTop_toTopOf="parent" />
<!-- This view does not move or change size. -->
<View
android:id="#+id/thirdView"
android:layout_width="35dp"
android:layout_height="35dp"
android:background="#android:color/holo_orange_dark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
You can achieve that by adding the below to the TextView:
Enabling width constraints.
Using a packed chain with a 0 horizontal bias.
Constraint the end to the start of the right view (imageview)
And constraint the end of the info imageview to the start of the secondView
So, you need to add the following to the textview:
app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintEnd_toStartOf="#id/secondView"
And to the imageview:
app:layout_constraintEnd_toStartOf="#+id/secondView"
Now the entire 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="70dp">
<!-- This view does not move or change size. -->
<View
android:id="#+id/firstView"
android:layout_width="35dp"
android:layout_height="35dp"
android:background="#android:color/holo_red_dark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</View>
<!-- This view's width is dynamic and changes size based on it's text length.
If the length of text is too long to fit, it uses an ellipsize. -->
<TextView
android:id="#+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:text="thisIsVeryLongTextThatIsTooLongAndIsLong"
android:textColor="#color/primary_text_dark"
android:textSize="16sp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/imageview"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="#id/firstView"
app:layout_constraintTop_toTopOf="parent" />
<!-- This view is pinned to the end of textView. -->
<ImageView
android:id="#+id/imageview"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="#android:color/holo_purple"
android:src="#android:drawable/ic_dialog_info"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/secondView"
app:layout_constraintStart_toEndOf="#id/textview"
app:layout_constraintTop_toTopOf="parent" />
<!-- This view does not move or change size. -->
<View
android:id="#+id/secondView"
android:layout_width="80dp"
android:layout_height="35dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="#android:color/holo_blue_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/thirdView"
app:layout_constraintTop_toTopOf="parent" />
<!-- This view does not move or change size. -->
<View
android:id="#+id/thirdView"
android:layout_width="35dp"
android:layout_height="35dp"
android:background="#android:color/holo_orange_dark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Preview:
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
I'm using ConstraintLayout where I will show as below
I would like to hide First (using gone), and which the view I expect to be as below (where ElasticBody will stretch over to use up the original First view space as well.
However, when I actual set First to gone, my view turn out to be as below (all image as from Android Studio Design view). My Elastic Body is missing as well, and the height expanded weirdly.
My layout code as below
<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="wrap_content"
android:padding="16dp">
<TextView
android:id="#+id/txt_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#0ff"
android:text="First"
android:visibility="gone"
android:textSize="26sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/txt_body"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/txt_body"
android:layout_width="0dp"
android:background="#f0f"
android:layout_height="wrap_content"
android:text="Elastic Body"
android:textSize="26sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/txt_tail"
app:layout_constraintStart_toEndOf="#+id/txt_first"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/txt_tail"
android:background="#ff0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tail"
android:textSize="26sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/txt_body"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
(Note, if you remove the gone, you'll get the first image view).
Why is this so? How could I fix it where when my First is gone, I could the Elastic Body stretch out correctly?
p/s: I know how to do it in LinearLayout and RelativeLayout... but wonder if this is a limitation on ConstraintLayout?
Try following.
Set the first view's left and top constraints to "parent". After that:
set the txt_body textview width to "0dp"
set the left constraint to the first view's right side
set the right constraint to the tail view's left side.
So, whenever you set the first view's visibility to "gone", the body view will be stretched like how you want it.
<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="wrap_content"
android:padding="16dp">
<TextView
android:id="#+id/txt_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#0ff"
android:text="First"
android:textSize="26sp"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="#+id/txt_body"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
<TextView
android:id="#+id/txt_body"
android:layout_width="0dp"
android:background="#f0f"
android:layout_height="wrap_content"
android:text="Elastic Body"
android:textSize="26sp"
app:layout_constraintRight_toLeftOf="#+id/txt_tail"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="#+id/txt_first"
/>
<TextView
android:id="#+id/txt_tail"
android:background="#ff0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tail"
android:textSize="26sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</android.support.constraint.ConstraintLayout>
Update
If you want to do using barrier then also you can do it.
<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="wrap_content"
android:padding="16dp">
<TextView
android:id="#+id/txt_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#0ff"
android:text="First"
android:textSize="26sp"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="#+id/barrier"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/txt_body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#f0f"
android:text="Elastic Body"
android:textSize="26sp"
app:layout_constraintStart_toEndOf="#+id/barrier"
app:layout_constraintEnd_toStartOf="#+id/txt_tail"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/txt_tail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff0"
android:text="Tail"
android:textSize="26sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="start"
app:constraint_referenced_ids="txt_body,txt_first" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrier1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="txt_body,txt_tail" />
</androidx.constraintlayout.widget.ConstraintLayout>
Say, you want to have a picture like this:
Here you have indents between the title and "Nice work", between "Nice work" and time, also horizontal indent to "Opinions". They are centered vertically.
"Opinions" is attached to the star, so that can be multilined and stay centered. I show results for 2 variants: in the first row opinions are multilined, while in the next row it is a single line. In columns you can see 4 variants of showing/hiding 2 labels.
A more simple and preferrable way is to wrap both labels into LinearLayout and insert it into parent ConstraintLayout. Then you can set vertical gravity, show or hide labels, hide the LinearLayout itself.
If you don't want to have nested layouts, use Barriers and Groups. It is a difficult task that can waste many hours. A key is having additional Views for aligning. Here I have 2 hiding labels ("Nice work" and "Opinions"), and I have to add 2 views (spaces).
The height of the right space is equal to the height of the star (14dp).
To simplify hiding several views, I joined them into groups.
You can see horizontal dotted lines - they are Barriers. I align them on tops and bottoms of the most big views (barrier_2 is similar):
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrier_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top"
app:constraint_referenced_ids="left_text,opinion" />
Vertical aligning is based on these 2 additional Spaces (see marginTop="10dp"):
<Space
android:id="#+id/left_text_space"
android:layout_width="25dp"
android:layout_height="10dp"
android:layout_marginTop="10dp"
app:layout_constraintStart_toEndOf="#id/left_text"
app:layout_constraintTop_toBottomOf="#id/title" />
It is difficult to cover all situations, so see the following 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"
android:paddingLeft="15dp"
android:paddingTop="5dp"
android:paddingRight="15dp"
android:paddingBottom="5dp">
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lineSpacingExtra="4sp"
android:lines="1"
android:paddingBottom="5dp"
android:text="«Title text»"
android:textColor="#333333"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrier_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top"
app:constraint_referenced_ids="left_text,opinion" />
<TextView
android:id="#+id/left_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#112233"
android:paddingLeft="5dp"
android:paddingTop="4dp"
android:paddingRight="5dp"
android:paddingBottom="4dp"
android:text="Nice work"
android:textColor="#ffffff"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="#id/barrier_2"
app:layout_constraintStart_toStartOf="#id/title"
app:layout_constraintTop_toTopOf="#id/left_text_space" />
<Space
android:id="#+id/left_text_space"
android:layout_width="25dp"
android:layout_height="10dp"
android:layout_marginTop="10dp"
app:layout_constraintStart_toEndOf="#id/left_text"
app:layout_constraintTop_toBottomOf="#id/title" />
<androidx.constraintlayout.widget.Group
android:id="#+id/left_text_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:constraint_referenced_ids="left_text,left_text_space" />
<Space
android:id="#+id/opinion_space"
android:layout_width="1dp"
android:layout_height="14dp"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="#id/left_text_space"
app:layout_constraintTop_toBottomOf="#id/title" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrier_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="left_text,opinion" />
<ImageView
android:id="#+id/opinion_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:contentDescription="#null"
app:layout_constraintBottom_toBottomOf="#id/barrier_2"
app:layout_constraintStart_toEndOf="#id/left_text_space"
app:layout_constraintTop_toTopOf="#id/opinion_space"
app:srcCompat="#drawable/ic_filled_rate_star" />
<TextView
android:id="#+id/opinion"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:lineSpacingExtra="1sp"
android:text="1. Opinion 1.\n2. Opinion 2.\n3. Opinion 3.\n4. Opinion 4."
android:textColor="#1122aa"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="#id/opinion_icon"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="#id/opinion_icon"
app:layout_constraintTop_toTopOf="#id/opinion_icon" />
<androidx.constraintlayout.widget.Group
android:id="#+id/opinion_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:constraint_referenced_ids="opinion_icon,opinion,opinion_space" />
<ImageView
android:id="#+id/time_icon"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_gravity="center_vertical"
android:layout_marginTop="8dp"
android:contentDescription="#null"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/barrier_2"
app:srcCompat="#drawable/ic_time" />
<TextView
android:id="#+id/time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="7dp"
android:layout_marginLeft="7dp"
android:ellipsize="end"
android:lineSpacingExtra="1sp"
android:lines="2"
android:paddingBottom="7dp"
android:text="17:00"
android:textColor="#9e9e9e"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="#id/time_icon"
app:layout_constraintTop_toTopOf="#id/time_icon" />
</androidx.constraintlayout.widget.ConstraintLayout>
Then in your activity you can show/hide labels. Hide Groups, not views inside, because strangely inside a Group views are always visible.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
left_text_group.visibility = View.GONE
opinion_group.visibility = View.VISIBLE
}
An answer has already been given linking to Barriers. I will provide an example of how I've actually implemented it:
<TextView
android:id="#+id/textView1"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:text="Some text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/textView2"
app:layout_constraintTop_toBottomOf="#id/textView1"
app:layout_constraintLeft_toLeftOf="parent"
android:text="Some other text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrier1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:barrierDirection="bottom"
app:constraint_referenced_ids="textView1,textView2" />
This example shows 2 TextViews either of which can be gone. The Views are on stacked from top to bottom, so barrierDirection is set to bottom. Should you need another direction, just change that line accordingly.
Setting any of the 2 TextViews to gone, will result in the Barrier shifting to the bottom of the other, and if we set both to gone, it'll just shift up to the element that textView1's top constraint was referencing, in this case, the parent.
Note: If your textView1's top constraint is something else, i.e. it's below another element, the barrier will end up there if both views are set to gone.