constraint layout – Two views with largest width

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.

enter image description here

Leave a Comment