Center TextView in parent ConstraintLayout but resize based on left/right anchored views

Viewed 640

I need to have a single-line TextView centered inside it's parent ConstraintLayout, but not overlap other views inside that same layout so that it ads ... ellipsis at the end

ConstraintLayout (height: 48dp, width: match_parent) which contains the following 3 child views:

  1. Left anchored container (height: 48dp, width:variable - green in the image below)
  2. TextView (centered in ConstraintLayout)
  3. Right anchored container (height: 48dp, width:variable - blue in the image below)

The left/right containers don't have equal widths (one can be 0 while the other 48dp, or one 48dp and the other 96dp)

enter image description here

XML layout looks like this:

<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="48dp"
    android:background="#FF0000">

    <LinearLayout
        android:id="@+id/container_start"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:background="#00FF00"
        android:orientation="horizontal"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />

    <TextView
        android:id="@+id/text_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toEndOf="@id/container_start"
        app:layout_constraintEnd_toStartOf="@id/container_end"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:maxLines="1"
        android:ellipsize="end"
        android:text="Title Title Title Title" />

    <LinearLayout
        android:id="@+id/container_end"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:background="#0000FF"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
  • If I anchor the TextView to the parent (left/right), it will be centered, but text will overlap side containers
  • If I anchor the TextView to the side containers, text won't overlap but then it might not be centered if containers have different widths.
2 Answers

Try my solution:

<?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="48dp"
    android:background="#FF0000">

    <LinearLayout
        android:id="@+id/container_start"
        android:layout_width="48dp"
        android:layout_height="0dp"
        android:background="#00FF00"
        android:orientation="horizontal"
        app:layout_constraintHorizontal_chainStyle="spread_inside"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/text_title" />

    <TextView
        android:id="@+id/text_title"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        app:layout_constraintStart_toEndOf="@id/container_start"
        app:layout_constraintEnd_toStartOf="@id/container_end"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:maxLines="1"
        android:ellipsize="end"
        android:text="Title Title Title Title" />

    <LinearLayout
        android:id="@+id/container_end"
        android:layout_width="96dp"
        android:layout_height="0dp"
        android:background="#0000FF"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@id/text_title"/>

</androidx.constraintlayout.widget.ConstraintLayout>

I change width and height to 0dp, because if we constraint sides, we can use it to stretch view.

Then I add android:gravity="center" to your TextView. And I also add app:layout_constraintHorizontal_chainStyle="spread_inside"

If you have any questions, let's discuss in the comments

Update 1

If you want to implement toolbar in your app, I suggest you to use androidx.appcompat.widget.Toolbar:

<?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="?attr/actionBarSize"
    android:background="#FF0000">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar_top"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:minHeight="?attr/actionBarSize"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:navigationIcon="@drawable/ic_arrow_back"
        app:menu="@menu/toolbar_actions">

        <TextView
            android:id="@+id/toolbar_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:maxLines="1"
            android:text="Toolbar Title TitleTitleTitleTitleTitle"
            android:textColor="@android:color/black"
            android:textSize="18sp"
            android:ellipsize="end"
            android:textStyle="bold"
            android:layout_gravity="center" />

    </androidx.appcompat.widget.Toolbar>

</androidx.constraintlayout.widget.ConstraintLayout>

And menu XML markup

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="action_one"
        android:icon="@drawable/ic_android"
        android:title="action 1"
        app:showAsAction="ifRoom"/>

    <item android:id="action_two"
        android:icon="@drawable/ic_android"
        android:title="action 2"
        app:showAsAction="ifRoom"/>

</menu>

Result:

enter image description here

Update 2

Try this one, maybe this is what you want.

<?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="48dp"
    android:background="#FF0000">

    <TextView
        android:id="@+id/text_title"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:gravity="center"
        android:maxLines="1"
        android:ellipsize="end"
        android:text="Title Title Title Titlsase"
        android:textColor="@android:color/black"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="@id/center_line"
        app:layout_constraintEnd_toEndOf="@id/center_line"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/center_line"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintGuide_percent="0.5"
        android:orientation="vertical"/>

    <LinearLayout
        android:id="@+id/container_start"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#00FF00"
        android:orientation="horizontal"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/text_title" >
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_arrow_back"
            android:layout_marginStart="10dp"
            android:layout_gravity="start|center_vertical" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/container_end"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#0000FF"
        android:orientation="horizontal"
        android:gravity="end"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/text_title">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_android"
            android:layout_marginEnd="10dp"
            android:layout_gravity="end|center_vertical" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_android"
            android:layout_marginEnd="10dp"
            android:layout_gravity="end|center_vertical" /> 

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_android"
            android:layout_marginEnd="10dp"
            android:layout_gravity="end|center_vertical" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Result:

enter image description here

it might be already late, but I had the same problem recently. I solved it by adding guidelines on the left and right layouts and then checking on redraw which one is bigger and setting the begin/end margin to left/right guideline.

The middle layout was constraint to those which meant, it would nicely stay in the middle. If the right layout went more than half, they wouldn't leave any space for the middle layout, so it would remain invisible.

Here is layout:

    <!-- Left layout -->

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/left_guide"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="0dp" />

    <LinearLayout
        android:id="@+id/title_layout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/right_guide"
        app:layout_constraintStart_toEndOf="@id/left_guide"
        app:layout_constraintTop_toTopOf="parent">

        <!-- ... -->
    </LinearLayout>


    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/right_guide"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_end="0dp" />
    
    <!-- Right layout -->

I wasn't able to constraint the guidelines to the view, because when I set the constraintGuide_end it would be calculated from that view, so I decided to do the rest programmatically.

and code:

  fun ToolbarBinding.initKeepTitleInCenter() {
        var leftGuideX = 0
        var rightGuideX = 0

        fun setGuides() {
            val width = root.width
            val max = maxOf(leftGuideX, width - rightGuideX)
            rightGuide.setGuidelineEnd(max)
            leftGuide.setGuidelineBegin(max)
        }

        rightLayout.viewTreeObserver.addOnPreDrawListener {
            if (rightGuideX != rightLayout.left) {
                rightGuideX = rightLayout.left
                setGuides()
            }
            true
        }

        leftLayout.viewTreeObserver.addOnPreDrawListener {
            if (leftGuideX != leftLayout.right) {
                leftGuideX = leftLayout.right
                setGuides()
            }
            true
        }
    }
Related