Whatsapp Message Layout - How to get time-view in the same row

后端 未结 12 966
执笔经年
执笔经年 2020-11-28 03:18

I was wondering how WhatsApp handles the time shown in every message.

For those who don\'t know:

  1. If the message is very short, the text and time are in
相关标签:
12条回答
  • 2020-11-28 03:49
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/rel_layout_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/bubble1"
        android:orientation="vertical">
    
        <TextView
            android:id="@+id/lblMsgFrom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Person Name or Id"           
            android:visibility="gone" />   
    
        <TextView
            android:id="@+id/lblMessage_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingBottom="5dp"
            android:paddingLeft="10dp"
            android:paddingRight="10dp"
            android:paddingTop="5dp"
            android:text="Sample \n Sample2 Sample2 Sample2 Sample2 Sample2 Sample2 Sample2 Sample2 Sample2 \n Sample2"
            android:textSize="16dp" />
    
        <TextView
            android:id="@+id/lblMessage_Time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignEnd="@+id/lblMessage_text"
            android:layout_alignRight="@+id/lblMessage_text"
            android:layout_below="@+id/lblMessage_text"
            android:text="04:50 Am"
            android:textColor="@android:color/darker_gray"
            android:textSize="10dp"
            android:textStyle="italic" />    
    
    </RelativeLayout>
    
    0 讨论(0)
  • 2020-11-28 03:49

    I suggest other solution. If you know max bubble width and time width, then you can precalculate how to place your views.

    Layout:

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    
        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            tools:text="This is text"/>
    
        <TextView
            android:id="@+id/time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="10sp"
            tools:text="10:10"/>
    </RelativeLayout>
    

    Code:

    fun setTextAndTime(textView: TextView, timeView: TextView, text: String, time: String) {
        // screen width - offset from bubble
        val maxWidth: Int = Resources.getSystem().displayMetrics.widthPixels - context.resources.getDimensionPixelSize(R.dimen.bubble_offset)
        val timeWidth: Int = getTextWidth(time, 10f)
    
        textView.text = text
        timeView.text = time
    
        textView.measure(makeMeasureSpec(maxWidth, EXACTLY), makeMeasureSpec(0, UNSPECIFIED))
        val offset = textView.layout.getLineWidth(textView.layout.lineCount - 1)
        val freeSpace = maxWidth - offset
    
        val moveTimestampBelow = freeSpace < timeWidth
        val multilineContent = textView.layout.lineCount > 1
    
        val params = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT)
        when {
            moveTimestampBelow -> params.apply {
                addRule(RelativeLayout.BELOW, textView.id)
                addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
            }
            multilineContent -> params.apply {
                params.addRule(RelativeLayout.ALIGN_BOTTOM, textView.id)
                addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
            }
            else -> params.apply {
                params.addRule(RelativeLayout.ALIGN_BOTTOM, textView.id)
                addRule(RelativeLayout.END_OF, textView.id)
            }
        }
        timeView.layoutParams = params
    }
    
    private fun getTextWidth(text: String, textSizeSp: Float): Int {
        val textPaint = Paint()
        val pxSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSizeSp, context.resources.displayMetrics)
        textPaint.textSize = pxSize
        textPaint.style = Paint.Style.FILL
        val result = Rect()
        textPaint.getTextBounds(text, 0, text.length, result)
        return result.width()
    }
    
    0 讨论(0)
  • 2020-11-28 03:57

    I propose another solution

    public static final String TAG = "MainActivity";
        private TextView mText;
        private RelativeLayout relativeLayout;
        private Boolean mFirstTime = true;
        private static final int WIDH_HOUR = 382;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
    
            final int width = getScreensWidh();
    
            mText = (TextView) findViewById(R.id.activity_main_text);
            relativeLayout = (RelativeLayout) findViewById(R.id.activity_main_relative);
    
            mText.setText("aaaaa dfsafsa afdsfa fdsafas adfas fdasf adfsa dsa aaaa dfsafsa afdsfa fdsafas adfas fdasf adfsa");
    
            ViewTreeObserver vto = mText.getViewTreeObserver();
            vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    if (mFirstTime) {
                        Layout layout = mText.getLayout();
                        int lines = layout.getLineCount();
    
                        int offset = layout.layout.getLineWidth(lines - 1);
                        int freeSpace = width - offset;
    
                        TextView hour = new TextView(MainActivity.this);
                        hour.setText("12:20");
                        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
                        params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
                        if (freeSpace > WIDH_HOUR) {
                            params.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.activity_main_text);
                        } else {
                            params.addRule(RelativeLayout.BELOW, R.id.activity_main_text);
                        }
                        hour.setLayoutParams(params);
                        relativeLayout.addView(hour);
                        Log.d(TAG, String.valueOf(freeSpace));
                        mFirstTime = false;
                    }
    
                }
            });
    
    
        }
    
        public int getScreensWidh() {
            Display display = getWindowManager().getDefaultDisplay();
            Point size = new Point();
            display.getSize(size);
            return size.x;
    
        }
    

    Two Public Methods

    • public abstract int getLineCount ()

    Return the number of lines of text in this layout.

    • public int getLineWidth(int line)

    Gets the unsigned horizontal extent of the specified line, including leading margin indent and trailing whitespace.

    0 讨论(0)
  • 2020-11-28 03:58

    @Hisham Muneer 's answer very good.

    But there are some problems. For example:

    • If the TextView has 2 full lines (end to end), the text will intersect with datetime text layout. Finally, the views will look like onion effect.
    • The text line wraps can't works efficiently. You must control this lines and relocate the datetime view.

    I'm going to share my solution, if you will need like this problem.

    This is example screenshot

    ImFlexboxLayout.java

        public class ImFlexboxLayout extends RelativeLayout {
        private TextView viewPartMain;
        private View viewPartSlave;
    
        private TypedArray a;
    
        private RelativeLayout.LayoutParams viewPartMainLayoutParams;
        private int viewPartMainWidth;
        private int viewPartMainHeight;
    
        private RelativeLayout.LayoutParams viewPartSlaveLayoutParams;
        private int viewPartSlaveWidth;
        private int viewPartSlaveHeight;
    
    
        public ImFlexboxLayout(Context context) {
            super(context);
        }
    
        public ImFlexboxLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            a = context.obtainStyledAttributes(attrs, R.styleable.ImFlexboxLayout, 0, 0);
        }
    
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
    
            try {
                viewPartMain = (TextView) this.findViewById(a.getResourceId(R.styleable.ImFlexboxLayout_viewPartMain, -1));
                viewPartSlave = this.findViewById(a.getResourceId(R.styleable.ImFlexboxLayout_viewPartSlave, -1));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            if (viewPartMain == null || viewPartSlave == null || widthSize <= 0) {
                return;
            }
    
            int availableWidth = widthSize - getPaddingLeft() - getPaddingRight();
            int availableHeight = heightSize - getPaddingTop() - getPaddingBottom();
    
            viewPartMainLayoutParams = (LayoutParams) viewPartMain.getLayoutParams();
            viewPartMainWidth = viewPartMain.getMeasuredWidth() + viewPartMainLayoutParams.leftMargin + viewPartMainLayoutParams.rightMargin;
            viewPartMainHeight = viewPartMain.getMeasuredHeight() + viewPartMainLayoutParams.topMargin + viewPartMainLayoutParams.bottomMargin;
    
            viewPartSlaveLayoutParams = (LayoutParams) viewPartSlave.getLayoutParams();
            viewPartSlaveWidth = viewPartSlave.getMeasuredWidth() + viewPartSlaveLayoutParams.leftMargin + viewPartSlaveLayoutParams.rightMargin;
            viewPartSlaveHeight = viewPartSlave.getMeasuredHeight() + viewPartSlaveLayoutParams.topMargin + viewPartSlaveLayoutParams.bottomMargin;
    
            int viewPartMainLineCount = viewPartMain.getLineCount();
            float viewPartMainLastLineWitdh = viewPartMainLineCount > 0 ? viewPartMain.getLayout().getLineWidth(viewPartMainLineCount - 1) : 0;
    
            widthSize = getPaddingLeft() + getPaddingRight();
            heightSize = getPaddingTop() + getPaddingBottom();
    
            if (viewPartMainLineCount > 1 && !(viewPartMainLastLineWitdh + viewPartSlaveWidth >= viewPartMain.getMeasuredWidth())) {
                widthSize += viewPartMainWidth;
                heightSize += viewPartMainHeight;
            } else if (viewPartMainLineCount > 1 && (viewPartMainLastLineWitdh + viewPartSlaveWidth >= availableWidth)) {
                widthSize += viewPartMainWidth;
                heightSize += viewPartMainHeight + viewPartSlaveHeight;
            } else if (viewPartMainLineCount == 1 && (viewPartMainWidth + viewPartSlaveWidth >= availableWidth)) {
                widthSize += viewPartMain.getMeasuredWidth();
                heightSize += viewPartMainHeight + viewPartSlaveHeight;
            } else {
                widthSize += viewPartMainWidth + viewPartSlaveWidth;
                heightSize += viewPartMainHeight;
            }
    
            this.setMeasuredDimension(widthSize, heightSize);
            super.onMeasure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
        }
    
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
    
            if (viewPartMain == null || viewPartSlave == null) {
                return;
            }
    
            viewPartMain.layout(
                    getPaddingLeft(),
                    getPaddingTop(),
                    viewPartMain.getWidth() + getPaddingLeft(),
                    viewPartMain.getHeight() + getPaddingTop());
    
            viewPartSlave.layout(
                    right - left - viewPartSlaveWidth - getPaddingRight(),
                    bottom - top - getPaddingBottom() - viewPartSlaveHeight,
                    right - left - getPaddingRight(),
                    bottom - top - getPaddingBottom());
        }
    }
    

    attrs.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="ImFlexboxLayout">
            <attr name="viewPartMain" format="reference"></attr>
            <attr name="viewPartSlave" format="reference"></attr>
        </declare-styleable>
    
    </resources>
    

    Example right ballon layout (balloon.xml)

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:baselineAligned="false"
        android:gravity="center_vertical"
        android:orientation="horizontal">
    
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="right|center_vertical"
            android:layout_weight="1"
            android:gravity="right">
    
            <tr.com.client.ImFlexboxLayout
                android:id="@+id/msg_layout"
                style="@style/BalloonMessageLayoutRight"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="right|bottom"
                android:gravity="left|center_vertical"
                app:viewPartMain="@+id/chat_msg"
                app:viewPartSlave="@+id/lytStatusContainer">
    
                <TextView
                    android:id="@+id/chat_msg"
                    style="@style/BalloonMessageRightTextItem"
                    android:layout_width="wrap_content"
                    android:layout_gravity="right|bottom"
                    android:focusableInTouchMode="false"
                    android:gravity="left|top"
                    android:text="hjjfg" />
    
                <LinearLayout
                    android:id="@+id/lytStatusContainer"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="5dp"
                    android:gravity="right"
                    android:minWidth="60dp">
    
                    <TextView
                        android:id="@+id/date_view"
                        style="@style/BallonMessageTimeText"
                        android:layout_alignParentRight="true"
                        android:layout_gravity="right|bottom"
                        android:layout_marginRight="5dp"
                        android:gravity="right"
                        android:maxLines="1" />
    
                    <include
                        android:id="@+id/lytStatus"
                        layout="@layout/layout_im_message_status"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="bottom"
                        android:layout_marginRight="5dp"
                        android:minWidth="40dp" />
    
                </LinearLayout>
    
            </tr.com.client.ImFlexboxLayout>
        </LinearLayout>
    </LinearLayout>
    

    You can modify layout xml and some sections related your scenario.

    There are 2 important point: you must define in layout xml "viewPartMain", "viewPartSlave" attributes. Because the code will decide measure via your main(chat textview) and slave(datetime text view) elements.

    I wish have good days. Greets.

    0 讨论(0)
  • 2020-11-28 03:59

    Adding HTML non-breaking spaces did the trick. Tested the code on most devices and working absolutely fine. Maybe whatsapp is also doing the same thing. Below is the chat code:

    See images below to see it working.

    XML Design:

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/rel_layout_left"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/txtDate"
        android:visibility="visible"
        android:orientation="vertical"
       >
    
        <TextView
            android:id="@+id/lblMsgFrom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="5dp"
            android:text="kfhdjbh"
            android:textColor="@color/lblFromName"
            android:textSize="12dp"
            android:textStyle="italic"
            android:visibility="gone" />
    
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_below="@+id/lblMsgFrom"
            android:layout_marginRight="-5dp"
            android:src="@drawable/bubble_corner" />
    
        <FrameLayout
            android:orientation="horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:background="@drawable/bg_msg_from"
            android:layout_toRightOf="@+id/imageView">
    
            <TextView
                android:id="@+id/txtTimeFrom"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:paddingRight="@dimen/d5"
                android:text="Time"
                android:textColor="@android:color/darker_gray"
                android:layout_gravity="bottom|right"
                android:padding="4dp"
                android:textSize="10dp"
                android:textStyle="italic"
                android:layout_below="@+id/txtMsgFrom"
                android:layout_alignRight="@+id/txtMsgFrom"
                android:layout_alignEnd="@+id/txtMsgFrom" />
    
           <TextView
                android:id="@+id/txtMsgFrom"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignTop="@+id/imageView"
                android:layout_toEndOf="@+id/lblMsgFrom"
                android:layout_toRightOf="@+id/imageView"
                android:paddingLeft="10dp"
                android:paddingRight="10dp"
                android:paddingTop="5dp"
                android:paddingBottom="5dp"
                android:text="kdfjhgjfhf"
                android:textColor="@color/black"
                android:textSize="16dp"
                android:layout_alignParentLeft="true"
                android:layout_marginLeft="0dp"
                android:layout_alignParentTop="true"
                android:layout_marginTop="0dp"
                android:layout_gravity="left|center_vertical" />
        </FrameLayout>
    
    </RelativeLayout>
    

    Code: bg_msg_from.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle" >
    
        <!-- view background color -->
        <!--<solid android:color="@color/bg_msg_from" >-->
        <solid android:color="@android:color/white" >
        </solid>
    
        <corners android:radius="@dimen/d5" >
        </corners>
    
    </shape>
    

    ** File: bubble_corner.png**

    enter image description here enter image description here enter image description here

    txtMsgFrom.setText(Html.fromHtml(convertToHtml(txtMsgFrom.getText().toString()) + " &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;")); // 10 spaces
    
    0 讨论(0)
  • 2020-11-28 04:00

    Its easier with Unicode from here.

    So with this you can archive the Unicode format

     new TextView("Hello\u00A0world");
    

    better than HTML string.

    source: https://stackoverflow.com/a/6565049

    0 讨论(0)
提交回复
热议问题