1

I would like to handle 3 cases of displaying text in my custom toolbar (constraint-layout).

  1. If text is short place it in the middle
  2. If text is medium long but it start overlapping icons - move textView to left to fill the empty space
  3. If text is long and do not fit - align to left and ellipsize at then end.

I just wonder if it can be done in XML. I've tried assigning constraints/barriers some but without success. Screen to illustrate these 3 cases. Looking forward for solution or guidance how to solve this problem. Thank you! :)

enter image description here

<?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:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?actionBarSize"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    app:layout_insetEdge="top">

    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/barrier"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:barrierDirection="left"
        app:constraint_referenced_ids="icon1"/>

    <TextView
        android:id="@+id/toolbar_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:ellipsize="end"
        android:maxLines="1"
        android:gravity="center"
        android:text="Title title"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/barrier"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <tv.solocoo.solocoo_components.FontImageView
        android:id="@+id/icon1"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/adaptive_icon"
        android:scaleType="centerInside"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/icon2"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/icon2"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/adaptive_icon"
        android:scaleType="centerInside"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
CallMePedro
  • 51
  • 3
  • 19
  • Doing that programmatically is OK with you?.. I don't think it could be done on layout – Zain Mar 05 '23 at 23:09
  • If it could be done, then it could be programmatically – CallMePedro Mar 06 '23 at 05:54
  • Hello @CallMePedro , Just i checked your XML, working fine with XML as per your code , but do you want to configure with programmatically? – shraddha patel Mar 06 '23 at 10:15
  • Nope, my XML it's not working fine. Take note that I want to have textView centered to the whole View, not center text inside textview. If u copy paste my xml you can see that short text's are not centered but moved to the left (cause of barrier - constraint to the left of icons which I don't want to overlap) – CallMePedro Mar 06 '23 at 10:31

3 Answers3

1

I used your layout as it was removing the ellipsize and gravity so I can set them programmatically

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="@color/black"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_insetEdge="top">

<androidx.constraintlayout.widget.Barrier
    android:id="@+id/barrier"
    android:layout_width="wrap_content"
    android:layout_height="0dp"
    app:barrierDirection="left"
    app:constraint_referenced_ids="icon1" />

<TextView
    android:id="@+id/toolbar_title"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:maxLines="1"
    android:text="Short"
    android:textColor="@color/white"
    android:textSize="20sp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="@+id/barrier"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<ImageView
    android:id="@+id/icon1"
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:scaleType="centerInside"
    android:src="@drawable/baseline_cast_24"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toStartOf="@+id/icon2"
    app:layout_constraintTop_toTopOf="parent" />

<ImageView
    android:id="@+id/icon2"
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:scaleType="centerInside"
    android:src="@drawable/baseline_cancel_24"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

In the activity, I attached the OnGlobalLayoutListener and used this answer to determine if the ellipsis is visible then changed the layout accordingly

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val toolbarText = findViewById<TextView>(R.id.toolbar_title)

    val listener = OnGlobalLayoutListener {
        val l = toolbarText.layout
        l?.let { layout ->
            val lines: Int = layout.lineCount
            if (lines > 0) if (layout.getEllipsisCount(lines - 1) > 0) {
                toolbarText.ellipsize = TextUtils.TruncateAt.END
                toolbarText.textAlignment = TEXT_ALIGNMENT_VIEW_START
                //if you want a padding at the start position
                toolbarText.setPadding(50, 0, 0,0 )
            } else {
                toolbarText.gravity = Gravity.CENTER
                toolbarText.ellipsize = TextUtils.TruncateAt.MIDDLE
                toolbarText.textAlignment = TEXT_ALIGNMENT_CENTER
            }
        }
    }
    val vto: ViewTreeObserver = toolbarText.viewTreeObserver

    vto.addOnGlobalLayoutListener (listener)
}

My results are

Short

Medium

Long

Cj_Rodriguez
  • 459
  • 2
  • 4
0

I just wonder if it can be done in XML.

I don't think this is achievable in XML solely; that is because you'll over-constraint the width of the title where you need to have two end constraints, one to the right side of the parent (when the text is short), and the other to the left edge of the left icon (when the text width grows). Which one would be considered?

Also, what it's needed includes a conditional constraint: you need to constraint the title to the parent end only if it's short; and constraint it to the icon otherwise. Which AFAIK not provided by ConstraintLayout.

And unfortunately, the max. width constraints in documentations are limited to a fixed size in dp with app:layout_constraintWidth_max, or a percentage from the parent using layout_constraintWidth_percent. You can't constraint the max width to a certain view or even to the parent AFAIK.

So, you'd consider one of both constraints on the layout, and overrides it with the other if it's needed programmatically.

Assuming it's constrained to the parent by default; then compare the width of the title to the remaining area until icon1 side; if it's greater, then add the other constraint programmatically:

layout:

<?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:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?actionBarSize"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    app:layout_insetEdge="top">

    <TextView
        android:id="@+id/toolbar_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:ellipsize="end"
        android:maxLines="1"
        android:gravity="center"
        android:text="Title title"
        app:layout_constrainedWidth="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <tv.solocoo.solocoo_components.FontImageView
        android:id="@+id/icon1"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/adaptive_icon"
        android:scaleType="centerInside"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/icon2"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/icon2"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/adaptive_icon"
        android:scaleType="centerInside"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Behavior:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(...);

    ConstraintLayout root = findViewById(R.id.toolbar);
    TextView title = findViewById(R.id.toolbar_title);
    ImageView icon1 = findViewById(R.id.icon1);
    ImageView icon2 = findViewById(R.id.icon2);
    
    root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
        
            root.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            
            int maxWidthAllowed = root.getWidth() - (icon1.getWidth() + icon2.getWidth());
            
            if (title.getWidth() > maxWidthAllowed) {
            
                // change the end constraint to the start of icon1
                ConstraintSet set = new ConstraintSet();
                set.clone(root);
                set.connect(title.getId(), ConstraintSet.END, icon1.getId(), ConstraintSet.START, 0);
                set.applyTo(root);
            }

        }
    });

}
Zain
  • 37,492
  • 7
  • 60
  • 84
0

I was having aa similar issue with some programmatic text alignment. What worked for me was to set the textview(or and combined child view)'s width to match_parent, then set the view's internal gravity to the right value in my list adapter's onBindViewHolder method. For example, for a textview named example in a framelayout:

val textview = viewholder.textview
textview.text = "YOUR STRING"
//init your other views

val params = FrameLayout.LayoutParams(
    ViewGroup.LayoutParams.MATCH_PARENT,
    ViewGroup.LayoutParams.WRAP_CONTENT
)

params.gravity = if (/*check for text length*/) {
    //text should be center aligned
    //add ellipses?
    Gravity.CENTER_HORIZONTAL
} else { 
    //text should be left aligned
    Gravity.START 
}

textview.layoutParams = params

The Java code looks like this:

TextView textview = holder.textview;
textview.setText("YOUR STRING")
//init your other views

FrameLayout.LayoutParams params = new 
    FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
    ViewGroup.LayoutParams.WRAP_CONTENT
);
if(/*DETERMINE STRING LENGTH*/) {
    //center align text
    params.gravity = Gravity.CENTER_HORIZONTAL;
} else {
    //left align text
    //add ellipses?
    params.gravity = Gravity.START
}

textview.setLayoutParams(params);