Подтвердить что ты не робот

Макет сообщения Whatsapp - Как получить представление времени в той же строке

Мне было интересно, как WhatsApp обрабатывает время, указанное в каждом сообщении.

Для тех, кто не знает:

  • Если сообщение очень короткое, текст и время находятся в одной строке.
  • Если сообщение длинное, время находится в нижнем правом углу - текст, обернутый вокруг него.

С RelativeLayout и toLeftOf я бы получил 1), но не 2), поскольку предыдущие строки были бы "обрезаны" в позиции временного представления. То же поведение Если я использую LinearLayout.

Поэтому я попытался использовать FrameLayout или RelativeLayout без какого-либо соединения между текстом и временем.

Однако, если текст до тех пор, пока сообщение больше, оба представления будут перекрываться. Если я поместил пустые символы в мое сообщение, у меня не было бы времени справа.

У них действительно есть какой-то текст-wrapping-lib для этого или можно ли делать только с макетами?

Вот скриншот:

enter image description here

4b9b3361

Ответ 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 hereenter image description hereenter image description here

txtMsgFrom.setText(Html.fromHtml(convertToHtml(txtMsgFrom.getText().toString()) + " &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;")); // 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()
}