I want to create a layout (using constraint layout) like the following:
In different languages Button1 may be larger than button2. How can I do this?
I've only been able to achieve this using a LinearLayout inside the constraint that contains the two buttons, but I'm trying to use only a layout.
Thanks
Update
I want to mention that Remq's answer works and gets around the self-reference problem I noted below where a view's width is determined by the location of a barrier that uses the same view as a referenced id. The key seems to be the specification of app:layout_constraintWidth_min="wrap" for both views. It is unclear to me why this works, or that it will continue to work, but I wanted to make note of it
Update #2
I took another look at why Remq's answer works. My experience is that without specifying app:layout_constraintWidth_min="wrap" for the views, both views collapse to zero width. Once the views measure out as zero width, app:layout_constraintWidth_min="wrap" grows them again so the contents are wrapped. This is just what I surmise and have no proof that this is what is going on, but it makes sense.
I have seen questions akin to this one on Stack Overflow a number of times. These questions never have a satisfactory answer IMO (including ones that I have answered.) The difficulty is that there is a dependency problem since one view depends on the width of another but that other view depends on the width of the first. We fall into a referential quandary. (Forcing widths programmatically is always an option but seems undesirable.)
Another and, probably, better approach is to use a custom ConstraintHelper that will inspect the sizes of the referenced views and adjust the width of all views to the width of the largest.
The custom ConstraintHelper is placed in the XML for the layout and references the effected views as in the following sample layout:
activity_main
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="center"
android:text="Button1"
app:layout_constraintBottom_toTopOf="#+id/button2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<Button
android:id="#+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Button2 may be larger"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/button1" />
<com.example.constraintlayoutlayer.GreatestWidthHelper
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="button1,button2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
The custom ConstraintHelper looks like this:
GreatestWidthHelper
class GreatestWidthHelper #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintHelper(context, attrs, defStyleAttr) {
override fun updatePostMeasure(container: ConstraintLayout) {
var maxWidth = 0
// Find the greatest width of the referenced widgets.
for (i in 0 until this.mCount) {
val id = this.mIds[i]
val child = container.getViewById(id)
val widget = container.getViewWidget(child)
if (widget.width > maxWidth) {
maxWidth = widget.width
}
}
// Set the width of all referenced view to the width of the view with the greatest width.
for (i in 0 until this.mCount) {
val id = this.mIds[i]
val child = container.getViewById(id)
val widget = container.getViewWidget(child)
if (widget.width != maxWidth) {
widget.width = maxWidth
// Fix the gravity.
if (child is TextView && child.gravity != Gravity.NO_GRAVITY) {
// Just toggle the gravity to make it right.
child.gravity = child.gravity.let { gravity ->
child.gravity = Gravity.NO_GRAVITY
gravity
}
}
}
}
}
}
The layout displays as shown below.
I stumbled upon this question and was able to solve it with androidx.constraintlayout.widget.Barrier which were introduced in 1.1.0-beta1 of constraint layout. So I thought it'd be nice to share with people who run into the same issue:
<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/button1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="center"
android:text="Button1"
app:layout_constraintBottom_toTopOf="#+id/button2"
app:layout_constraintEnd_toEndOf="#id/barrierEnd"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintWidth_min="wrap" />
<Button
android:id="#+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Button2 may be larger"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="#id/barrierEnd"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/button1"
app:layout_constraintWidth_min="wrap" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrierEnd"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="end"
app:constraint_referenced_ids="button1,button2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Haven't been able to center them horizontally, so if someone figures that out I'll update this example.
As per what I understand from your question, below code will help 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="match_parent">
<TextView
android:id="#+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello world"
app:layout_constraintBottom_toTopOf="#+id/text2"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello world"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/text1" />
</androidx.constraintlayout.widget.ConstraintLayout>
Related
I have two Buttons organized in a row in a ContraintLayout. By default, I need them to be evenly distributed each taking 50% of the ConstraintLayout width (easy enough with a vertical Guideline set to 50%):
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
...
<Button
android:id="#+id/secondaryButton"
style="#style/Button.Secondary"
android:text="Secondary Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/primaryButtonBarrier"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/detail" />
<Button
android:id="#+id/primaryButton"
style="#style/Button.Primary"
android:layout_width="0dp"
android:text="Primary Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/middleGuideline"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintTop_toBottomOf="#id/detail" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/middleGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/primaryButtonBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="start"
app:constraint_referenced_ids="primaryButton, middleGuideline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#id/detail" />
</androidx.constraintlayout.widget.ConstraintLayout>
Desired behavior (currently achieved programmatically with code snippet below):
I also need to support "overflow" text in the primary Button on the right.
So, if I have a larger amount of text, the Button width will push past the 50% guideline. I've tried using app:layout_constraintWidth_min, but it doesn't take a percentage (wrap or dimen).
The best solution I can come up with is setting the Button width to wrap_content and then the min width programmatically after the ConstraintLayout has been measured:
doOnLayout { constraintLayout ->
val layoutParams = primaryButton.layoutParams as ConstraintLayout.LayoutParams
layoutParams.matchConstraintMinWidth = constraintLayout.measuredWidth / 2
primaryButton.layoutParams = layoutParams
}
I feel like there has to be a combo of settings here that I'm missing to achieve this. Thanks in advance!
There is no straightforward way to do what you want that I know of with any standard XML code and the standard ConstraintLayout attributes. You might be able to do something with a nested layout, but, in general, that should be avoided with ConstraintLayout. I think that the way you devised is probably going to be the way to go.
However, if you want to do this all in XML, you could create a shadow button that is a replica of the primary button in all aspects (position, style, text, etc.) except that it is invisible. A barrier can then ride to the left of the shadow button and the center guideline. The visible primary button can now be constrained to the barrier since the barrier is not dependent upon the primary button. The primary button will at least be 1/2 the width of the ConstraintLayout but can expand beyond.
A second approach would be to create a custom view for the primary button. This custom view can implement an attribute that specifies a minimum width for the button based on the width of the ConstraintLayout.
ButtonMinWidthOfParent.kt
class ButtonMinWidthOfParent #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : MaterialButton(context, attrs) {
private var minWidthOfParent = 0f
init {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.ButtonMinWidthOfParent,
0, 0
).apply {
try {
minWidthOfParent = getFloat(R.styleable.ButtonMinWidthOfParent_minWidthOfParent, 0f)
} finally {
recycle()
}
}
}
#Override
override fun getSuggestedMinimumWidth(): Int {
return maxOf(
super.getSuggestedMinimumWidth(),
((parent as ViewGroup).measuredWidth * minWidthOfParent).toInt()
)
}
}
attrs.xml
<resources>
<declare-styleable name="ButtonMinWidthOfParent">
<attr name="minWidthOfParent" format="float"/>
</declare-styleable>
</resources>
activity_main
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="#+id/detail"
android:layout_width="0dp"
android:layout_height="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/secondaryButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Secondary Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/primaryButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/detail" />
<com.example.secondarybutton.ButtonMinWidthOfParent
android:id="#+id/primaryButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Primary Button that is a little bit longer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/detail"
app:minWidthOfParent="0.5" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/middleGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<View
android:id="#+id/view"
android:layout_width="2dp"
android:layout_height="0dp"
android:background="#android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
There are undoubtedly other ways, but the XML way has the advantage of being a pure XML implementation with a little trickery while the custom view is a more standard way to extend view capabilities.
I have a toolbar like component implementation, that I'm having trouble with the layout in all situations. It has a left icon, a title and a right menu/button. I need the title to be centered on the full screen (or at least the full width of the layout) but also to not overlap with the other components. So the width of the title would have to be constrained by the left icon and the right button.
I have two intermediary solutions but can't seem to find a way to combine them.
One is to center the title on the screen. The problem here is that the title overlaps with the right button (and would overlap with the left icon too, if large enough ...). Here is the layout 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="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/bottomSheetHeaderIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/bottomSheetHeaderTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textAlignment="center"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="My also very very very very very long title" />
<TextView
android:id="#+id/right_action"
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"
tools:text="Really long string longer"/>
</androidx.constraintlayout.widget.ConstraintLayout>
The other is to center the title between the left icon and the right button. Now there is no overlap, but the title is not centered correctly. Since the two side elements have very different sizes, the title is only centered between them, which is no good. Here is the same layout with the constraints for this case:
<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="wrap_content">
<ImageView
android:id="#+id/bottomSheetHeaderIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/bottomSheetHeaderTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textAlignment="center"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/right_action"
app:layout_constraintStart_toEndOf="#+id/bottomSheetHeaderIcon"
app:layout_constraintTop_toTopOf="parent"
tools:text="My also very very very very very long title" />
<TextView
android:id="#+id/right_action"
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"
tools:text="Really long string longer"/>
</androidx.constraintlayout.widget.ConstraintLayout>
I'm trying to get away with just a XML layout solution, without having to do programmatically detect overlaps and change the layout.
Solutions with other layouts would also work, if they exist.
The problem is the same as the questions here and here, but I'm hoping that by giving more detail, this one gets an accepted solution.
Here is a programatic answer to this question, that is what I'm using for now, but I'd really like a layout solution ...
I use the first layout, centered on the screen, and, as this is part of a custom view for me, add the following code to the constructor (title is a reference to bottomSheetHeaderTitle and rightAction is right_action, the left icon is assumed to always be smaller than the right action text).
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (title.width > 0) {
if (title.right > rightAction.x) {
title.maxWidth = (2.0 * (width / 2.0 - (width - rightAction.x))).toInt()
}
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
})
This code detects if the title overlaps the right action, and, if it does, adds a max width to it. The max width is the double of the distance between the center of the screen and the beginning of the right action.
I have the following XML code , I couldn't make 'summaryViewPager' fill the remaining space vertically, until the bottom of the screen :
<?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">
<com.google.android.material.card.MaterialCardView
android:id="#+id/mainCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:background="#666266"
android:padding="10dp"
app:cardCornerRadius="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:layout_marginLeft="3dp"
android:layout_marginTop="2dp"
android:text="#string/category"
android:textColor="#FFBA93"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="#+id/cardView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:background="#666266"
android:padding="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/mainCardView">
<Button
android:id="#+id/shareButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="#drawable/ic_share"
app:layout_constraintEnd_toStartOf="#+id/copyButton"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="#+id/seenImageView"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="3dp"
android:layout_marginLeft="3dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:contentDescription="#string/todo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/favoriteButton"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/unseen" />
</com.google.android.material.card.MaterialCardView>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/summaryViewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/cardView2" />
</androidx.constraintlayout.widget.ConstraintLayout>
The above code doesn't show the PageViewer at all below cardView2 , however when I set android:layout_height="500dp it does show PageViewer, but it doesn't fill the whole screen.
Initial Thoughts
I copy pasted your Layout into Android Studio 4.x, replaced all #string references with "something", and all #drawables with an existing one (from #android:drawable/) so my layout would render.
From what I see, it looks correct:
I noticed some inconsistencies in your Layout.
CardView Number 1 (top)
Careful with Padding on a CardView (don't know your min/max API so, not sure if this applies to you).
Your CardView has no constrain for its BOTTOM, it's left to "whatever size you compute after wrapping" (Height = wrap, Bottom_To = null).
This ^ means that for the CardView to have a height, all its innerwidgets must compute their sizes, so the top card view knows how much size + margin + padding it needs. It doesn't depend on OTHER widgets (that are not children) because its constrains are all to the parent. (rather it only needs its parent).
CardView Number 2 (bottom)
This cardView wraps all its dimensions but has the same issue as the one above, it doesn't constraint its height (except to be pinned at the bottom of the previous one, CardView 1). Again, this is fine, so as long as nobody depends on this (which is not true). This one has to wait on more widgets to know its size, because it has to wait on the CardView1 to know how much space is left, and also needs to know how much its children need. It's not too hard because both (the ImageView and the Button) have fixed sizes (20 and 30 dp respectively) + margins/padding.
The children of a MaterialCardView cannot/should not/must not use constraints because a CardView is not a ConstraintLayout, rather a glorified FrameLayout, which can only hold ONE child (or if it has more, it will put them on on top of the other). So all those constraints (for the shareButton and seenImageView) are ignored.
The solution to the above, is to have an inner ConstraintLayout as the sole child of a CardView, and inside this inner CL, put all your children and their constraints. The inner ConstraintLayout should have its width/height as either wrap_content or match_parent so they use the constraints of the parent. Since htey have fixed sizes, this is not an issue. (and if they didn't it wouldn't be an issue, but would need another measure pass).
Regardless of this, this second CardView is able to calculate its height, because its children are reporting a size of 20+30 (overlapped) + 15 margin top (image) so.. all this combined is likely 45dp~ of height (since they overlap only the biggest numbers apply here).
All the constraints are ignored for these two children.
ViewPager (bottom)
Finally we reach the VPager. This viewPager is match_parent for its width (since you have constraints to the start/end, you should just use 0dp). And it has wrap_content for its height.
layout_height=wrap_content -> this is a problem here. Because the viewPager doesn't (at the time of layout pass/measure) yet know what its contents are going to be. So you probably want this to be 0dp and let the ViewPager use all available space after the above has been calculated.
The marginTop you have here, will not work as it is, because the CardView 1 and 2 have no bottom constrain, so this one would have to create yet another layout/measure pass after it's all said and done to be able to apply a margin (that's how it works).
Ok Enough Rant - what can you do?
I'd "fix" the layout by adding the correct constraints and -if needed- use a VerticalChain and biasing for all widgets.
I'd fix the middle (Cardview 2) contents by wrapping the textview and imageView in a ConstraintLayout.
I'd remove left/right and replace with start/end (unless you target API 16 or below).
I'd set the ViewPager to 0dp on both dimensions.
The children of your CardView2 refer to copyButton and favoriteButton but these don't exist in the Layout you pasted, so I assume you have more buttons there).
Full Version (modified)
And in case you wonder, here's what I did ( I added a color to the VP's background so it was easier to "see").
<?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">
<com.google.android.material.card.MaterialCardView
android:id="#+id/mainCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
app:layout_constraintVertical_chainStyle="packed"
android:background="#666266"
android:padding="10dp"
app:cardCornerRadius="20dp"
app:layout_constraintBottom_toTopOf="#id/cardView2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:layout_marginTop="2dp"
android:text="Category"
android:textColor="#FFBA93"
android:textSize="20sp"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="#+id/cardView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:background="#666266"
android:padding="10dp"
app:layout_constraintBottom_toTopOf="#id/summaryViewPager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/mainCardView">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/innerCardView2Root"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="#+id/shareButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="5dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="5dp"
android:background="#android:drawable/ic_menu_share"
app:layout_constraintEnd_toStartOf="#+id/seenImageView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="#+id/seenImageView"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="3dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="5dp"
android:contentDescription="#null"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/shareButton"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#android:drawable/ic_menu_search" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/summaryViewPager"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:background="#color/colorSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/cardView2" />
</androidx.constraintlayout.widget.ConstraintLayout>
I have three TextViews "hi", "x" and "Hello World" which I would like to align in the bottom of the Hello World text (ie hi_x_World). Hello World is just one line but both layout_width and layout_height are set to wrap_content.
They have different font sizes so even though I can easily align the bottom of the boxes of the textviews, the text itself does not become aligned.
I found a different XML parameter app:layout_constraintBaseline_toBaselineOf="#+id/text that works when I only have one line in the TextView. However, when I have 2 or more lines (like in the Hello World TextView) the baseline that's considered is in the 'Hello' instead of 'World'.
Is there any way to change the setting to consider the baseline below the word "World" instead of "Hello" ?
Second update: This is another way of looking at the first solution taken from this Stack Overflow answer that will also work with ConstraintLayout. This solution uses a custom TextView. The custom TextView returns the baseline of the last line of text in the TextView from the getBaseline() function instead of the baseline of the first line which is the default action. This is a nice, clean solution (IMO) that takes into account multi-line TextViews but also gravity, etc.
Kotlin version of BaselineLastLineTextView
class BaselineLastLineTextView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : AppCompatTextView(context, attrs) {
override fun getBaseline(): Int {
val layout = layout ?: return super.getBaseline()
val baselineOffset = super.getBaseline() - layout.getLineBaseline(0)
return baselineOffset + layout.getLineBaseline(layout.lineCount - 1)
}
}
First update: This is an update to my answer below which is still a valid solution (IMO). This is an alternate approach that does not involve any Java/Kotlin code and can be accomplished just using XML.
Create an invisible wrap_content TextView that has the same font size as the "Hello World!" TextView. (You may also need to consider padding and margins depending upon the actual layout.) Constrain this new view to the bottom of "Hello World!", make it invisible and set the contents to something short that is guaranteed to occupy just one line. This will give you a target view that has the same base line as the last line of the "Hello World!" view.
Constrain the base lines of "hi" and "x" to the new invisible view. All views will now share the same base line and without coding.
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/hiddenView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A"
android:textSize="50sp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="#id/helloView"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="#+id/hiView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hi"
android:textSize="46sp"
app:layout_constraintBaseline_toBaselineOf="#id/hiddenView"
app:layout_constraintEnd_toStartOf="#+id/xView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#id/helloView" />
<TextView
android:id="#+id/xView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="x"
android:textSize="36sp"
app:layout_constraintBaseline_toBaselineOf="#id/hiddenView"
app:layout_constraintEnd_toStartOf="#+id/helloView"
app:layout_constraintStart_toEndOf="#+id/hiView"
app:layout_constraintTop_toTopOf="#id/helloView" />
<TextView
android:id="#+id/helloView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello\nWorld!"
android:textSize="50sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/xView"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:onClick="onClick"
android:text="Adjust Base Lines"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/helloView" />
</androidx.constraintlayout.widget.ConstraintLayout>
First answer: As noted in another answer, there is no way to do this just using ConstraintLayout constraints. You will need to resort to a programmatic solution.
Within each TextView is a StaticLayout that can reveal a bit about the typography of the text. By referring to the static layout, padding can be added to the appropriate views to make the base lines align.
In this demo, the three TextViews simply have their tops aligned. Initially, the views look like this:
When the button is clicked, the base line locations are calculated and padding is added to the tops of the "hi" and "x" TextViews.
Details will vary with the implementation, but this is the general technique.
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun onClick(view: View) {
button.isEnabled = false
// Get the StaticLayout from the TextView
val layout = helloView.layout
// Get the base line location for last line of Hello World! TextView, "hi" and "x"
val helloBaseLIne = layout.getLineBaseline(layout.lineCount - 1)
val hiBaseLine = hiView.layout.getLineBaseline(0)
val xBaseLine = xView.layout.getLineBaseline(0)
// Shift "hi" and "x" down so base lines match that of hello world!
hiView.updatePadding(top = helloBaseLIne - hiBaseLine)
xView.updatePadding(top = helloBaseLIne - xBaseLine)
}
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:id="#+id/layout"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/hiView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hi"
android:textSize="46sp"
app:layout_constraintTop_toTopOf="#id/helloView"
app:layout_constraintEnd_toStartOf="#+id/xView"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="#+id/xView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="x"
android:textSize="36sp"
app:layout_constraintTop_toTopOf="#id/helloView"
app:layout_constraintEnd_toStartOf="#+id/helloView"
app:layout_constraintStart_toEndOf="#+id/hiView" />
<TextView
android:id="#+id/helloView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello\nWorld!"
android:textSize="50sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/xView"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Adjust Base Lines"
android:onClick="onClick"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/helloView" />
</androidx.constraintlayout.widget.ConstraintLayout>
AFAIK, there is no way to accomplish this task using ConstraintLayout as of today.
If you know the content of the "helloWorldTextView" in advance, you might wanna split the lines into several textViews and then use app:layout_constraintBaselineToBaselineOf.
I know this is a tricky workaround, but it's the only way that comes to mind.
You can use:
app:layout_constraintBottom_toBottomOf="#+id/helloworldtextid"
Also, don't forget to set the height to wrap_content so that the bottom of the TextViews line up to the bottom of the "Hello World" even when it wraps to two lines or more.
If you want it centered to "Hello World" when it is multi-line, consider adding
app:layout_constraintTop_toTopOf="#+id/helloworldtextid"
along with the bottom one. It will center the text to the multi-lined "Hello World" vertically.
Use Guideline
Utility class representing a Guideline helper object for ConstraintLayout. Helper objects are not displayed on device (they are marked as View.GONE) and are only used for layout purposes. They only work within a ConstraintLayout.
A Guideline can be either horizontal or vertical:
a) Vertical Guidelines have a width of zero and the height of their ConstraintLayout parent
b) Horizontal Guidelines have a height of zero and the width of their ConstraintLayout parent
Widgets can then be constrained to a Guideline, allowing multiple
widgets to be positioned easily from one Guideline, or allowing
reactive layout behavior by using percent positioning.
Example
Code:-
<?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">
<androidx.constraintlayout.widget.Guideline
android:id="#+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent=".4"
/>
<TextView
android:id="#+id/textViewHi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="50dp"
android:text="hi"
android:textSize="30sp"
app:layout_constraintBottom_toTopOf="#+id/guideline" />
<TextView
android:id="#+id/textViewX"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="#+id/textViewHi"
android:layout_marginLeft="50dp"
android:text="x"
android:textSize="30sp"
app:layout_constraintBottom_toTopOf="#+id/guideline" />
<TextView
android:id="#+id/textViewHelloWorld"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="#+id/textViewX"
android:layout_marginLeft="50dp"
android:text="Hello World"
android:textSize="45sp"
app:layout_constraintBottom_toTopOf="#+id/guideline" />
</androidx.constraintlayout.widget.ConstraintLayout>
Is there a possibility (in ConstraintLayout) to let a view grow only as long as there is space for another view at his right?
The use case is to have a value and unit TextViews besides each other. The value TextView should be able to grow as long as there is space for the unit. If there is not enough space, the value should be cut.
I've tried it with chains and some other things but can't get it done. The value doesn't stop growing and then the unit is not visible anymore. Here's the current code:
<TextView
android:id="#+id/value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:lines="1"
app:layout_constraintBaseline_toBaselineOf="#+id/unit"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="#id/unit"
tools:text="12533939532" />
<TextView
android:id="#+id/unit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintLeft_toRightOf="#id/value"
app:layout_constraintRight_toRightOf="parent"
tools:text="km" />
yes you can by using match_constraint (0dp) which equal to match_parent for other layout, so by using match_constraint we set weight for first view which will occupies all available space also add
app:layout_constraintWidth_default="wrap"
to apply default width behavior as wrap_content
here is code with change
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="#+id/value"
android:layout_width="0dp"
app:layout_constraintWidth_default="wrap"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:lines="1"
app:layout_constraintBaseline_toBaselineOf="#+id/unit"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="#id/unit"
tools:text="12533939532" />
<TextView
android:id="#+id/unit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
app:layout_constraintLeft_toRightOf="#id/value"
app:layout_constraintRight_toRightOf="parent"
tools:text="km" />
</android.support.constraint.ConstraintLayout>
got some explanation from site
Better view dimension controls
The new available behaviors when a dimension is set to 0dp (MATCH_CONSTRAINT). As before, both endpoints (left/right or top/bottom) need to be connected to targets.
layout_constraintWidth_default = spread (default, similar to the previous behavior)
layout_constraintWidth_default = wrap
layout_constraintHeight_default = spread
layout_constraintHeight_default = wrap
Wrap provides a significant new behaviour, with the widget resizing as if wrap_content was used, but limited by the connected constraints. A widget will thus not grow beyond the endpoints.
http://tools.android.com/recent/constraintlayoutbeta5isnowavailable