Skip to content

Instantly share code, notes, and snippets.

@trivalent
Created July 1, 2016 12:29
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trivalent/00526294fa38072a6034f5dc5910824a to your computer and use it in GitHub Desktop.
Save trivalent/00526294fa38072a6034f5dc5910824a to your computer and use it in GitHub Desktop.
<view
android:id="@+id/content_layout"
class="com.example.ChatBubbleLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
>
<TextView
android:id="@+id/txt_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center|right"
android:orientation="horizontal"
android:background="@android:color/holo_red_light"
>
<TextView
android:id="@+id/txt_send_timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="TIME"
/>
</LinearLayout>
</view>
import android.annotation.TargetApi;
import android.content.Context;
import android.text.Layout;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
/**
* Created by trivalent on 7/1/2016.
*/
public class ChatBubbleLayout extends FrameLayout {
public ChatBubbleLayout(Context context) {
super(context);
}
public ChatBubbleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ChatBubbleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(21)
public ChatBubbleLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
TextView message_textView = (TextView) getChildAt(0);
View localView = getChildAt(1);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int view_size = View.MeasureSpec.getSize(widthMeasureSpec);
Layout messageTextviewLayout = message_textView.getLayout();
int linestart = messageTextviewLayout.getLineStart(messageTextviewLayout.getLineCount() - 1);
int lineend = messageTextviewLayout.getLineEnd(messageTextviewLayout.getLineCount() - 1);
int desiredWidth = (int) Layout.getDesiredWidth(message_textView.getText()
.subSequence(linestart, lineend), message_textView.getPaint());
int measuredWidth = message_textView.getMeasuredWidth();
int requiredWidth = Math.min(measuredWidth,
(int) Math.ceil(Layout.getDesiredWidth(message_textView.getText(),
message_textView.getPaint())) + message_textView.getPaddingRight() +
message_textView.getPaddingLeft());
if (view_size - getPaddingLeft() - getPaddingRight()
>= requiredWidth + localView.getMeasuredWidth()) {
setMeasuredDimension(requiredWidth + localView.getMeasuredWidth() +
getPaddingLeft() + getPaddingRight(), getMeasuredHeight());
} else if ((requiredWidth - message_textView.getPaddingLeft() - message_textView.getPaddingRight()
< desiredWidth + localView.getMeasuredWidth())) {
setMeasuredDimension(getMeasuredWidth(),
getMeasuredHeight() + localView.getMeasuredHeight());
}
}
}
@hardikm9850
Copy link

How can we make it work for multiple lines?

@trivalent
Copy link
Author

ChatBubbleLayout.java :

public class ChatBubbleLayout extends FrameLayout {
    //Rect lastLineSpec = new Rect();

    private static final String TAG = " CHAT_BUBBLE_LAYOUT :: ";
    public ChatBubbleLayout(Context context) {
        super(context);
    }

    public ChatBubbleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ChatBubbleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(21)
    public ChatBubbleLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        TextView childTextView = (TextView) getChildAt(0);
        View childDateView = getChildAt(1);

        int view_width = View.MeasureSpec.getSize(widthMeasureSpec);

        int lineCount = childTextView.getLineCount();

        int dateViewHeight = childDateView.getMeasuredHeight();

        int dateViewWidth = childDateView.getMeasuredWidth();

        int textViewPadding = childTextView.getPaddingLeft() + childTextView.getPaddingRight();

        int lastLineStart = childTextView.getLayout().getLineStart(lineCount - 1);
        int lastLineEnd = childTextView.getLayout().getLineEnd(lineCount - 1);

        int lastLineWidth = (int)Layout.getDesiredWidth(childTextView.getText().subSequence(lastLineStart,
                lastLineEnd), childTextView.getPaint());

        int finalFramelayoutWidth = 0;
        int finalFrameLayoutHeight = 0;
        int viewPaddingLeftNRight = getPaddingLeft() + getPaddingRight();
        int finalFrameLayoutRequiredWidth = lastLineWidth + textViewPadding + dateViewWidth  + viewPaddingLeftNRight;

        int lineHeight = (childTextView.getMeasuredHeight() / lineCount) / 2;
        int bottomMargin = lineHeight - (dateViewHeight/2);

        if(( (childTextView.getMeasuredWidth() + viewPaddingLeftNRight)>= view_width)
                || (finalFrameLayoutRequiredWidth >= view_width) ) {
            finalFramelayoutWidth = view_width;
            finalFrameLayoutHeight = getMeasuredHeight();
            if((finalFrameLayoutRequiredWidth >= view_width) ) {
                finalFrameLayoutHeight += dateViewHeight;
                finalFramelayoutWidth = childTextView.getMeasuredWidth() + viewPaddingLeftNRight;

                ((LayoutParams)childDateView.getLayoutParams()).bottomMargin = 0;
            } else {
                ((LayoutParams)childDateView.getLayoutParams()).bottomMargin = bottomMargin;
            }

        } else {
            finalFramelayoutWidth = Math.max(finalFrameLayoutRequiredWidth,
                    childTextView.getMeasuredWidth() + viewPaddingLeftNRight);
            finalFrameLayoutHeight = getMeasuredHeight();
            ((LayoutParams)childDateView.getLayoutParams()).bottomMargin = bottomMargin;
        }

        if(finalFramelayoutWidth > view_width)
            finalFramelayoutWidth = view_width;

        setMeasuredDimension(finalFramelayoutWidth, finalFrameLayoutHeight);
    }
}

Layout:

<com.ui.components.ChatBubbleLayout
            android:id="@+id/chat_bubble_item_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="left"
            android:layout_weight="1"
            android:background="@drawable/chat_bubble_male"
            >

            <TextView
                android:id="@+id/message_text"
                style="@style/INCOMING_CHAT_TEXT_STYLE"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="start|left"
                android:autoLink="all" />

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="end|right|bottom"
                android:gravity="center"
                android:paddingLeft="8dp"
                >

                <TextView
                    android:id="@+id/time_text"
                    style="@style/INCOMING_CHAT_TS_TEXT_STYLE"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:gravity="center_vertical"
                    />

            </LinearLayout>
        </com.ui.components.ChatBubbleLayout>

@trivalent
Copy link
Author

Please try with the above code... I am using the same in my application and works well for multi line too....

@hardikm9850
Copy link

hardikm9850 commented Sep 20, 2016

The updated code works for multiple lines but for single line it adds more space between text and time view.
I added some fill-in code where you are calculating max between

finalFrameLayoutRequiredWidth, childTextView.getMeasuredWidth() + viewPaddingLeftNRight)

Thank you for the solution 👍

@johncpang
Copy link

A simple way to ensure space between message text and time text is to add android:paddingLeft to the time view. Also, to have the timestamp sits on the bottom, int bottomMargin is not needed.

@karolsteve
Copy link

@hardikm9850 Can I have your final implementation please

@arsalankhan994
Copy link

Not working for Arabic language

@deepakbajpay
Copy link

@trivalent can you please share your styles.xml as well. as you have used INCOMING_CHAT_TEXT_STYLE and INCOMING_CHAT_TS_TEXT_STYLE from it.

@trivalent
Copy link
Author

@trivalent can you please share your styles.xml as well. as you have used INCOMING_CHAT_TEXT_STYLE and INCOMING_CHAT_TS_TEXT_STYLE from it.

There is nothing special in the styles. I am just specifying the text colour/font size etc in those.

@trivalent
Copy link
Author

Not working for Arabic language

Would be great if you can share the solution, in case you found it.

@sakshi5724
Copy link

it gives null pointer exception on onMesure()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment