web-dev-qa-db-ger.com

WhatsApp Nachrichtenlayout - So erhalten Sie eine Zeitansicht in derselben Zeile

Ich habe mich gefragt, wie WhatsApp die in jeder Nachricht angegebene Zeit verarbeitet.

Für diejenigen, die es nicht wissen:

  1. Wenn die Nachricht sehr kurz ist, befinden sich Text und Uhrzeit in derselben Zeile.
  2. Wenn die Nachricht lang ist, befindet sich die Zeit in der rechten unteren Ecke - der Text ist um sie herum gewickelt.

Mit RelativeLayout und toLeftOf würde ich 1) erhalten, aber nicht 2), da vorherige Zeilen an der Position der Zeitansicht "abgeschnitten" werden würden. Gleiches Verhalten Wenn ich eine LinearLayout verwende.

Also habe ich versucht, eine FrameLayout oder eine RelativeLayout zu verwenden, ohne eine Verbindung zwischen Text und Zeit herzustellen.

Wenn der Text jedoch so lang ist, wie die Nachrichtenansicht groß ist, überlappen sich beide Ansichten. Wenn ich die Nachricht mit Leerzeichen füge, hätte ich keine Zeit auf der rechten Seite.

Haben sie wirklich eine Art Text-Wrapping-lib dafür oder ist das nur mit Layouts möglich?

Hier ist der angeforderte Screenshot:

enter image description here

34
Frame91

Der Trick war das Hinzufügen von geschützten Leerzeichen. Den Code auf den meisten Geräten getestet und funktioniert absolut einwandfrei. Vielleicht macht WhatsApp auch das Gleiche. Unten ist der Chat-Code: 

Siehe Bilder unten, um zu sehen, wie es funktioniert.

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>

** Datei: bubble_corner.png **

 Right Arrow Image  enter image description here

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
29
Hisham Muneer

@Hisham Muneer's Antwort sehr gut.

Es gibt jedoch einige Probleme. Zum Beispiel:

  • Wenn die TextView (Ende bis Ende) zwei vollständige Zeilen hat, wird der Text mit dem Datums-Text-Layout Schließlich werden die Ansichten wie ein Zwiebeleffekt aussehen. 
  • Die Textzeilenumbrüche können nicht effizient arbeiten. Sie müssen diese -Zeile steuern und die datetime-Ansicht verschieben.

Ich werde meine Lösung mitteilen, wenn Sie dieses Problem brauchen.

Dies ist ein Beispiel für einen Screenshot  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>

Beispiel eines rechten Ballonlayouts (ballon.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>

Sie können die Layout-XML-Datei und einige Abschnitte Ihres Szenarios ändern. 

Es gibt 2 wichtige Punkte: Sie müssen im Layout xml "viewPartMain", "viewPartSlave" definieren. Denn der Code entscheidet über Ihre Haupt- (Chat-Textansicht) und Slave-Elemente (Datetime-Textansicht).

Ich wünsche gute tage Grüße.

23
Sinan Ergin

Es ist einfacher mit Unicode von hier .

Damit können Sie das Unicode-Format archivieren

 new TextView("Hello\u00A0world");

besser als HTML-String.

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

4
kkm

Ich schlage eine andere Lösung vor

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;

    }

Zwei öffentliche Methoden

Gibt die Anzahl der Textzeilen in diesem Layout zurück.

Ruft die vorzeichenlose horizontale Ausdehnung der angegebenen Linie ab, einschließlich des Einzugs für den vorderen Rand und des Leerzeichens.

2
Cabezas

Sie können das Layout und den Code unten verwenden, um den gewünschten Effekt zu erzielen . Quellcode Gist

Was ich verwendet habe, ist, die Breite des Textes + das Zeit-Layout abzurufen und zu überprüfen, ob dieses die Breite des Container-Layouts überschreitet, und die Höhe des Containers entsprechend anzupassen. Wir müssen von FrameLayout ausgehen, da dies die überlappende Ansicht von zwei untergeordneten Ansichten ist.

Dies wurde getestet, um auf Englisch zu arbeiten. Anregungen und Verbesserungen sind immer willkommen :)

Hoffe, ich habe jemandem geholfen, nach der gleichen Lösung zu suchen.

2
Rahul Shukla

Ich denke, der einfachste Weg, um diese Art von Layout zu erreichen, wäre das Hinzufügen von Leerzeichen in Ihrer Nachricht, um sicherzustellen, dass rechts genug Platz vorhanden ist, um die Zeit nicht zu decken (ich sehe keinen anderen einfachen Weg, um eine Marge zu haben Auffüllen/Positionieren nur für die letzte Zeile Ihres Textes) .__ Dann setzen Sie die Zeit in die relative Position als Ausrichtung rechts unten

1
Xavier Falempin

Ich schlage eine andere Lösung vor. Wenn Sie die maximale Blasenbreite und Zeitdauer kennen, können Sie vorausberechnen, wie Sie Ihre Ansichten platzieren.

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