Search code examples
androidandroid-studioandroid-layoutconstraintsandroid-constraintlayout

Android ConstraintLayout - position view to center when there is enough space and move to edges if necessary


This is my case I'm trying to solve:

enter image description here

So, my goal is:

  1. To keep View1 in centre if there is enough space, that means View2 isn't that wide as to overlap with View1.
  2. If View2 is wide enough to make it impossible to keep View1 in centre without overlapping, then in this case move View1 more to the left.

What I have already tried is this:

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/view1"
        android:layout_width="wrap_content"
        android:layout_height="44dp"
        android:layout_marginEnd="8dp"
        android:src="@drawable/ic_img"
        app:layout_constrainedWidth="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/view2"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_min="wrap" />

    <View
        android:id="@+id/view2"
        android:layout_width="0dp"
        android:background="@color/black"
        android:layout_height="50dp"
        app:layout_constrainedWidth="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_min="wrap"
        app:layout_constraintWidth_percent="0.43" />

</androidx.constraintlayout.widget.ConstraintLayout>

With these constraints I tell View2 to take at least 43% of parent's width and if that is not enough then just apply wrap_content. And then in View1 just do:

app:layout_constraintEnd_toStartOf="@+id/view2"

The value 43% is just an estimation that would pretty much position View1 to the centre in the 1st case of the picture. But it's not very accurate in some devices.

So would there be a solution that would position View1 exactly in the centre for the 1st scenario, and then move it to the left if necessary?


Solution

  • This is not something that can be done with just XML, AFAIK. You can accomplish it with a little code, though.

    Change the layout so view2 is constrained to the right of the ConstraintLayout and the right of view1 is constrained to the left of view2.

    <androidx.constraintlayout.widget.ConstraintLayout 
        android:id="@+id/layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <View
            android:id="@+id/view1"
            android:layout_width="100dp"
            android:layout_height="50dp"
            android:layout_marginEnd="8dp"
            android:background="#2196F3"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/view2"
            app:layout_constraintTop_toTopOf="parent" />
    
        <View
            android:id="@+id/view2"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:background="#FF5722"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    Now, place the following code in your main activity. (It can go elsewhere, but the main activity is used here for demo purposes.) This code will manipulate the right margin of view1 to place it either in the center (if it doesn't overlap with view2) or pushed to the left in case of overlap.

        val layout = findViewById<ConstraintLayout>(R.id.layout)
        layout.doOnNextLayout {
            val view1 = findViewById<View>(R.id.view1)
            val view2 = findViewById<View>(R.id.view2)
            val view1Margin =
                if ((layout.width + view1.width) / 2 < view2.x) {
                    // view1 can be centered.
                    (view2.x - (layout.width + view1.width) / 2).toInt()
                } else {
                    // view1 is pushed to left of center. 16dp is the right margin now.
                    (16 * resources.displayMetrics.density).toInt()
                }
            val lp = view1.layoutParams as ViewGroup.MarginLayoutParams
            lp.rightMargin = view1Margin
            view1.layoutParams = lp
        }
    

    Now when view2 is short enough, view1 will be centered:

    enter image description here

    And when view2 is longer, view1 will be pushed to the side.

    enter image description here