I'm following the example given by max4ever where he showed how to achieve what I'm looking for. I create a custom EditText class:
public class LinedEditText extends AppCompatEditText {
private Rect mRect;
private Paint mPaint;
// we need this constructor for LayoutInflater
public LinedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setColor(getResources().getColor(R.color.black)); //SET YOUR OWN COLOR HERE
}
#Override
protected void onDraw(Canvas canvas) {
//int count = getLineCount();
int height = getHeight();
int line_height = getLineHeight();
int count = height / line_height;
if (getLineCount() > count)
count = getLineCount();//for long text with scrolling
Rect r = mRect;
Paint paint = mPaint;
int baseline = getLineBounds(0, r);//first line
for (int i = 0; i < count; i++) {
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
baseline += getLineHeight();//next line
}
super.onDraw(canvas);
}
}
Then I'm adding it to my layout .xml like this:
<?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="match_parent"
tools:context="myapp.com.test.NotesLayout">
<myapp.com.test.LinedEditText
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
The problem I'm having is that the EditText only starts in the middle of the view like this:
So, my question is, how can I adjust this custom EditText so that it shows on top/from the start of the view?
HOW I FIXED IT
I changed my xml to this
<myapp.com.test.LinedEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#null"
android:imeOptions="actionNone"
android:gravity="top"
/>
Setting android:gravity="top" is why it was starting in the middle of the view. Thank you for all the contributions.
Set
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
in your custom Edittext in your layout file.This will yield the desired result.
I'm trying to draw lines in edittext. I have a scrollview inside which is editText.When number of lines in the editText is less then the total possible line that can be accommodated in view, I still want to draw the left lines after already drawn lines for text present in editText.
For example suppose if the no of lines in the edittext are 3 then only 3 lines are drawn .But I want to draw lines throughout the view in this case.
I've written below code but it is only drawing line upto height of text not beyond that.It will be a great help if anyone could help it .
public class DisplayActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_display);
final TextView textView_header = (TextView) findViewById(R.id.header);
final String randomText = getIntent().getExtras().getString("Data");
int index = Integer.parseInt(randomText);
int pos = MainActivity.keyList.get(index);
final String text = MainActivity.settings.getString("data" + pos, "");
final String text_2 = MainActivity.settings_2.getString("data" + pos,
"");
textView_header.setText(text);
LayoutParams textViewLayoutParams = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
LinearLayout ll = (LinearLayout) findViewById(R.id.editText_layout);
ScrollView sv = new ScrollView(this);
sv.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
LinedEditText et = new LinedEditText(this, null);
et.setLayoutParams(textViewLayoutParams);
et.setText(text_2);
et.setKeyListener(null);
et.setEnabled(false);
et.setTextColor(Color.BLACK);
et.setBackgroundColor(Color.TRANSPARENT);
sv.addView(et);
ll.addView(sv);
}
public class LinedEditText extends EditText {
private Rect mRect;
private Paint mPaint;
public LinedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.GRAY);
}
#Override
protected void onDraw(Canvas canvas) {
int height = getHeight();
int line_height = getLineHeight();
int count = height / line_height;
if (getLineCount() > count)
count = getLineCount();// for long text with scrolling
Rect r = mRect;
Paint paint = mPaint;
int baseline = getLineBounds(0, r);// first line
for (int i = 0; i < count; i++) {
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1,
paint);
baseline += getLineHeight();// next line
}
super.onDraw(canvas);
}
}
}
This is my xml layout for the activity
<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="match_parent"
android:background="#drawable/page2"
tools:context="com.example.writenote.DisplayActivity" >
<TextView
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="140px" />
<LinearLayout
android:id="#+id/editText_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="#+id/line"
android:layout_marginTop="0px"
android:orientation="vertical" />
<TextView
android:id="#+id/line"
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_below="#+id/header"
android:layout_marginLeft="20px"
android:layout_marginRight="5px"
android:layout_marginTop="10dp"
android:background="#a9a9a9" />
<TextView
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/textView"
android:layout_marginLeft="30px"
android:layout_marginRight="25px"
android:layout_marginTop="30px"
android:text="hello"
android:textColor="#000"
android:textSize="60px" />
<ImageView
android:id="#+id/imageView1"
android:layout_width="260px"
android:layout_height="260px"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:src="#drawable/circleedit" />
</RelativeLayout>
I think the problem is in getHeight() which is in onDraw. It is returning height of editext which consists of three lines. Is there any way to provide the height of linearlayout which encloses edittext? I tried to provide height of linearlayout in onDraw method but there is no effect it's still drawing 3 lines.
I tried to do this in onDraw method
//ll is linearLayout
int height = ll.getHeight();
Your problem comes from the ScrollView, this ViewGroup is made to contain variable content size and add a scrollbar if this content is bigger than the provided space in the layout, thus you cannot use the match_parent configuration inside a ScrollView (your EditText will automatically be set to wrap_content on its height).
You simply have to call sv.fillViewPort(true); in your onCreate to stretch the content if smaller than the ScrollView height, as stated in the android documentation.
<EditText
android:id="#+id/editText"
android:width="wrap_content"
android:height="wrap_content"
android:maxLines="2"/>
maxLines number is your required lines count;
you want multiple lines in edit text ....set edit text width is fixed and height is warap_content then it automaticall enter new line....
I have use custom EditText like notepad from the link Custom EditText
The text entered on the custom EditText doesn't appears correctly on the lines it contains . It some time appears on the line or below the line , unexpected behaviour
My output Click here !
Required output Click here !
Please help me in this case .
LineEditText.java
public class LinedEditText extends EditText {
private Rect mRect;
private Paint mPaint;
// we need this constructor for LayoutInflater
public LinedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setColor(Color.parseColor("#C0C0C0")); //SET YOUR OWN COLOR HERE
}
#Override
protected void onDraw(Canvas canvas) {
//int count = getLineCount();
int height = getHeight();
int line_height = getLineHeight();
int count = height / line_height;
if (getLineCount() > count)
count = getLineCount();//for long text with scrolling
Rect r = mRect;
Paint paint = mPaint;
int baseline = getLineBounds(0, r);//first line
for (int i = 0; i < count; i++) {
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
baseline += getLineHeight();//next line
}
super.onDraw(canvas);
}
}
layout.xml
<RelativeLayout android:id="#+id/rel_edit_story"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/txt_image"
android:padding="10dip"
android:layout_margin="10dip"
android:background="#drawable/relative_lined_edittext_border">
<com.rb.lined.edittext.LinedEditText
android:id="#+id/edit_story"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#null"
android:inputType="textMultiLine|textNoSuggestions"
android:minLines="10"
android:singleLine="false"
android:imeOptions="actionNone"
android:text="Story : " />
</RelativeLayout>
For Desabeling Red underline you can put like
android:inputType="textNoSuggestions"
or
android:inputType="textVisiblePassword"
and for removing blue line this may help.
android:background="#null"
Example
<com.example.lineedittextdemo.LineEditText
android:id="#+id/edit_story"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:background="#null"
android:inputType="textMultiLine|textNoSuggestions"
android:minLines="10"
android:singleLine="false"
android:imeOptions="actionNone"
android:text="Story : \n" />
Here is my layout:
The issue I'm facing is with the drawable checkmark. How would I go about aligning it next to the text, both of them centered within the button? Here is the XML:
<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="match_parent"
tools:context=".PostAssignmentActivity" >
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal" >
<Button
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableLeft="#drawable/ic_checkmark_holo_light"
android:text="Post" />
<Button
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cancel" />
</LinearLayout>
</RelativeLayout>
Applying android:gravity="center_vertical" pulls the text and drawable together, but then the text is no longer aligned in the center.
Solution 1
Set android:paddingLeft inside your first button. This will force the drawableLeft by paddingLeft amount to the right. This is the fast/hacky solution.
Solution 2
Instead of using a ButtonView, use a LinearLayout that contains both a textview and imageview. This is a better solution. It gives you more flexibility in the positioning of the checkmark.
Replace your ButtonView with the following code. You need the LinearLayout and TextView to use buttonBarButtonStyle so that the background colors are correct on selection and the text size is correct. You need to set android:background="#0000" for the children, so that only the LinearLayout handles the background coloring.
<LinearLayout
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal" >
<ImageView
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:background="#0000"
android:src="#drawable/ic_checkmark_holo_light"/>
<TextView
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:background="#0000"
android:text="Done" />
</LinearLayout>
Here are some screenshots I took while trying this out.
None of these solutions worked correctly without presenting unacceptable trade-offs (create a layout with views in it? Not a good idea). So why not roll your own? This is what I got:
First create an attrs.xml with this:
<resources>
<declare-styleable name="IconButton">
<attr name="iconSrc" format="reference" />
<attr name="iconSize" format="dimension" />
<attr name="iconPadding" format="dimension" />
</declare-styleable>
</resources>
This allows to create an icon with specific size, padding from text, and image in our new view. The view code looks like this:
public class IconButton extends Button {
private Bitmap mIcon;
private Paint mPaint;
private Rect mSrcRect;
private int mIconPadding;
private int mIconSize;
public IconButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
public IconButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public IconButton(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
int shift = (mIconSize + mIconPadding) / 2;
canvas.save();
canvas.translate(shift, 0);
super.onDraw(canvas);
if (mIcon != null) {
float textWidth = getPaint().measureText((String)getText());
int left = (int)((getWidth() / 2f) - (textWidth / 2f) - mIconSize - mIconPadding);
int top = getHeight()/2 - mIconSize/2;
Rect destRect = new Rect(left, top, left + mIconSize, top + mIconSize);
canvas.drawBitmap(mIcon, mSrcRect, destRect, mPaint);
}
canvas.restore();
}
private void init(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.IconButton);
for (int i = 0; i < array.getIndexCount(); ++i) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.IconButton_iconSrc:
mIcon = drawableToBitmap(array.getDrawable(attr));
break;
case R.styleable.IconButton_iconPadding:
mIconPadding = array.getDimensionPixelSize(attr, 0);
break;
case R.styleable.IconButton_iconSize:
mIconSize = array.getDimensionPixelSize(attr, 0);
break;
default:
break;
}
}
array.recycle();
//If we didn't supply an icon in the XML
if(mIcon != null){
mPaint = new Paint();
mSrcRect = new Rect(0, 0, mIcon.getWidth(), mIcon.getHeight());
}
}
public static Bitmap drawableToBitmap (Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable)drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
And then it can be used like this:
<com.example.grennis.myapplication.IconButton
android:layout_width="200dp"
android:layout_height="64dp"
android:text="Delete"
app:iconSrc="#android:drawable/ic_delete"
app:iconSize="32dp"
app:iconPadding="6dp" />
This works for me.
You can use
<com.google.android.material.button.MaterialButton/> .
https://material.io/develop/android/components/material-button/
It finally allows setting the icon gravity.
<com.google.android.material.button.MaterialButton
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:gravity="center"
android:text="Awesome button"
app:icon="#drawable/your_icon"
app:iconGravity="textStart" />
Here is a clean easy way, without doing anything fancy, to achieve the results of having a Button that is much wider than the content with Image and Text which are centered.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:background="#drawable/button_background_selector">
<Button
android:layout_centerInParent="true"
android:gravity="center"
android:duplicateParentState="true"
android:layout_width="wrap_content"
android:text="New User"
android:textSize="15sp"
android:id="#android:id/button1"
android:textColor="#android:color/white"
android:drawablePadding="6dp"
android:drawableLeft="#drawable/add_round_border_32x32"
android:layout_height="64dp" />
</RelativeLayout>
In our case, we wanted to use the default Button class (to inherit its various styles and behaviors) and we needed to be able to create the button in code. Also, in our case we could have text, an icon (left drawable), or both.
The goal was to center the icon and/or text as a group when the button width was wider than wrap_content.
public class CenteredButton extends Button
{
public CenteredButton(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
// We always want our icon and/or text grouped and centered. We have to left align the text to
// the (possible) left drawable in order to then be able to center them in our onDraw() below.
//
setGravity(Gravity.LEFT|Gravity.CENTER_VERTICAL);
}
#Override
protected void onDraw(Canvas canvas)
{
// We want the icon and/or text grouped together and centered as a group.
// We need to accommodate any existing padding
//
float buttonContentWidth = getWidth() - getPaddingLeft() - getPaddingRight();
// In later versions of Android, an "all caps" transform is applied to buttons. We need to get
// the transformed text in order to measure it.
//
TransformationMethod method = getTransformationMethod();
String buttonText = ((method != null) ? method.getTransformation(getText(), this) : getText()).toString();
float textWidth = getPaint().measureText(buttonText);
// Compute left drawable width, if any
//
Drawable[] drawables = getCompoundDrawables();
Drawable drawableLeft = drawables[0];
int drawableWidth = (drawableLeft != null) ? drawableLeft.getIntrinsicWidth() : 0;
// We only count the drawable padding if there is both an icon and text
//
int drawablePadding = ((textWidth > 0) && (drawableLeft != null)) ? getCompoundDrawablePadding() : 0;
// Adjust contents to center
//
float bodyWidth = textWidth + drawableWidth + drawablePadding;
canvas.translate((buttonContentWidth - bodyWidth) / 2, 0);
super.onDraw(canvas);
}
}
Here is my code and working perfect.
<Button
android:id="#+id/button"
android:layout_width="200dp"
android:layout_height="50dp"
android:layout_gravity="center"
android:background="#drawable/green_btn_selector"
android:gravity="left|center_vertical"
android:paddingLeft="50dp"
android:drawableLeft="#drawable/plus"
android:drawablePadding="5dp"
android:text="#string/create_iou"
android:textColor="#color/white" />
public class DrawableCenterTextView extends TextView {
public DrawableCenterTextView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public DrawableCenterTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public DrawableCenterTextView(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
Drawable[] drawables = getCompoundDrawables();
if (drawables != null) {
Drawable drawableLeft = drawables[0];
Drawable drawableRight = drawables[2];
if (drawableLeft != null || drawableRight != null) {
float textWidth = getPaint().measureText(getText().toString());
int drawablePadding = getCompoundDrawablePadding();
int drawableWidth = 0;
if (drawableLeft != null)
drawableWidth = drawableLeft.getIntrinsicWidth();
else if (drawableRight != null) {
drawableWidth = drawableRight.getIntrinsicWidth();
}
float bodyWidth = textWidth + drawableWidth + drawablePadding;
canvas.translate((getWidth() - bodyWidth) / 2, 0);
}
}
super.onDraw(canvas);
}
}
This is now available in the Material Button by default with the app:iconGravity property. However, the Material Button does not allow for setting the background to a drawable (RIP gradients).
I converted the answers by #BobDickinson and #David-Medenjak above to kotlin and it works great.
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.Gravity
import androidx.appcompat.widget.AppCompatButton
import kotlin.math.max
class CenteredButton #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = R.attr.buttonStyle
) : AppCompatButton(context, attrs, defStyle) {
init {
gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
}
override fun onDraw(canvas: Canvas) {
val buttonContentWidth = (width - paddingLeft - paddingRight).toFloat()
var textWidth = 0f
layout?.let {
for (i in 0 until layout.lineCount) {
textWidth = max(textWidth, layout.getLineRight(i))
}
}
val drawableLeft = compoundDrawables[0]
val drawableWidth = drawableLeft?.intrinsicWidth ?: 0
val drawablePadding = if (textWidth > 0 && drawableLeft != null) compoundDrawablePadding else 0
val bodyWidth = textWidth + drawableWidth.toFloat() + drawablePadding.toFloat()
canvas.save()
canvas.translate((buttonContentWidth - bodyWidth) / 2, 0f)
super.onDraw(canvas)
canvas.restore()
}
}
I know it's a bit late, but if anyone looking for another answer, here is another way to add icon without the need to wrap button with a ViewGroup
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
tools:context=".MainActivity">
<Button
android:id="#+id/btnCamera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Click!"
android:textAllCaps="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
*need to set textAllCaps to false to make the spannable working
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val buttonLabelBuilder = SpannableStringBuilder(btnCamera.text)
val iconDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_camera)
iconDrawable?.setBounds(0, 0, btnCamera.lineHeight, btnCamera.lineHeight)
val imageSpan = ImageSpan(iconDrawable, ImageSpan.ALIGN_BOTTOM)
buttonLabelBuilder.insert(0, "i ")
buttonLabelBuilder.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
btnCamera.text = buttonLabelBuilder
}
}
I started with #BobDickinson's answer, but it did not cope well with multiple lines. The approach is good, because you still end up with a Button that can properly be reused.
Here is an adapted solution that will also work if the button has multiple lines (Please don't ask why.)
Just extend Button and use the following in onDraw, the getLineRight() is used to look up the actual length of each line.
#Override
protected void onDraw(Canvas canvas) {
// We want the icon and/or text grouped together and centered as a group.
// We need to accommodate any existing padding
final float buttonContentWidth = getWidth() - getPaddingLeft() - getPaddingRight();
float textWidth = 0f;
final Layout layout = getLayout();
if (layout != null) {
for (int i = 0; i < layout.getLineCount(); i++) {
textWidth = Math.max(textWidth, layout.getLineRight(i));
}
}
// Compute left drawable width, if any
Drawable[] drawables = getCompoundDrawables();
Drawable drawableLeft = drawables[0];
int drawableWidth = (drawableLeft != null) ? drawableLeft.getIntrinsicWidth() : 0;
// We only count the drawable padding if there is both an icon and text
int drawablePadding = ((textWidth > 0) && (drawableLeft != null)) ? getCompoundDrawablePadding() : 0;
// Adjust contents to center
float bodyWidth = textWidth + drawableWidth + drawablePadding;
canvas.save();
canvas.translate((buttonContentWidth - bodyWidth) / 2, 0);
super.onDraw(canvas);
canvas.restore();
}
Here is a another solution:
<LinearLayout
android:id="#+id/llButton"
android:layout_width="match_parent"
style="#style/button_celeste"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
style="#style/button_celeste"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="10dp"
android:clickable="false"
android:drawableLeft="#drawable/icon_phone"
android:text="#string/call_runid"/>
</LinearLayout>
and the event:
LinearLayout btnCall = (LinearLayout) findViewById(R.id.llButton);
btnCall.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
call(runid.Phone);
}
});
I had the same issue, and I've come up with a solution that doesn't require XML changes or custom Views.
This code snippet retrieves the width of the text and the left/right drawables, and sets the Button's left/right padding so there will only be enough space to draw the text and the drawables, and no more padding will be added.
This can be applied to Buttons as well as TextViews, their superclasses.
public class TextViewUtils {
private static final int[] LEFT_RIGHT_DRAWABLES = new int[]{0, 2};
public static void setPaddingForCompoundDrawableNextToText(final TextView textView) {
ViewTreeObserver vto = textView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
shinkRoomForHorizontalSpace(textView);
}
});
}
private static void shinkRoomForHorizontalSpace(TextView textView) {
int textWidth = getTextWidth(textView);
int sideCompoundDrawablesWidth = getSideCompoundDrawablesWidth(textView);
int contentWidth = textWidth + sideCompoundDrawablesWidth;
int innerWidth = getInnerWidth(textView);
int totalPadding = innerWidth - contentWidth;
textView.setPadding(totalPadding / 2, 0, totalPadding / 2, 0);
}
private static int getTextWidth(TextView textView) {
String text = textView.getText().toString();
Paint textPaint = textView.getPaint();
Rect bounds = new Rect();
textPaint.getTextBounds(text, 0, text.length(), bounds);
return bounds.width();
}
private static int getSideCompoundDrawablesWidth(TextView textView) {
int sideCompoundDrawablesWidth = 0;
Drawable[] drawables = textView.getCompoundDrawables();
for (int drawableIndex : LEFT_RIGHT_DRAWABLES) {
Drawable drawable = drawables[drawableIndex];
if (drawable == null)
continue;
int width = drawable.getBounds().width();
sideCompoundDrawablesWidth += width;
}
return sideCompoundDrawablesWidth;
}
private static int getInnerWidth(TextView textView) {
Rect backgroundPadding = new Rect();
textView.getBackground().getPadding(backgroundPadding);
return textView.getWidth() - backgroundPadding.left - backgroundPadding.right;
}
}
Notice that:
It actually still leaves some more space than needed (good enough for my purposes, but you may look for the error)
It overwrites whatever left/right padding is there. I guess it's not difficult to fix that.
To use it, just call TextViewUtils.setPaddingForCompoundDrawableNextToText(button) on your onCreate or onViewCreated().
There are several solutions to this problem. Perhaps the easiest on some devices is to use paddingRight and paddingLeft to move the image and text next to each other as below.
Original button
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="16dp"
android:text="#string/scan_qr_code"
android:textColor="#color/colorPrimary"
android:drawableLeft="#drawable/ic_camera"
android:paddingRight="90dp"
android:paddingLeft="90dp"
android:gravity="center"
/>
The problem here is on smaller devices this padding can cause unfortunate problems such as this:
The other solutions are all some version of "build a button out of a layout an image and a textview". They work, but completely emulating a button can be tricky. I propose one more solution; "build a button out of a layout an image, a textview, and a button"
Here's the same button rendered as I propose:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="16dp"
android:gravity="center"
>
<Button
android:id="#+id/scanQR"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/white_bg_button"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_centerInParent="true"
android:gravity="center"
android:elevation="10dp"
>
<ImageView
android:id="#+id/scanImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:src="#drawable/ic_camera"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="#style/Base.TextAppearance.AppCompat.Button"
android:text="#string/scan_qr_code"
android:textColor="#color/colorPrimary"
/>
</LinearLayout>
</RelativeLayout>
As you can see, the button is now within a relative layout, but it's text and drawableLeft are not part of the button, they are in a separate layout that's placed on top of the button. With this, the button still acts like a button. The gotchas are:
The inner layout needs an elevation for newer versions of Android. The button itself has an elevation greater than the ImageView and TextView, so even though they are defined after the Button, they will still be "below" it in elevation and be invisible. Setting 'android:elevation' to 10 solves this.
The textAppearance of the TextView must be set so that it has the same appearance as it would in a button.
Another quite hacky alternative is to add blank spacer views with weight="1" on each side of the buttons. I don't know how this would affect performance.
<View
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" />
I'm trying to Customize button with two TextView with different typeface within a single button. For that I just extended Button and with have written the following code inside the constructor,
LayoutInflater layoutInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = layoutInflater.inflate(R.layout.custom_button,
(ViewGroup) findViewById(R.id.custom_button_view));
TextView firstTextView = (TextView) layout
.findViewById(R.id.firstTextView);
TextView secondTextView = (TextView) layout
.findViewById(R.id.secondTextView);
in the layout custom_button I have placed two TextView with different typeface and text and custom_button_view is the ID of that LinearLayout, what I got is an empty button with no text.
Any Ideas, Thanks.
You can use Layout as a button by setting ur custom button style to layout and can add two textViews to it, in this way:
<LinearLayout android:id="#+id/customButtonLayout"
android:layout_height="wrap_content" style="#android:style/Widget.Button"
android:layout_width="wrap_content">
<TextView android:text="First" android:id="#+id/firstTextView"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textColor="#000"></TextView>
<TextView android:textColor="#000" android:text="Second"
android:layout_height="wrap_content" android:id="#+id/secondTextView"
android:layout_width="wrap_content" android:layout_marginLeft="10dp"></TextView>
</LinearLayout>
and in Activity you can have this to set different typeface:
Typeface font=Typeface.createFromAsset(getAssets(),"ARIALN.TTF") ;
Typeface font2=Typeface.createFromAsset(getAssets(), "COMPCTAN.TTF");
TextView firstTextView = (TextView)findViewById(R.id.firstTextView);
TextView secondTextView = (TextView)findViewById(R.id.secondTextView);
firstTextView.setTypeface(font);
secondTextView.setTypeface(font2);
LinearLayout btnLayout=(LinearLayout) findViewById(R.id.customButtonLayout);
btnLayout.setOnClickListener(this);
You can derive a new class from Button and override the onDraw(Canvas canvas) method. I did it for a button with an icon and a text, and it works without any xml. The main problem will be to write the text at the good place on the button. For this you can use the Paint.getTextBounds() function to get the text dimensions.
Using a LayoutInflater is probably a better practice, but I didn't manage to make it work.
public class CustomButton extends Button {
private int mWidth;
private int mHeight;
private Bitmap mBitmap;
private String mText;
private Paint mPaintIcon;
private Rect mRectIconSrc;
private Rect mRectIconDst;
private Paint mPaintText;
public CustomButton(Context context, Bitmap bitmap, int width, int height, String text) {
super(context);
mBitmap = bitmap;
mWidth = width;
mHeight = height;
mText = text;
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
setLayoutParams(params);
mPaintIcon = new Paint();
mRectIconSrc = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
mRectIconDst = new Rect(0, 0, mHeight, mHeight);
mPaintText = new Paint();
mPaintText.setColor(0xFF778800);
mPaintText.setTextSize(30);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mBitmap, mRectIconSrc, mRectIconDst, mPaintIcon);
canvas.drawText(mText, mWidth/4, mHeight*2/3, mPaintText);
}
}
You can surround the button with a FrameLayout and then add a textview within the FrameLayout. You can manage the typeface in the activity. If the text doesn't show try using bringToFront()
layout:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:id="#+id/button_frame"
>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/button_border"
android:gravity="center"
android:onClick="onClick"
android:text="#string/get_more"
android:id="#+id/get_more"
android:stateListAnimator="#null"
/>
<TextView
android:id="#+id/linearTimer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="right"
android:padding="10dp"
android:text="123"
>
</FrameLayout>
Activity:
countDownView = (TextView) findViewById(R.id.linearTimer);
Typeface tf = Typeface.createFromAsset(getAssets(), "fonts/digital-7.ttf");
countDownView.setTypeface(tf);
countDownView.bringToFront();