Макет сообщения Whatsapp - Как получить представление времени в той же строке
Мне было интересно, как WhatsApp обрабатывает время, указанное в каждом сообщении.
Для тех, кто не знает:
- Если сообщение очень короткое, текст и время находятся в одной строке.
- Если сообщение длинное, время находится в нижнем правом углу - текст, обернутый вокруг него.
С RelativeLayout
и toLeftOf
я бы получил 1), но не 2), поскольку предыдущие строки были бы "обрезаны" в позиции временного представления. То же поведение Если я использую LinearLayout
.
Поэтому я попытался использовать FrameLayout
или RelativeLayout
без какого-либо соединения между текстом и временем.
Однако, если текст до тех пор, пока сообщение больше, оба представления будут перекрываться.
Если я поместил пустые символы в мое сообщение, у меня не было бы времени справа.
У них действительно есть какой-то текст-wrapping-lib для этого или можно ли делать только с макетами?
Вот скриншот:
![enter image description here]()
Ответы
Ответ 1
Добавление 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 **
![введите описание изображения здесь]()
![enter image description here]()
![enter image description here]()
![enter image description here]()
txtMsgFrom.setText(Html.fromHtml(convertToHtml(txtMsgFrom.getText().toString()) + "         ")); // 10 spaces
Ответ 2
@Хишам Мьюнер ответил очень хорошо.
Но есть некоторые проблемы. Например:
- Если TextView имеет 2 полные строки (от начала до конца), текст будет пересекаться с макетом даты и времени. Наконец, взгляды будут выглядеть как эффект лука.
- Текстовые переносы строк не могут работать эффективно. Вы должны контролировать эти строки и переместить представление даты и времени.
Я собираюсь поделиться своим решением, если вам понадобится такая проблема.
Это пример скриншота ![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". Потому что код будет определять размер с помощью вашего основного (текстовое представление чата) и подчиненного (текстовое представление даты и времени).
Желаю хороших дней. Поздравил.
Ответ 3
Его проще с Unicode от здесь.
Таким образом, вы можете архивировать формат Unicode
new TextView("Hello\u00A0world");
лучше, чем строка HTML.
источник: fooobar.com/questions/78269/...
Ответ 4
Я предлагаю другое решение
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;
}
Два общедоступных метода
Возвращает количество строк текста в этом макете.
Возвращает беззнаковое горизонтальное протяжение указанной строки, включая отступ вперед и конечное пробел.
Ответ 5
Вы можете использовать макет и код ниже для достижения желаемого эффекта.
Исходный код
То, что я использовал, это получить ширину текста + временную компоновку и проверить, превышает ли это ширину макета контейнера и соответственно регулировать высоту контейнера. Мы должны перейти от FrameLayout, так как это позволяет перекрывать два дочерних представления.
Это проверено на работу с английским языком. Предложения и улучшения всегда приветствуются:)
Надеюсь, я помог кому-то найти то же самое решение.
Ответ 6
Я думаю, что самый простой способ добиться этого вида llayout состоял бы в том, чтобы добавить достаточное пустое пространство в ваше сообщение, чтобы быть уверенным, что достаточно места справа, чтобы не покрывать время (я не вижу другого простого способа иметь margin/padding/positioning для последней строки вашего текста)
Затем вы просто поместите время в относительном, как выравнивание нижнего правого
Ответ 7
<?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>
Ответ 8
Я предлагаю другое решение. Если вы знаете максимальную ширину пузыря и ширину времени, то вы можете заранее рассчитать, как разместить ваши виды.
Планировка:
<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()
}