How to set android:drawable to left and top? - android

I just wanted to set icon to the top and left corner of my TextView.
This is my code and the output respectively:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/Content"
android:drawableLeft="#drawable/icon" />
Output:
but I want to set my icon to the top and left like this one :

For this purpose you have to take the seperate imageView like this and in textview field you have to add one line code that android:layout_toRightOf="#+id/imageView":
For better understanding see following code:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/imageView"
android:background="#mipmap/ic_launcher"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="#+id/imageView"
android:text="this si fodfjkhsdhaffjsdfasdfjhsdfhjsdhfjhsdfhsdhfhdsjfhsdjhfjhdsfhsdhfjhsdjhfjsdhfjhsdjfhjsdhfjhsdjfhjdshfsdjhfjsdhfsdkjhfjsdhfjhsdjfhjsdhjfhsdjhfjsdhfjhjsdhfjsdhjfhsdjf"
/>
</RelativeLayout>

Use below code;
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/Content"
android:drawableTop="#drawable/def"
android:drawableLeft="#drawable/icon" />

You can align a compound-Drawable to the top (or bottom) by creating a custom Drawable that wraps your Drawable.
Usage
GravityCompoundDrawable gravityDrawable = new GravityCompoundDrawable(innerDrawable);
// NOTE: next 2 lines are important!
innerDrawable.setBounds(0, 0, innerDrawable.getIntrinsicWidth(), innerDrawable.getIntrinsicHeight());
gravityDrawable.setBounds(0, 0, innerDrawable.getIntrinsicWidth(), innerDrawable.getIntrinsicHeight());
textView.setCompoundDrawables(gravityDrawable, null, null, null);
Custom GravityCompoundDrawable class:
public class GravityCompoundDrawable extends Drawable {
// inner Drawable
private final Drawable mDrawable;
public GravityCompoundDrawable(Drawable drawable) {
mDrawable = drawable;
}
#Override
public int getIntrinsicWidth() {
return mDrawable.getIntrinsicWidth();
}
#Override
public int getIntrinsicHeight() {
return mDrawable.getIntrinsicHeight();
}
#Override
public void draw(Canvas canvas) {
int halfCanvas= canvas.getHeight() / 2;
int halfDrawable = mDrawable.getIntrinsicHeight() / 2;
// align to top
canvas.save();
canvas.translate(0, -halfCanvas + halfDrawable);
mDrawable.draw(canvas);
canvas.restore();
}
}

You can use setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom)
(For better Understand refer this link:)
https://developer.android.com/reference/android/widget/TextView.html#attr_android:drawableTop

This code will help you and see the attached image.
<?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="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/llMain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="1">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.10"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#mipmap/ic_launcher" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#mipmap/ic_launcher" />
</LinearLayout>
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.90"
android:text="It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using" />
</LinearLayout>
</LinearLayout>
https://i.stack.imgur.com/hLUGT.png

try this
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="hello world"
android:layout_toEndOf="#+id/imageView" />
<ImageView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_error" />
</RelativeLayout>

None of the solutions from SO worked form me. In the end I did it like this:
class MyTextView #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null
) : AppCompatTextView(context, style) {
private val leftDrawable = ContextCompat.getDrawable(context, R.drawable.checkmark)
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
setBulletPoint(compoundDrawables[0], canvas)
}
private fun setBulletPoint(drawableLeft: Drawable?, canvas: Canvas?) {
if (!TextUtils.isEmpty(text)) {
leftDrawable?.let { drlft ->
if (lineCount == 1) {
setCompoundDrawablesWithIntrinsicBounds(drlft, null, null, null)
} else {
val buttonWidth = drlft.intrinsicWidth
val buttonHeight = drlft.intrinsicHeight
val topSpace = abs(buttonHeight - lineHeight) / 2
drlft.setBounds(0, topSpace, buttonWidth, topSpace + buttonHeight)
canvas?.apply {
save()
drlft.draw(canvas)
restore()
}
}
}
}
}
}

If you want to resolve it using compound drawable, you should create a custom textview and use it.
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
class TopAlignCompoundDrawableTextView: AppCompatTextView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
context,
attrs,
defStyle
)
override fun dispatchDraw(canvas: Canvas?) {
super.dispatchDraw(canvas)
val centerTextView = height / 2
val drawableLeft = compoundDrawables[0]
drawableLeft?.let {
val topDrawable = it.intrinsicHeight/2 - centerTextView
it.setBounds(
0,
topDrawable,
it.intrinsicWidth,
it.intrinsicHeight + topDrawable
)
}
val drawableRight = compoundDrawables[2]
drawableRight?.let {
val topDrawable = it.intrinsicHeight/2 - centerTextView
it.setBounds(
0,
topDrawable,
it.intrinsicWidth,
it.intrinsicHeight + topDrawable
)
}
}
}

Related

Why does my extended EditText not respond like an EditText?

I created a class that extends EditText and forces them into a square shape with a border. I cannot click on nor edit the text in any of these. When I change the 4 SquareCell views back to EditText views, I am able to edit them (but obviously lose the square format). Why can't I edit SquareCell views?
SquareCell class
package com.example.autogrid
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatEditText
class SquareCell #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatEditText(context, attrs, defStyleAttr) {
private var squareSize = 0f
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLACK
style = Paint.Style.STROKE
strokeWidth = 5f
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//find smaller dimension between width and height and set layout size to it
val smallerDim = Math.min(
MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec)
)
super.onMeasure(
MeasureSpec.makeMeasureSpec(smallerDim, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(smallerDim, MeasureSpec.EXACTLY)
)
squareSize = smallerDim.toFloat()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//add border
canvas.drawRect(0f, 0f, squareSize, squareSize, paint)
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.example.autogrid.SquareCell
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Edit Text 00"/>
<com.example.autogrid.SquareCell
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Edit Text 01"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.example.autogrid.SquareCell
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Edit Text 10"/>
<com.example.autogrid.SquareCell
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Edit Text 11"/>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Result with SquareCells (not editable):
Result when I use EditTexts (editable but not squares):
Actually, you don't need to use that library you can archive these things throw a custom shape, and set that background to editText.
Here is the shape code for reference
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF"/>
<stroke android:width="1dp"android:color="#F0F0F0" />
</shape>

Why is the size applied to the custom view not reflected?

The code at the bottom is a custom button code. (custom_button_view.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/yellow"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:id="#+id/symbol"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:src="#android:drawable/ic_delete" />
<TextView
android:id="#+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:text="my custom button view"
android:textStyle="bold" />
</LinearLayout>
You can clearly see that the height of the custom button is set to wrap_content. And I set the custom button in MainActivity.
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.ez.customview.CustomButton
android:id="#+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
app:back="#android:color/holo_orange_light"
app:sym="#android:drawable/ic_dialog_alert"
app:text="This is Custom Button view" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:text="TEST BUTTON" />
</LinearLayout>
But in real activity, it doesn't applied custom button view height size. Why this happen? And how to I get fix??
[Edit]
Here is CustomButton class.
class CustomButton : LinearLayout {
private var bg: LinearLayout? = null
private var symbol: ImageView? = null
private var text: TextView? = null
constructor(context: Context) : super(context) {
initView(context)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
initView(context)
getAttrs(attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleInt: Int) : super(context, attrs, defStyleInt) {
initView(context)
getAttrs(attrs, defStyleInt)
}
private fun initView(context: Context) {
val v = View.inflate(context, R.layout.custom_button_view, this)
val view = (context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(R.layout.custom_button_view, this, false)
addView(view)
bg = findViewById(R.id.bg)
symbol = findViewById(R.id.symbol)
text = findViewById(R.id.text)
}
private fun getAttrs(attrs: AttributeSet) {
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.CustomButton, 0, 0)
setTypeArray(typedArray)
}
private fun getAttrs(attrs: AttributeSet, defStyle: Int) {
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.CustomButton, defStyle, 0)
setTypeArray(typedArray)
}
private fun setTypeArray(array: TypedArray) {
try {
bg?.setBackgroundResource(array.getResourceId(R.styleable.CustomButton_back, android.R.color.holo_blue_bright))
symbol?.setImageResource(array.getResourceId(R.styleable.CustomButton_sym, android.R.drawable.ic_dialog_email))
text?.setTextColor(array.getColor(R.styleable.CustomButton_textColor, ContextCompat.getColor(context, android.R.color.black)))
text?.text = array.getString(R.styleable.CustomButton_text)
} finally {
array.recycle()
}
}
}
I think it could be caused by this line:
symbol?.setImageResource(array.getResourceId(R.styleable.CustomButton_sym, android.R.drawable.ic_dialog_email))You set the image as ic_dialog_email, so your custom button is the same size as that image.
I'm not sure though

Android how to draw shape centered in view

I have multiple instances of a custom view where I'm trying to draw custom shapes using the center values from the view. The problem is that I've been unsuccessful when trying to get the center and drawing shapes on it, here is the result from the code below, you can see that the black squares are not properly centered in each one of the 4 squares:
class MyCustomView
constructor(context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
private var center = PointF(0f, 0f)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
center.set(w / 2f, h / 2f)
}
override fun onDraw(canvas: Canvas) {
val squareDimension = 100F
val halfDimension = squareDimension / 2
if (center.x == 0F || center.y == 0F) {
center = PointF(width / 2F, height / 2F)
}
val paint = Paint()
paint.color = Color.rgb(
0,
0,
0)
canvas.drawRect(
center.x - halfDimension,
center.y - halfDimension,
center.x + halfDimension,
center.y + halfDimension,
paint)
}
}
Android my layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:id="#+id/first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="#id/second"
app:layout_constraintBottom_toTopOf="#id/third"
android:background="#color/colorPrimary">
<MyCustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="#+id/second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="#id/first"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="#id/second"
android:background="#color/colorAccent">
<MyCustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="#+id/third"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="#id/fourth"
app:layout_constraintTop_toBottomOf="#id/first"
android:background="#color/colorAccent">
<MyCustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="#+id/fourth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/third"
app:layout_constraintTop_toBottomOf="#id/second"
android:background="#color/colorPrimary"
>
<MyCustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
How can I draw the shape properly in the center of each view?
If you look at your views, MyCustomView and its parent are set to wrap_content. It is also the LinearLayouts whose bounds you see in the screen. If you inspect your views, it's likely that MyCustomViews are not actually where you expect.
Your options:
Make LinearLayout widths and heights 0dp (though from the constraints this isn't required) and make MyCustomView widths and heights match_parent.
Remove LinearLayouts altogether and replace them with MyCustomView. The parent is currently redundant, though you may have a use for it later on.
In terms of your class, things look good, though I would have a single pointf only (val), create the paint globally and not on each draw, and avoid mutating the point during onDraw. You can optionally compute a RectF during onSizeChanged and avoid any further computation during onDraw

textview remove space after line break

I'm trying to make a chat card. How to remove space (right of green line) on TextView after line break?
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/m"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="102dp"
android:background="#color/red_700"
android:padding="8dp"
android:text="Teaasdasdasdasdasdasdas dasdasdasdasdasdasdas dasasdasdasdasdasdasdasdxt"/>
You have to calculate width manually. So just create your own TextView
import android.content.Context;
import android.text.Layout;
import android.util.AttributeSet;
public class NewTextView extends android.support.v7.widget.AppCompatTextView {
public NewTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = (int) Math.ceil(getMaxLineWidth(getLayout()));
int height = getMeasuredHeight();
setMeasuredDimension(width, height);
}
private float getMaxLineWidth(Layout layout) {
float maximumWidth = 0.0f;
int lines = layout.getLineCount();
for (int i = 0; i < lines; i++) {
maximumWidth = Math.max(layout.getLineWidth(i), maximumWidth);
}
return maximumWidth;
}
}
and then use it in your xml
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.xxx.NewTextView
android:id="#+id/m"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="102dp"
android:background="#color/colorPrimary"
android:padding="8dp"
android:text="Teaasdasdasdasdasdasdas dasdasdasdasdasdasdas dasasdasdasdasdasdasdasdxt"/>
</FrameLayout>
Top Answer in Kotlin. Left and right paddings are needed otherwise the content appears cropped.
class TrimmedTextView(
context: Context,
attrs: AttributeSet
) : AppCompatTextView(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
var maxWidth = ceil(getMaxLineWidth(layout)).toInt()
maxWidth += paddingRight + paddingLeft
setMeasuredDimension(maxWidth, measuredHeight)
}
private fun getMaxLineWidth(layout: Layout): Float {
var maximumWidth = 0.0f
val lines = layout.lineCount
for (i in 0 until lines) {
maximumWidth = max(layout.getLineWidth(i), maximumWidth)
}
return maximumWidth
}
}
You can try this
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="2"
>
<TextView
android:id="#+id/m"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:background="#android:color/holo_red_dark"
android:padding="8dp"
android:text="Teaasdasdasdasdasdasdas dasdasdasdasdasdasdas dasasdasdasdasdasdasdasdxt"
/>
</LinearLayout>
</FrameLayout>
Official solution from Android docs:
Add dependencies:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:27.0.0'
compile 'com.android.support:percent:27.0.0'
}
And change layout content to this:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.AppCompatTextView
android:id="#+id/m"
android:layout_height="wrap_content"
android:background="#color/red_700"
android:padding="8dp"
app:layout_widthPercent="50%"
android:text="Teaasdasdasdasdasdasdas dasdasdasdasdasdasdas dasasdasdasdasdasdasdasdxt"/>
</FrameLayout>

Whatsapp Message Layout - How to get time-view in the same row

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

Categories

Resources