Search code examples
androidnine-patch

android resize chat bubbles width to wrapped text


I'm trying to deal with nine patches and word wrapping inside a text view. The problem is that TextView doesn't resize its width to wrapped text, so the chat bubbles left a huge gap which I cannot reduce.

chat bubbles with gaps

image

Investigation of ViewHierarchy shows that these gaps are inside the TextView, view hierarchy of chat layout. so it's not related to the nine-patch. image

May be somebody faced a similar issue...

<LinearLayout
android:id="@+id/messages"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical">

<TextView
    android:id="@+id/senderName"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:ellipsize="marquee"
    android:gravity="bottom|start"
    android:maxLines="1"
    android:layout_marginTop="5dp"
    android:layout_marginStart="18dp"
    android:layout_marginLeft="18dp"
    android:textColor="@color/talkatone_gray"
    android:textSize="@dimen/chat_item_meta_info_text_size"
    android:visibility="gone"/>

    <TextView
        android:id="@+id/message_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end"
        android:layout_marginBottom="@dimen/chat_bubble_margin_top"
        android:background="@android:color/transparent"
        android:lineSpacingExtra="4sp"
        android:clickable="true"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:gravity="center_vertical"
        android:text="@string/form_chat_failed_to_open"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:textColor="@color/chat_text"
        android:textSize="@dimen/chat_text_message_size"/>

    <FrameLayout
        android:id="@+id/attachment_frame"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/chat_bubble_margin_top"
        android:orientation="vertical">

            <ImageButton
                android:id="@+id/video_play_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical|center_horizontal"
                android:background="@android:color/transparent"
                android:contentDescription="@null"
                android:src="@drawable/play_gif"/>
    </FrameLayout>


Solution

  • in your case the main problem is that you use it in adapter with viewholder pattern implementation.

    So there is two issues you need to solve:

    1. Reset TextView width each time before you'll reuse it.
    2. Define longest line in your TextView measure and set appropriate width.

    For reset TextView width add to your adapter getView() method, when you reuse your holder, next:

    ViewGroup.LayoutParams paramsReset = holder.messageText.getLayoutParams();
    paramsReset.width = ViewGroup.LayoutParams.WRAP_CONTENT;
    holder.messageText.setLayoutParams(paramsReset);
    

    For second issue you need to create custom view, extended from TextView and override onDraw() method:

    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        if(getLineCount() > 1)
        {
            float longestLineWidth = -1;
            for (int lineIndex = 0; lineIndex < getLineCount(); lineIndex++)
            {
                int lineStartIndex = getLayout().getLineStart(lineIndex);
                int lineEndIndex = getLayout().getLineEnd(lineIndex);
                String currentTextLine = getText().toString().substring(lineStartIndex, lineEndIndex);
                // Added "_____" for your paddings.
                float currentLineWidth = getPaint().measureText(currentTextLine + "_____");
    
                if (longestLineWidth < currentLineWidth)
                {
                   longestLineWidth = currentLineWidth;
                }
            }
            ViewGroup.LayoutParams paramsNew = getLayoutParams();
            paramsNew.width = (int) longestLineWidth;
            setLayoutParams(paramsNew);
        }
    }
    

    I override onDraw() also because of needed for use it in ListView, looks like you'll need to find other callback, that calls when holder has appeared in view.


    It works, but not efficient, and have some other issues, please use it as first step.