Je me demandais comment WhatsApp gère l'heure indiquée dans chaque message.
Pour ceux qui ne savent pas:
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:
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:
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>
<?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>
txtMsgFrom.setText(Html.fromHtml(convertToHtml(txtMsgFrom.getText().toString()) + "         ")); // 10 spaces
La réponse de @Hisham Muneer est très bonne.
Mais il y a quelques problèmes. Par exemple:
Je vais partager ma solution, si vous avez besoin de ce problème.
Ceci est un exemple de capture d'écran
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.
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.
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.
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.
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
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()
}
<?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>