How to set minimum percentage width of View in a ConstraintLayout

Viewed 628

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%):

enter image description here

<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.

enter image description here

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!

1 Answers

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>

enter image description here

enter image description here

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.

Related