I'm trying to make a help popup. Since the help tips will be different according to which screen you click the help button from, I want to put the text inside a scroll just in case. My layout looks like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/helpPopupTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:gravity="center"
android:text="#string/help_popup_title"
android:textColor="?attr/colorOnBackground"
android:textSize="24sp"
android:textStyle="bold" />
<ScrollView
android:id="#+id/scroll"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:layout_weight="1"
android:fillViewport="true">
<TextView
android:id="#+id/helpTips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/help_tips"
android:textSize="20sp" />
</ScrollView>
<Button
android:id="#+id/footerButton"
style="#style/RoundedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="20dp"
android:text="#string/footer_button_text" />
It's basically a title, the scroll, and a button. My problem is, when the popup shows up, the scroll is just as tall as the text inside of it.
This is how it looks
This is how I build the dialog:
val dialogView = LayoutInflater.from(this).inflate(R.layout.help_popup, null)
val dialogBuilder = AlertDialog.Builder(this).setView(dialogView)
dialogView.findViewById<TextView>(R.id.helpPopupTitle).setText(R.string.help_popup_title)
dialogView.findViewById<TextView>(R.id.helpTips).setText(R.string.help_tips)
val dialog = dialogBuilder.create()
dialog.setCanceledOnTouchOutside(true)
dialogView.findViewById<Button>(R.id.footerButton).setOnClickListener {
dialog.dismiss()
}
dialog.show()
val displayMetrics = DisplayMetrics()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
val display = display
display?.getRealMetrics(displayMetrics)
} else {
#Suppress("DEPRECATION")
val display = windowManager.defaultDisplay
#Suppress("DEPRECATION")
display.getMetrics(displayMetrics)
}
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
val popupWidth = width * 0.9
val popupHeight = height * 0.85
dialog.window!!.setLayout(popupWidth.toInt(), popupHeight.toInt())
It is really messy, but so far it works since what I want is a popup that covers most of the screen but not entirely. I've looked in other threads but most just say "just put android:fillViewport="true" and the scroll will fill the parent" but it doesn't work for me, maybe I messed up something while building the popup. Any help?
EDIT: After trying the answer provided by gioravered the weight is actually working and the scroll fills the parent. The only problem is that now the layout is slightly offcentered.
Popup after the edit
Keep your LinearLayout, but add an ID for the root view (mainLayout):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mainLayout"
android:orientation="vertical">
<TextView
android:id="#+id/helpPopupTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:gravity="center"
android:text="#string/help_popup_title"
android:textColor="?attr/colorOnBackground"
android:textSize="24sp"
android:textStyle="bold" />
<ScrollView
android:id="#+id/scroll"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:layout_weight="1"
android:fillViewport="true">
<TextView
android:id="#+id/helpTips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/help_tips"
android:textSize="20sp" />
</ScrollView>
<Button
android:id="#+id/footerButton"
style="#style/RoundedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="20dp"
android:text="#string/footer_button_text" />
</LinearLayout>
What you did was changing the size of the dialog window.
Instead, let's change the size of the Layout itself.
val dialogView = LayoutInflater.from(this).inflate(R.layout.popup, null)
val dialogBuilder = AlertDialog.Builder(this).setView(dialogView)
val dialog = dialogBuilder.create()
dialog.setCanceledOnTouchOutside(true)
dialogView.findViewById<Button>(R.id.footerButton).setOnClickListener {
dialog.dismiss()
}
dialog.show()
val displayMetrics = DisplayMetrics()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
val display = display
display?.getRealMetrics(displayMetrics)
} else {
#Suppress("DEPRECATION")
val display = windowManager.defaultDisplay
#Suppress("DEPRECATION")
display.getMetrics(displayMetrics)
}
val screenWidth = displayMetrics.widthPixels
val screenHeight = displayMetrics.heightPixels
val popupWidth = screenWidth * 0.9
val popupHeight = screenHeight * 0.85
val mainLayout = dialogView.findViewById<LinearLayout>(R.id.mainLayout)
val params = (mainLayout.layoutParams as? FrameLayout.LayoutParams)?.apply {
width = popupWidth.toInt()
height = popupHeight.toInt()
gravity = Gravity.CENTER;
}
mainLayout.layoutParams = params
The change is this line:
dialogView.findViewById<LinearLayout>(R.id.mainLayout).layoutParams =
Since the layout is the parent layout it will determine the size of the dialog.
I'm preparing a little game in my app and would like to create a recylerview based on gridlayoutmanager and my problem is that I can't center horizontally all elements inside this view.
I have already tried to fix it by some changes in layout file but it didn't help.
Adapter :
class AdapterCheckYrslf(private val word: String) :
RecyclerView.Adapter<AdapterCheckYrslf.CardViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_checkrslf_letter, parent, false)
return CardViewHolder(view)
}
override fun getItemCount(): Int {
return word.length
}
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
holder.currentLetter.text = word[position].toUpperCase().toString()
}
class CardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val currentLetter = itemView.findViewById<TextView>(R.id.item_checkyrslf_letter)
}
}
GridSpacingItemDecoration, it makes a space beetwen every item :
class GridSpacingItemDecoration(private val space: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View,
parent: RecyclerView, state: RecyclerView.State) {
outRect.left = space
outRect.right = space
outRect.bottom = space
outRect.top = space
}
}
ColumnQty :
class ColumnQty(context: Context, viewId: Int) {
private val width: Int
private val height: Int
private var remaining: Int = 0
private val displayMetrics: DisplayMetrics
init {
val view = View.inflate(context, viewId, null)
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
width = view.measuredWidth
height = view.measuredHeight
displayMetrics = context.resources.displayMetrics
}
fun calculateNoOfColumns(): Int {
var numberOfColumns = displayMetrics.widthPixels / width
remaining = displayMetrics.widthPixels - numberOfColumns * width
if (remaining / (2 * numberOfColumns) < 15) {
numberOfColumns--
remaining = displayMetrics.widthPixels - numberOfColumns * width
}
return numberOfColumns
}
fun calculateSpacing(): Int {
val numberOfColumns = calculateNoOfColumns()
return remaining / (2 * numberOfColumns)
}
}
Activity :
val gridManagerFix = ColumnQty(this, R.layout.item_checkrslf_letter)
check_yrslf_word.layoutManager = GridLayoutManager(this, gridManagerFix.calculateNoOfColumns())
check_yrslf_word.addItemDecoration(GridSpacingItemDecoration(gridManagerFix.calculateSpacing()))
check_yrslf_word.setHasFixedSize(true)
Fragment of layout :
<LinearLayout
android:id="#+id/helper_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal|center_vertical"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="#+id/check_yrslf_end"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/check_yrslf_hand">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/check_yrslf_word"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:clipChildren="true"
android:clipToPadding="true"
android:numColumns="auto_fit"
android:overScrollMode="never"
android:scrollbars="none"
android:scrollingCache="true"
android:stretchMode="columnWidth"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
Item layout :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.card.MaterialCardView
android:id="#+id/item_checkyrslf_cardLetter"
style="#style/Widget.MaterialComponents.CardView"
android:layout_width="27dp"
android:layout_height="27dp"
android:layout_marginBottom="5dp"
android:checkable="false"
app:cardBackgroundColor="#color/grey_3"
app:cardElevation="0dp"
app:checkedIconTint="#color/white"
app:strokeColor="#color/colorAccent"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:id="#+id/item_checkyrslf_letter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fontFamily="#font/poppins_medium"
android:gravity="center"
android:includeFontPadding="false"
android:text="A"
android:textAppearance="#style/TextAppearance.MaterialComponents.Headline1"
android:textColor="#color/textColor"
android:textSize="16sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
Pictures :
How does this layout look in Preview
That's what I have
I would like to have
Try making the RecyclerView android:layout_width="match_parent" and android:gravity="center"
Edit 1:
Try removing all those added params to your RecyclerView: (Something like this)
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/check_yrslf_word"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_gravity="center_horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Now try it, just for debugging purposes.
I have solved it by changing GridLayoutManager to LinearLayoutManager.
I was wondering how WhatsApp handles the time shown in every message.
For those who don't know:
If the message is very short, the text and time are in the same row.
If the message is long, the time is in the bottom right corner - the text wrapped around it.
With a RelativeLayout and toLeftOf I would get 1) but not 2) as previous lines would be "cut off" at the position of the time view. Same behaviour If i use a LinearLayout.
So I tried to use a FrameLayout or a RelativeLayout without any connection between text and time.
However, if the text is as long as the message-view is big, both views would overlap.
If I put blank characters to my message I wouldn't have the time on the right.
Do they really have some kind of text-wrapping-lib for this or is it possible to do only with layouts?
Here is the requested screenshot:
#Hisham Muneer 's answer very good.
But there are some problems. For example:
If the TextView has 2 full lines (end to end), the text will
intersect with datetime text layout. Finally, the views will
look like onion effect.
The text line wraps can't works efficiently. You must control this
lines and relocate the datetime view.
I'm going to share my solution, if you will need like this problem.
This is 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>
Example right ballon layout (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>
You can modify layout xml and some sections related your scenario.
There are 2 important point: you must define in layout xml "viewPartMain", "viewPartSlave" attributes. Because the code will decide measure via your main(chat textview) and slave(datetime text view) elements.
I wish have good days. Greets.
Adding HTML non-breaking spaces did the trick. Tested the code on most devices and working absolutely fine. Maybe whatsapp is also doing the same thing. Below is the chat code:
See images below to see it working.
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>
** File: bubble_corner.png**
txtMsgFrom.setText(Html.fromHtml(convertToHtml(txtMsgFrom.getText().toString()) + " ")); // 10 spaces
Its easier with Unicode from here.
So with this you can archive the Unicode format
new TextView("Hello\u00A0world");
better than HTML string.
source: https://stackoverflow.com/a/6565049
Based on #Sinan Ergin answer but slightly improved:
less layouts
simplified gravity + layout_gravity usage
left + right layout files
FrameLayout instead of RelativeLayout
converted to Kotlin
no attrs.xml & style references
/**
* Layout that allows a [TextView] to flow around a [View].
*
* First child must be of type [TextView].
* Second child must be of type [View].
*/
class TextViewContainerFlowLayout #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
private val textView by lazy(NONE) { getChildAt(0) as TextView }
private val containerView by lazy(NONE) { getChildAt(1) }
private val viewPartMainLayoutParams by lazy(NONE) { textView.layoutParams as LayoutParams }
private val viewPartSlaveLayoutParams by lazy(NONE) { containerView.layoutParams as LayoutParams }
private var containerWidth = 0
private var containerHeight = 0
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
var widthSize = MeasureSpec.getSize(widthMeasureSpec)
if (widthSize <= 0) {
return
}
val availableWidth = widthSize - paddingLeft - paddingRight
val textViewWidth = textView.measuredWidth + viewPartMainLayoutParams.leftMargin + viewPartMainLayoutParams.rightMargin
val textViewHeight = textView.measuredHeight + viewPartMainLayoutParams.topMargin + viewPartMainLayoutParams.bottomMargin
containerWidth = containerView.measuredWidth + viewPartSlaveLayoutParams.leftMargin + viewPartSlaveLayoutParams.rightMargin
containerHeight = containerView.measuredHeight + viewPartSlaveLayoutParams.topMargin + viewPartSlaveLayoutParams.bottomMargin
val viewPartMainLineCount = textView.lineCount
val viewPartMainLastLineWidth = if (viewPartMainLineCount > 0) textView.layout.getLineWidth(viewPartMainLineCount - 1) else 0.0f
widthSize = paddingLeft + paddingRight
var heightSize = paddingTop + paddingBottom
if (viewPartMainLineCount > 1 && viewPartMainLastLineWidth + containerWidth < textView.measuredWidth) {
widthSize += textViewWidth
heightSize += textViewHeight
} else if (viewPartMainLineCount > 1 && viewPartMainLastLineWidth + containerWidth >= availableWidth) {
widthSize += textViewWidth
heightSize += textViewHeight + containerHeight
} else if (viewPartMainLineCount == 1 && textViewWidth + containerWidth >= availableWidth) {
widthSize += textView.measuredWidth
heightSize += textViewHeight + containerHeight
} else {
widthSize += textViewWidth + containerWidth
heightSize += textViewHeight
}
setMeasuredDimension(widthSize, heightSize)
super.onMeasure(
MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY)
)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
textView.layout(
paddingLeft,
paddingTop,
textView.width + paddingLeft,
textView.height + paddingTop
)
containerView.layout(
right - left - containerWidth - paddingRight,
bottom - top - paddingBottom - containerHeight,
right - left - paddingRight,
bottom - top - paddingBottom
)
}
}
view_chat_entry.xml
<my.ui.view.TextViewContainerFlowLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:gravity="left|center_vertical"
android:padding="12dp"
>
<my.ui.android.TextView
android:id="#+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:gravity="left|top"
android:textIsSelectable="true"
tools:text="hjjfg"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom"
>
<TextView
android:id="#+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:maxLines="1"
/>
<my.ui.view.ChatEntryStatusView
android:id="#+id/status"
android:layout_width="wrap_content"
android:layout_marginStart="4dp"
android:layout_height="wrap_content"
/>
</LinearLayout>
</my.ui.view.TextViewContainerFlowLayout>
view_chat_entry_status.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:layout_width="wrap_content"
tools:layout_height="wrap_content"
tools:parentTag="android.widget.FrameLayout"
>
<ImageView
android:contentDescription="#null"
android:id="#+id/statusImageView"
android:layout_width="15dp"
android:layout_height="15dp"
app:srcCompat="#drawable/ic_check_one"/>
</merge>
adapter_item_chat_left.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginEnd="64dp"
android:layout_marginTop="4dp"
android:gravity="left"
>
<include
layout="#layout/view_chat_entry"
android:id="#+id/chatEntry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
adapter_item_chat_right.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginStart="64dp"
android:layout_marginTop="4dp"
android:gravity="right"
>
<include
layout="#layout/view_chat_entry"
android:id="#+id/chatEntry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
End result (styling is done on the container with a drawable):
I propose another 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;
}
Two Public Methods
public abstract int getLineCount ()
Return the number of lines of text in this layout.
public int getLineWidth(int line)
Gets the unsigned horizontal extent of the specified line, including leading margin indent and trailing whitespace.
You can use the layout and code below to achieve the desired effect.
Source code gist
What I have used is get the width of the text + the time layout and check if this exceeds the container layout width, and adjust the height of the container accordingly. We have to extend from FrameLayout since this is the one which allows overlapping of two child views.
This is tested to be working on English locale. Suggestions and improvements are always welcome :)
Hope I've helped someone looking for the same solution.
I guess the easiest way to achieve this kind oflayout would be to add enough blank space in your message to be sure there is enough space on the right to not cover the time (I don't see any other easy way to have a margin/padding/positioning for the last line of your text only)
Then you just place the time in the relative as an align bottom right
layout_chat_left.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/layoutChat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp">
<RelativeLayout
android:id="#+id/message_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_toRightOf="#id/test_arrow"
android:background="#drawable/bg_msg_left"
android:paddingLeft="15dp"
android:paddingTop="5dp"
android:paddingRight="15dp"
android:paddingBottom="7dp"
tools:ignore="UselessParent">
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:maxWidth="200dp"
android:text="demo Text"
android:textColor="#222"
android:textSize="17sp" />
<TextClock
android:id="#+id/msg_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="#+id/text"
android:layout_toEndOf="#+id/text"
android:text="1:30 P.M."
android:textColor="#888" />
<ImageView
android:id="#+id/is_Read_iv"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginBottom="2dp"
android:layout_marginLeft="2dp"
android:layout_alignBottom="#+id/text"
android:layout_toEndOf="#+id/msg_time"
android:src="#drawable/icon_tick"
android:tint="#color/BlueTint"/>
</RelativeLayout>
<ImageView
android:id="#+id/test_arrow"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentLeft="true"
android:layout_marginTop="1dp"
android:layout_marginRight="-6dp"
android:background="#null"
android:scaleX="-1.5"
android:src="#drawable/v_bubble_corner_left" />
</RelativeLayout>
layout_chat_right.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/layoutChat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp">
<RelativeLayout
android:id="#+id/message_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_toLeftOf="#id/test_arrow"
android:background="#drawable/bg_msg_right"
android:paddingLeft="15dp"
android:paddingTop="5dp"
android:paddingRight="15dp"
android:paddingBottom="7dp"
tools:ignore="UselessParent">
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:maxWidth="200dp"
android:text="demo Text"
android:textColor="#222"
android:textSize="17sp" />
<TextClock
android:id="#+id/msg_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="#+id/text"
android:layout_toEndOf="#+id/text"
android:text="1:30 P.M."
android:textColor="#888" />
<ImageView
android:id="#+id/is_Read_iv"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginBottom="2dp"
android:layout_marginLeft="2dp"
android:layout_alignBottom="#+id/text"
android:layout_toEndOf="#+id/msg_time"
android:src="#drawable/icon_tick"
android:tint="#color/BlueTint" />
</RelativeLayout>
<ImageView
android:id="#+id/test_arrow"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentRight="true"
android:layout_marginLeft="-6dp"
android:layout_marginTop="1dp"
android:background="#null"
android:scaleX="1.5"
android:src="#drawable/v_bubble_corner_right" />
</RelativeLayout>
bg_msg_left.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_right" >-->
<solid android:color="#color/white" >
</solid>
<corners
android:topLeftRadius="0dp"
android:topRightRadius="5dp"
android:bottomLeftRadius="5dp"
android:bottomRightRadius="5dp">
</corners>
</shape>
bg_msg_right.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_right" >-->
<solid android:color="#color/whatsapp_green" >
</solid>
<corners
android:topLeftRadius="5dp"
android:topRightRadius="0dp"
android:bottomLeftRadius="5dp"
android:bottomRightRadius="5dp">
</corners>
</shape>
v_bubble_corner_left.xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#color/white"
android:pathData="M8,5v14l11,-14z" />
</vector>
v_bubble_corner_right.xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#color/whatsapp_green"
android:pathData="M8,5v14l11,-14z"/>
</vector>
And the CommentAdapter.java is
import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.daimajia.androidanimations.library.Techniques;
import com.daimajia.androidanimations.library.YoYo;
import com.google.android.material.card.MaterialCardView;
import java.util.ArrayList;
import java.util.List;
public class CommentAdapter extends RecyclerView.Adapter<CommentAdapter.ViewHolder> {
private List<String> mComment;
private List<String> mTimeData;
private List<Integer> mIcon;
private List<Integer> mDirection;
private List<Integer> mRecordID;
private Context mContext;
private LayoutInflater mInflater;
private static final String TAG = "CommentAdapter";
private ItemLongClickListener mLongClickListener;
// data is passed into the constructor
CommentAdapter(Context context, List<String> dataComment, List<String> dataTimeData, List<Integer> dataDirection, List<Integer> dataRecordID) {
mContext = context;
this.mInflater = LayoutInflater.from( context );
this.mComment = dataComment;
this.mTimeData = dataTimeData;
this.mDirection = dataDirection;
this.mRecordID = dataRecordID;
}
// inflates the row layout from xml when needed
#NonNull
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if (viewType == 1) {
view = mInflater.inflate( R.layout.layout_chat_left, parent, false );
} else {
view = mInflater.inflate( R.layout.layout_chat_right, parent, false );
}
return new ViewHolder( view );
}
// binds the data to the TextView in each row
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
String mTitle = mComment.get( position );
holder.tvComment.setText( mTitle );
String mSubTitle = mTimeData.get( position );
holder.tvTime.setText( mSubTitle );
int maxWidth = mContext.getResources().getDisplayMetrics().widthPixels;
holder.layoutChat.getLayoutParams().width = maxWidth;
}
// total number of rows
#Override
public int getItemCount() {
return mComment.size();
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener {
TextView tvComment;
TextView tvTime;
TextView tvSerial;
RelativeLayout layoutChat;
MaterialCardView cardView;
ViewHolder(View itemView) {
super( itemView );
tvComment = itemView.findViewById( R.id.text );
tvTime = itemView.findViewById( R.id.msg_time );
layoutChat = itemView.findViewById( R.id.layoutChat );
itemView.setOnLongClickListener( this );
}
#Override
public boolean onLongClick(View v) {
Log.d( TAG, "onLongClick: " + getAdapterPosition() );
if (mLongClickListener!=null)
mLongClickListener.onItemLongClick( v, mRecordID.get( getAdapterPosition() ) );
return true;
}
}
void setOnLongClickListener(ItemLongClickListener itemLongClickListener) {
this.mLongClickListener = itemLongClickListener;
}
// parent activity will implement this method to respond to click events
public interface ItemLongClickListener {
void onItemLongClick(View view, int position);
}
#Override
public int getItemViewType(int position) {
if (mDirection.get( position ) == 1)
return 1;
return 2;
}
}
Here are the screenshots, one from a live demo
<?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>
I suggest other solution. If you know max bubble width and time width, then you can precalculate how to place your views.
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()
}
The previous answers didn't satisfy my needs as they were too complex and the scrolling on RecyclerView was way too slow! I could feel the stutter while scrolling. So, I modified #Rahul Shuklas answer to make it more efficient. I am sharing my result below. The code is self explanatory, I have added comments for more understandability.
class ChatBubbleLayout : FrameLayout {
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
#TargetApi(21)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
doMeasure()
}
private fun doMeasure() {
val messageTextView = findViewById<TextView>(R.id.tv_message)
val dateTextView = findViewById<TextView>(R.id.tv_message_time)
// Message line count
val lineCount = messageTextView.lineCount
// Message padding
val messageTextViewPadding = messageTextView.paddingLeft + messageTextView.paddingRight
// First / Second last line of message
val lastLineStart = messageTextView.layout.getLineStart(lineCount - 1)
val lastLineEnd = messageTextView.layout.getLineEnd(lineCount - 1)
// Width of First / Second last line of message
var desiredWidth = Layout.getDesiredWidth(messageTextView.text.subSequence(lastLineStart,
lastLineEnd), messageTextView.paint).toInt()
var desiredHeight = measuredHeight
if (desiredWidth < minimumWidth && messageTextView.measuredWidth < minimumWidth) {
// Probably a small or single line message
desiredWidth = minimumWidth + messageTextViewPadding
} else {
// Probably a bit long or multiple line message
desiredWidth = messageTextView.measuredWidth + messageTextViewPadding
}
if(desiredHeight < minimumHeight) {
desiredHeight = minimumHeight
}
setMeasuredDimension(desiredWidth, desiredHeight)
}
}
My Layout XML file
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<com.app.chat.ui.ChatBubbleLayout
android:id="#+id/chat_bubble_item_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="#dimen/height_16dp"
android:background="#drawable/medium_green_rounded_corner"
android:minWidth="96dp"
android:minHeight="44dp">
<TextView
android:id="#+id/tv_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|left"
android:autoLink="all"
android:linksClickable="true"
android:maxWidth="280dp"
android:paddingLeft="#dimen/margin_8dp"
android:paddingTop="#dimen/margin_8dp"
android:paddingRight="#dimen/margin_8dp"
android:paddingBottom="#dimen/margin_8dp"
android:text="#{chatMessageVM.iMessage.message}"
android:textColor="#color/white"
android:textColorLink="#color/white"
android:textIsSelectable="true"
android:textSize="#dimen/text_14sp"
tools:text="Nope" />
<TextView
android:id="#+id/tv_message_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|right|bottom"
android:layout_marginRight="#dimen/margin_4dp"
android:layout_marginBottom="#dimen/margin_2dp"
android:gravity="center_vertical"
android:text="#{chatMessageVM.iMessage.readableTimestamp}"
android:textColor="#color/gray_5"
android:textSize="#dimen/text_12sp"
tools:text="11:21 AM" />
</com.app.chat.ui.ChatBubbleLayout>
</LinearLayout>
I hope it helps future readers.
Here is my Layout file chat_row_right_1.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_toLeftOf="#+id/test_arrow"
android:id="#+id/message_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="15dp"
android:paddingBottom="7dp"
android:paddingTop="5dp"
android:paddingRight="15dp"
android:layout_marginTop="5dp"
android:maxWidth="200dp"
android:background="#drawable/layout_bg2_1"
tools:ignore="UselessParent">
<TextView
android:layout_marginEnd="10dp"
android:id="#+id/text"
android:text="demo Text"
android:textColor="#222"
android:textSize="17sp"
android:layout_width="wrap_content"
android:maxWidth="200dp"
android:layout_height="wrap_content" />
<TextClock
android:id="#+id/msg_time"
android:layout_toEndOf="#+id/text"
android:layout_alignBottom="#+id/text"
android:text="1:30 P.M."
android:textColor="#888"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="#+id/is_Read_iv"
android:layout_toEndOf="#+id/msg_time"
android:layout_alignBottom="#+id/text"
android:layout_width="wrap_content"
android:src="#drawable/ic_done_black_24dp"
android:layout_height="wrap_content" />
</RelativeLayout>
<ImageView
android:id="#+id/test_arrow"
android:layout_alignParentRight="true"
android:layout_width="20dp"
android:background="#null"
android:layout_marginTop="-2dp"
android:layout_marginLeft="-8dp"
android:layout_height="wrap_content"
android:src="#drawable/ic_play_arrow_black_24dp"/>
</RelativeLayout>
And here is ic_right_bubble.xml file in drawable folder
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#cfc"
android:pathData="M8,5v14l11,-14z"/>
</vector>
You will get exact same as WhatsApp See screenshot
This question already has answers here:
Android: center gridview horizontally
(4 answers)
Closed 9 years ago.
I have a GridView with 4 items that are added dynamically. I can center them vertically but can not horizontally. Tried everything...
Main.xml
...
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_gravity="center" >
<GridView
android:id="#+id/grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnWidth="90dp"
android:numColumns="auto_fit"
android:padding="10dp"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:stretchMode="spacingWidthUniform"
android:gravity="center"
android:listSelector="#drawable/card_button" >
</GridView>
</LinearLayout>
...
Items.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/btn"
android:layout_width="match_parent"
android:layout_height="90dp"
android:orientation="vertical"
android:gravity="center" >
<ImageButton
android:id="#+id/menuItem_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/light"
android:contentDescription="#string/item"
android:background="#00000000"
android:tint="#222222"
android:paddingTop="8dp"
android:paddingBottom="4dp" />
<TextView
android:id="#+id/menuItem_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#222222"
android:paddingBottom="8dp"
android:text="#string/item"
android:textSize="18sp"
android:singleLine="true" />
</LinearLayout>
I don't want to have fixed number of columns so android:numColumns="auto_fit" must stay.
I find out that it's not possible to center GridView horizontally with .xml
So I had to do it programmatically... This answer helped me a lot: https://stackoverflow.com/questions/6141910/android-center-gridview-horizontally
I had to make some changes to make it work:
GridView Adapter.java:
public Adapter(Context context, int width, int height, float density, GridView grid) {
this.context = context;
width_ = width; //get metrics.widthPixels from MainActivtiy.java
height_ = height; //get metrics.heightPixels from MainActivtiy.java
density_ = density; //get metrics.scaledDensity from MainActivtiy.java
grid_ = grid; //get GridView
Rect p = new Rect();
grid_.getSelector().getPadding(p);
int selectorPadding = p.left + p.right;
int mSizePx = (int) Math.floor(90 * density_); //90 is my column width
int mSpacingPx = (int) Math.floor(10 * density_); //10 is space between columns
int numColumns = (int) Math.floor(1f * (width_ - selectorPadding + mSpacingPx) / (mSizePx + mSpacingPx));
// CHANGES
int numbOfCol = menuValues.length; //menuValues (Array[]) are items you are adding to GridView
int contentWidth = 0;
//the most important part
if (numColumns > numbOfCol) {
contentWidth = numbOfCol * mSizePx;
contentWidth += (numbOfCol - 1) * mSpacingPx;
contentWidth += selectorPadding;
} else {
contentWidth = numColumns * mSizePx;
contentWidth += (numColumns - 1) * mSpacingPx;
contentWidth += selectorPadding;
}
int slack = width_ - contentWidth;
//
grid_.setNumColumns(numColumns); //set calculated number of collumns
grid_.setColumnWidth(mSizePx); //90
grid_.setVerticalSpacing(mSpacingPx); //10
grid_.setHorizontalSpacing(mSpacingPx); //10
//changed
grid_.setPadding(slack / 2, mSpacingPx, slack / 2, mSpacingPx); //I set top and bottom padding to 10dp (mSpacingPx)
}
Main.xml (changed):
...
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_gravity="center" >
<GridView
android:id="#+id/grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchMode="none"
android:gravity="center"
android:listSelector="#drawable/card_button"
android:drawSelectorOnTop="true" >
</GridView>
</LinearLayout>
...
Hi a have RelativeLayout and want display 6 buttons like a "TableLayout", i calc size buttons and tried display on Activity. Only last button appear.
I trying do Activity with 6 buttons (buttonsperscreen = 6) when call method.
Can you help me?
The code is:
private void doLayoutWithButtons(int buttonsperscreen) {
// if total butons is % 2 == 0
if (buttonsperscreen % 2 == 0) {
// get display dimensions
DisplayMetrics d = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(d);
int w = d.widthPixels;
int h = d.heightPixels;
// calc size of buttons
int widthButton = w / 2; // only two columns of buttons.
int heightButton = h / ((buttonsperscreen) / 2); // sample 100px /
// (6/2) = 3
int posx = 0;
int posy = 0;
for (int i = 1; i <= buttonsperscreen; i++) {
Button newButton = new Button(this);
newButton.setId(100 + i + 1); // ID of zero will not work
newButton.setText("Botão: " + i);
// atribuindo o tamanho do botão.
newButton.setHeight(heightButton);
newButton.setWidth(widthButton);
newButton.setId(1000 + i);
// positioning buttons...
RelativeLayout layout1 = new RelativeLayout(this);
// set layout with size of button
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
widthButton, heightButton);
params.leftMargin = posx;
params.topMargin = posy;
newButton.setLayoutParams(params);
layout1.setLayoutParams(new LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
layout1.addView(newButton);
setContentView(layout1);
// to calc positions x,y for each button for next iteration
if (posx + widthButton < w) {
posx = posx + widthButton;
} else {
posx = 0;
posy = posy + heightButton;
}
}
}
}
Thanks
Mateus
If you're trying to display buttons evenly spaced across a screen, you don't need to do this in code. You can specify things like this in a layout.
Something like this will give 6 buttons equal widths displayed horizontally.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="horizontal">
<Button android:layout_height="wrap_content" android:id="#+id/button1"
android:text="Button" android:layout_width="0dp"
android:layout_weight="1" />
<Button android:layout_height="wrap_content" android:id="#+id/button2"
android:text="Button" android:layout_width="0dp"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="horizontal">
<Button android:layout_height="wrap_content" android:id="#+id/button3"
android:text="Button" android:layout_width="0dp"
android:layout_weight="1" />
<Button android:layout_height="wrap_content" android:id="#+id/button4"
android:text="Button" android:layout_width="0dp"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="horizontal">
<Button android:layout_height="wrap_content" android:id="#+id/button5"
android:text="Button" android:layout_width="0dp"
android:layout_weight="1" />
<Button android:layout_height="wrap_content" android:id="#+id/button6"
android:text="Button" android:layout_width="0dp"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
As long as you keep the layout_width to be 0dp, and set the weight to be 1, Android will automatically set the widths to be equal, no matter what number of buttons you have.