Whatsapp消息布局 - 如何在同一行中获取时间视图

时间:2015-05-11 13:05:32

标签: android android-layout textview

我想知道WhatsApp如何处理每条消息中显示的时间。

对于那些不了解的人:

  1. 如果消息很短,则文本和时间在同一行。
  2. 如果消息很长,则时间位于右下角 - 文本环绕着它。
  3. 使用RelativeLayouttoLeftOf我会得到1)而不是2)因为之前的行会被"切断"在时间视图的位置。相同的行为如果我使用LinearLayout

    所以我尝试使用FrameLayoutRelativeLayout,但文本和时间之间没有任何关联。

    但是,如果文本只要消息视图很大,则两个视图都会重叠。 如果我在帖子中留下空白字符,我就没有时间在右边。

    他们真的有一些text-wrapping-lib,或者只能用布局吗?

    以下是请求的屏幕截图:

    enter image description here

11 个答案:

答案 0 :(得分:29)

添加HTML不间断空格就可以了。在大多数设备上测试代码并且工作得非常好。也许whatsapp也在做同样的事情。以下是聊天代码:

请参阅下面的图片,看它是否正常工作。

XML设计:

<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>

代码: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>

**文件:bubble_corner.png **

Right Arrow Image enter image description here

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

答案 1 :(得分:25)

@Hisham Muneer的回答非常好。

但是有一些问题。例如:

  • 如果TextView有2个实线(端对端),则文本将为 与datetime文本布局相交。最后,意见将 看起来像洋葱的效果。
  • 文本行换行无法有效工作。你必须控制它 行并重新定位日期时间视图。

如果您需要这样的问题,我将分享我的解决方案。

这是示例屏幕截图 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>

右边气球布局示例(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>

您可以修改布局xml以及与您的方案相关的一些部分。

2重点:您必须在布局xml “viewPartMain”“viewPartSlave”属性中定义。因为代码将通过主(聊天文本视图)和从属(日期时间文本视图)元素来决定度量。

我希望过好日子。迎接。

答案 2 :(得分:4)

使用here中的Unicode更容易。

因此,您可以归档Unicode格式

 new TextView("Hello\u00A0world");

比HTML字符串更好。

来源:https://stackoverflow.com/a/6565049

答案 3 :(得分:2)

我提出了另一种解决方案

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;

    }

两种公共方法

返回此布局中的文本行数。

获取指定行的无符号水平范围,包括前导边距缩进和尾随空格。

答案 4 :(得分:2)

您可以使用下面的布局和代码来达到预期效果。 Source code gist

我使用的是获取文本的宽度+时间布局,并检查它是否超出容器布局宽度,并相应地调整容器的高度。我们必须从FrameLayout扩展,因为这是允许重叠两个子视图的那个。

这已经过测试,适用于英语语言环境。建议和改进总是受欢迎的:)

希望我帮助某人寻找相同的解决方案。

答案 5 :(得分:1)

基于@Sinan Ergin的答案,但略有改进:

  • 更少的布局
  • 简化的重力+ layout_gravity的使用
  • 左右布局文件
  • FrameLayout而不是RelativeLayout
  • 转换为科特林
  • 没有attrs.xml和样式引用
/**
 * Layout that allows a [TextView] to flow around a [View].
 *
 * First child must be of type [TextView].
 * Second child must be of type [View].
 */
class TextViewContainerFlowLayout @JvmOverloads constructor(
  context: Context,
  attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
  private val textView by lazy(NONE) { getChildAt(0) as TextView }
  private val containerView by lazy(NONE) { getChildAt(1) }

  private val viewPartMainLayoutParams by lazy(NONE) { textView.layoutParams as LayoutParams }
  private val viewPartSlaveLayoutParams by lazy(NONE) { containerView.layoutParams as LayoutParams }
  private var containerWidth = 0
  private var containerHeight = 0

  override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    var widthSize = MeasureSpec.getSize(widthMeasureSpec)

    if (widthSize <= 0) {
      return
    }

    val availableWidth = widthSize - paddingLeft - paddingRight
    val textViewWidth = textView.measuredWidth + viewPartMainLayoutParams.leftMargin + viewPartMainLayoutParams.rightMargin
    val textViewHeight = textView.measuredHeight + viewPartMainLayoutParams.topMargin + viewPartMainLayoutParams.bottomMargin

    containerWidth = containerView.measuredWidth + viewPartSlaveLayoutParams.leftMargin + viewPartSlaveLayoutParams.rightMargin
    containerHeight = containerView.measuredHeight + viewPartSlaveLayoutParams.topMargin + viewPartSlaveLayoutParams.bottomMargin

    val viewPartMainLineCount = textView.lineCount
    val viewPartMainLastLineWidth = if (viewPartMainLineCount > 0) textView.layout.getLineWidth(viewPartMainLineCount - 1) else 0.0f

    widthSize = paddingLeft + paddingRight
    var heightSize = paddingTop + paddingBottom

    if (viewPartMainLineCount > 1 && viewPartMainLastLineWidth + containerWidth < textView.measuredWidth) {
      widthSize += textViewWidth
      heightSize += textViewHeight
    } else if (viewPartMainLineCount > 1 && viewPartMainLastLineWidth + containerWidth >= availableWidth) {
      widthSize += textViewWidth
      heightSize += textViewHeight + containerHeight
    } else if (viewPartMainLineCount == 1 && textViewWidth + containerWidth >= availableWidth) {
      widthSize += textView.measuredWidth
      heightSize += textViewHeight + containerHeight
    } else {
      widthSize += textViewWidth + containerWidth
      heightSize += textViewHeight
    }

    setMeasuredDimension(widthSize, heightSize)

    super.onMeasure(
      MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
      MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY)
    )
  }

  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    super.onLayout(changed, left, top, right, bottom)

    textView.layout(
      paddingLeft,
      paddingTop,
      textView.width + paddingLeft,
      textView.height + paddingTop
    )

    containerView.layout(
      right - left - containerWidth - paddingRight,
      bottom - top - paddingBottom - containerHeight,
      right - left - paddingRight,
      bottom - top - paddingBottom
    )
  }
}

view_chat_entry.xml

<my.ui.view.TextViewContainerFlowLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="right|bottom"
    android:gravity="left|center_vertical"
    android:padding="12dp"
    >
  <my.ui.android.TextView
      android:id="@+id/message"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="right|bottom"
      android:gravity="left|top"
      android:textIsSelectable="true"
      tools:text="hjjfg"
      />
  <LinearLayout
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:gravity="bottom"
      >
    <TextView
        android:id="@+id/date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:maxLines="1"
        />
    <my.ui.view.ChatEntryStatusView
        android:id="@+id/status"
        android:layout_width="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_height="wrap_content"
        />
  </LinearLayout>
</my.ui.view.TextViewContainerFlowLayout>

adapter_item_chat_left.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="4dp"
    android:layout_marginEnd="64dp"
    android:layout_marginTop="4dp"
    android:gravity="left"
    >
  <include
      layout="@layout/view_chat_entry"
      android:id="@+id/chatEntry"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      />
</LinearLayout>

adapter_item_chat_right.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="4dp"
    android:layout_marginStart="64dp"
    android:layout_marginTop="4dp"
    android:gravity="right"
    >
  <include
      layout="@layout/view_chat_entry"
      android:id="@+id/chatEntry"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      />
</LinearLayout>

最终结果(样式是通过可绘制容器完成的):

enter image description here

答案 6 :(得分:0)

<?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>

答案 7 :(得分:0)

我建议其他解决方案。如果知道最大气泡宽度和时间宽度,则可以预先计算如何放置视图。

布局:

<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>

代码:

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()
}

答案 8 :(得分:0)

先前的答案无法满足我的需求,因为它们太复杂了,并且RecyclerView上的滚动太慢了!滚动时我感觉口吃。因此,我修改了@Rahul Shuklas的答案,以使其更有效率。我在下面分享我的结果。该代码是不言自明的,为便于理解,我添加了注释。

class ChatBubbleLayout : FrameLayout {
constructor(context: Context) : super(context) {}

constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}

constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}

@TargetApi(21)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)

    doMeasure()
}

private fun doMeasure() {

        val messageTextView = findViewById<TextView>(R.id.tv_message)
        val dateTextView = findViewById<TextView>(R.id.tv_message_time)

        // Message line count
        val lineCount = messageTextView.lineCount

        // Message padding
        val messageTextViewPadding = messageTextView.paddingLeft + messageTextView.paddingRight

        // First / Second last line of message
        val lastLineStart = messageTextView.layout.getLineStart(lineCount - 1)
        val lastLineEnd = messageTextView.layout.getLineEnd(lineCount - 1)

        // Width of First / Second last line of message
        var desiredWidth = Layout.getDesiredWidth(messageTextView.text.subSequence(lastLineStart,
                lastLineEnd), messageTextView.paint).toInt()

        var desiredHeight = measuredHeight

        if (desiredWidth < minimumWidth && messageTextView.measuredWidth < minimumWidth) {
            // Probably a small or single line message

            desiredWidth = minimumWidth + messageTextViewPadding

        } else {
            // Probably a bit long or multiple line message

            desiredWidth = messageTextView.measuredWidth + messageTextViewPadding
        }

        if(desiredHeight < minimumHeight) {
            desiredHeight = minimumHeight
        }

        setMeasuredDimension(desiredWidth, desiredHeight)
    } 
}

我的布局XML文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="right">

    <com.app.chat.ui.ChatBubbleLayout
        android:id="@+id/chat_bubble_item_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="@dimen/height_16dp"
        android:background="@drawable/medium_green_rounded_corner"
        android:minWidth="96dp"
        android:minHeight="44dp">

        <TextView
            android:id="@+id/tv_message"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="start|left"
            android:autoLink="all"
            android:linksClickable="true"
            android:maxWidth="280dp"
            android:paddingLeft="@dimen/margin_8dp"
            android:paddingTop="@dimen/margin_8dp"
            android:paddingRight="@dimen/margin_8dp"
            android:paddingBottom="@dimen/margin_8dp"
            android:text="@{chatMessageVM.iMessage.message}"
            android:textColor="@color/white"
            android:textColorLink="@color/white"
            android:textIsSelectable="true"
            android:textSize="@dimen/text_14sp"
            tools:text="Nope" />

        <TextView
            android:id="@+id/tv_message_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end|right|bottom"
            android:layout_marginRight="@dimen/margin_4dp"
            android:layout_marginBottom="@dimen/margin_2dp"
            android:gravity="center_vertical"
            android:text="@{chatMessageVM.iMessage.readableTimestamp}"
            android:textColor="@color/gray_5"
            android:textSize="@dimen/text_12sp"
            tools:text="11:21 AM" />

    </com.app.chat.ui.ChatBubbleLayout>
</LinearLayout>

我希望它对将来的读者有所帮助。

答案 9 :(得分:0)

这是我的布局文件chat_row_right_1.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <RelativeLayout
        android:layout_toLeftOf="@+id/test_arrow"
        android:id="@+id/message_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="15dp"
        android:paddingBottom="7dp"
        android:paddingTop="5dp"
        android:paddingRight="15dp"
        android:layout_marginTop="5dp"
        android:maxWidth="200dp"
        android:background="@drawable/layout_bg2_1"
        tools:ignore="UselessParent">
    <TextView
        android:layout_marginEnd="10dp"
        android:id="@+id/text"
        android:text="demo Text"
        android:textColor="#222"
        android:textSize="17sp"
        android:layout_width="wrap_content"
        android:maxWidth="200dp"
        android:layout_height="wrap_content" />
    <TextClock
        android:id="@+id/msg_time"
        android:layout_toEndOf="@+id/text"
        android:layout_alignBottom="@+id/text"
        android:text="1:30 P.M."
        android:textColor="#888"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ImageView
        android:id="@+id/is_Read_iv"
        android:layout_toEndOf="@+id/msg_time"
        android:layout_alignBottom="@+id/text"
        android:layout_width="wrap_content"
        android:src="@drawable/ic_done_black_24dp"
        android:layout_height="wrap_content" />

    </RelativeLayout>
    <ImageView
        android:id="@+id/test_arrow"
        android:layout_alignParentRight="true"
        android:layout_width="20dp"
        android:background="@null"
        android:layout_marginTop="-2dp"
        android:layout_marginLeft="-8dp"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_play_arrow_black_24dp"/>
</RelativeLayout>

这是可绘制文件夹中的ic_right_bubble.xml文件

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">

<path
    android:fillColor="#cfc"
    android:pathData="M8,5v14l11,-14z"/>
</vector>

您将获得与WhatsApp完全相同的信息参见屏幕截图 enter image description here

答案 10 :(得分:0)

layout_chat_left.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layoutChat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp">

<RelativeLayout
    android:id="@+id/message_send"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp"
    android:layout_toRightOf="@id/test_arrow"
    android:background="@drawable/bg_msg_left"
    android:paddingLeft="15dp"
    android:paddingTop="5dp"
    android:paddingRight="15dp"
    android:paddingBottom="7dp"
    tools:ignore="UselessParent">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="10dp"
        android:maxWidth="200dp"
        android:text="demo Text"
        android:textColor="#222"
        android:textSize="17sp" />

    <TextClock
        android:id="@+id/msg_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/text"
        android:layout_toEndOf="@+id/text"
        android:text="1:30 P.M."
        android:textColor="#888" />

    <ImageView
        android:id="@+id/is_Read_iv"
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:layout_marginBottom="2dp"
        android:layout_marginLeft="2dp"
        android:layout_alignBottom="@+id/text"
        android:layout_toEndOf="@+id/msg_time"
        android:src="@drawable/icon_tick"
        android:tint="@color/BlueTint"/>

</RelativeLayout>

<ImageView
    android:id="@+id/test_arrow"
    android:layout_width="20dp"
    android:layout_height="20dp"
    android:layout_alignParentLeft="true"
    android:layout_marginTop="1dp"
    android:layout_marginRight="-6dp"
    android:background="@null"
    android:scaleX="-1.5"
    android:src="@drawable/v_bubble_corner_left" />
</RelativeLayout>

layout_chat_right.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layoutChat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp">

<RelativeLayout
    android:id="@+id/message_send"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp"
    android:layout_toLeftOf="@id/test_arrow"
    android:background="@drawable/bg_msg_right"
    android:paddingLeft="15dp"
    android:paddingTop="5dp"
    android:paddingRight="15dp"
    android:paddingBottom="7dp"
    tools:ignore="UselessParent">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="10dp"
        android:maxWidth="200dp"
        android:text="demo Text"
        android:textColor="#222"
        android:textSize="17sp" />

    <TextClock
        android:id="@+id/msg_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/text"
        android:layout_toEndOf="@+id/text"
        android:text="1:30 P.M."
        android:textColor="#888" />

    <ImageView
        android:id="@+id/is_Read_iv"
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:layout_marginBottom="2dp"
        android:layout_marginLeft="2dp"

        android:layout_alignBottom="@+id/text"
        android:layout_toEndOf="@+id/msg_time"
        android:src="@drawable/icon_tick"
        android:tint="@color/BlueTint" />

</RelativeLayout>

<ImageView
    android:id="@+id/test_arrow"
    android:layout_width="20dp"
    android:layout_height="20dp"
    android:layout_alignParentRight="true"
    android:layout_marginLeft="-6dp"
    android:layout_marginTop="1dp"
    android:background="@null"
    android:scaleX="1.5"
    android:src="@drawable/v_bubble_corner_right" />
</RelativeLayout>

bg_msg_left.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_right" >-->
<solid android:color="@color/white" >
</solid>

<corners
    android:topLeftRadius="0dp"
    android:topRightRadius="5dp"
    android:bottomLeftRadius="5dp"
    android:bottomRightRadius="5dp">
</corners>
</shape>

bg_msg_right.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_right" >-->
<solid android:color="@color/whatsapp_green" >
</solid>
<corners
    android:topLeftRadius="5dp"
    android:topRightRadius="0dp"
    android:bottomLeftRadius="5dp"
    android:bottomRightRadius="5dp">
</corners>

</shape>

v_bubble_corner_left.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
    <path
        android:fillColor="@color/white"
        android:pathData="M8,5v14l11,-14z" />
</vector>

v_bubble_corner_right.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
    android:fillColor="@color/whatsapp_green"
    android:pathData="M8,5v14l11,-14z"/>
</vector>

CommentAdapter.java是

import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.daimajia.androidanimations.library.Techniques;
import com.daimajia.androidanimations.library.YoYo;
import com.google.android.material.card.MaterialCardView;

import java.util.ArrayList;
import java.util.List;

public class CommentAdapter extends RecyclerView.Adapter<CommentAdapter.ViewHolder> {

private List<String> mComment;
private List<String> mTimeData;
private List<Integer> mIcon;
private List<Integer> mDirection;
private List<Integer> mRecordID;
private Context mContext;
private LayoutInflater mInflater;
private static final String TAG = "CommentAdapter";
private ItemLongClickListener mLongClickListener;

// data is passed into the constructor
CommentAdapter(Context context, List<String> dataComment, List<String> dataTimeData, List<Integer> dataDirection, List<Integer> dataRecordID) {
    mContext = context;
    this.mInflater = LayoutInflater.from( context );
    this.mComment = dataComment;
    this.mTimeData = dataTimeData;
    this.mDirection = dataDirection;
    this.mRecordID = dataRecordID;
}

// inflates the row layout from xml when needed
@NonNull
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view;
    if (viewType == 1) {
        view = mInflater.inflate( R.layout.layout_chat_left, parent, false );
    } else {
        view = mInflater.inflate( R.layout.layout_chat_right, parent, false );
    }


    return new ViewHolder( view );
}

// binds the data to the TextView in each row
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    String mTitle = mComment.get( position );
    holder.tvComment.setText( mTitle );
    String mSubTitle = mTimeData.get( position );
    holder.tvTime.setText( mSubTitle );
    int maxWidth = mContext.getResources().getDisplayMetrics().widthPixels;
    holder.layoutChat.getLayoutParams().width = maxWidth;

}

// total number of rows
@Override
public int getItemCount() {
    return mComment.size();
}


// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener {
    TextView tvComment;
    TextView tvTime;
    TextView tvSerial;
    RelativeLayout layoutChat;
    MaterialCardView cardView;

    ViewHolder(View itemView) {
        super( itemView );
        tvComment = itemView.findViewById( R.id.text );
        tvTime = itemView.findViewById( R.id.msg_time );
        layoutChat = itemView.findViewById( R.id.layoutChat );
        itemView.setOnLongClickListener( this );

    }

    @Override
    public boolean onLongClick(View v) {
        Log.d( TAG, "onLongClick: " + getAdapterPosition() );
        if (mLongClickListener!=null)
        mLongClickListener.onItemLongClick( v, mRecordID.get( getAdapterPosition() ) );
        return true;
    }
}




void setOnLongClickListener(ItemLongClickListener itemLongClickListener) {
    this.mLongClickListener = itemLongClickListener;
}


// parent activity will implement this method to respond to click events
public interface ItemLongClickListener {
    void onItemLongClick(View view, int position);
}

@Override
public int getItemViewType(int position) {
    if (mDirection.get( position ) == 1)
        return 1;
    return 2;
}

}

以下是屏幕截图,来自现场演示

enter image description here

enter image description here