web-dev-qa-db-fra.com

Whatsapp Message Layout - Comment avoir la vue du temps dans la même ligne

Je me demandais comment WhatsApp gère l'heure indiquée dans chaque message.

Pour ceux qui ne savent pas:

  1. Si le message est très court, le texte et l'heure sont dans la même ligne.
  2. Si le message est long, l'heure est dans le coin en bas à droite - le texte qui l'entoure.

Avec RelativeLayout et toLeftOf j'obtiendrais 1) mais pas 2) car les lignes précédentes seraient "coupées" à la position de la vue temporelle. Même comportement si j'utilise une LinearLayout.

J'ai donc essayé d'utiliser une FrameLayout ou une RelativeLayout sans aucun lien entre le texte et le temps.

Cependant, si le texte est aussi long que la vue du message est grande, les deux vues se chevaucheront ... Si je mets des caractères vides dans mon message, je n'aurais pas le temps à droite.

Ont-ils vraiment une sorte de text-wrapping-lib pour cela ou est-il possible de le faire uniquement avec des mises en page?

Voici la capture d'écran demandée:

enter image description here

34
Frame91

L'ajout d'espaces insécables en HTML a fait l'affaire. Testé le code sur la plupart des appareils et fonctionnant parfaitement. Peut-être que WhatsApp fait aussi la même chose. Ci-dessous le code du chat: 

Voir les images ci-dessous pour le voir fonctionner.

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

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>

** Fichier: 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

La réponse de @Hisham Muneer est très bonne.

Mais il y a quelques problèmes. Par exemple:

  • Si le TextView a 2 lignes complètes (de bout en bout), le texte intersectera avec la disposition du texte datetime. Enfin, les vues ressembleront à un effet d’oignon. 
  • Le retour à la ligne de texte ne peut pas fonctionner efficacement. Vous devez contrôler ces lignes Et déplacer la vue datetime.

Je vais partager ma solution, si vous avez besoin de ce problème.

Ceci est un exemple de capture d'écran  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>

Exemple de disposition de ballon droite (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>

Vous pouvez modifier la mise en page XML et certaines sections liées à votre scénario. 

Il y a 2 point important: vous devez définir les attributs xml "viewPartMain", "viewPartSlave". Parce que le code décidera de la mesure via vos éléments principaux (chat textview) et esclave (date text time).

Je souhaite de bons jours. Salue.

23
Sinan Ergin

C'est plus facile avec Unicode de ici .

Donc, avec cela, vous pouvez archiver le format Unicode

 new TextView("Hello\u00A0world");

mieux que la chaîne HTML.

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

4
kkm

Je propose une autre solution

public static final String TAG = "MainActivity";
    private TextView mText;
    private RelativeLayout relativeLayout;
    private Boolean mFirstTime = true;
    private static final int WIDH_HOUR = 382;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        final int width = getScreensWidh();

        mText = (TextView) findViewById(R.id.activity_main_text);
        relativeLayout = (RelativeLayout) findViewById(R.id.activity_main_relative);

        mText.setText("aaaaa dfsafsa afdsfa fdsafas adfas fdasf adfsa dsa aaaa dfsafsa afdsfa fdsafas adfas fdasf adfsa");

        ViewTreeObserver vto = mText.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (mFirstTime) {
                    Layout layout = mText.getLayout();
                    int lines = layout.getLineCount();

                    int offset = layout.layout.getLineWidth(lines - 1);
                    int freeSpace = width - offset;

                    TextView hour = new TextView(MainActivity.this);
                    hour.setText("12:20");
                    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
                    params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
                    if (freeSpace > WIDH_HOUR) {
                        params.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.activity_main_text);
                    } else {
                        params.addRule(RelativeLayout.BELOW, R.id.activity_main_text);
                    }
                    hour.setLayoutParams(params);
                    relativeLayout.addView(hour);
                    Log.d(TAG, String.valueOf(freeSpace));
                    mFirstTime = false;
                }

            }
        });


    }

    public int getScreensWidh() {
        Display display = getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        return size.x;

    }

Deux méthodes publiques

Renvoie le nombre de lignes de texte dans cette disposition.

Obtient l'étendue horizontale non signée de la ligne spécifiée, y compris le retrait de la marge avant et les espaces finaux.

2
Cabezas

Vous pouvez utiliser la disposition et le code ci-dessous pour obtenir l’effet souhaité . Code source Gist

Ce que j’ai utilisé, c’est d’obtenir la largeur du texte + la disposition temporelle et de vérifier si celle-ci dépasse la largeur de disposition du conteneur, puis d’ajuster la hauteur du conteneur en conséquence. Nous devons étendre à partir de FrameLayout car c'est celui qui permet de superposer deux vues enfants.

Cela a été testé pour fonctionner sur les paramètres régionaux anglais. Les suggestions et améliorations sont toujours les bienvenues :)

J'espère avoir aidé quelqu'un à la recherche de la même solution.

2
Rahul Shukla

Je suppose que le moyen le plus simple d’atteindre ce type de texte serait d’ajouter suffisamment d’espace dans votre message pour vous assurer qu’il y a suffisamment d’espace à droite pour ne pas couvrir le temps remplissage/positionnement pour la dernière ligne de votre texte uniquement) Ensuite, il vous suffit de placer le temps dans le relatif comme aligner en bas à droite

1
Xavier Falempin

Je suggère une autre solution. Si vous connaissez la largeur maximale de la bulle et la largeur temporelle, vous pouvez alors calculer à l'avance comment placer vos vues.

Disposition:

<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