Custom Button with two TextView - android

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();

Related

Drawing multiple line in edittext

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

Android ImageButton with background, source and text

I want to create ImageButton with custom background, custom Icon and text below that icon
What I have so far is
<ImageButton
android:id="#+id/imageButton1"
android:layout_width="140dp"
android:layout_height="120dp"
android:layout_marginLeft="3dp"
android:layout_marginBottom="6dp"
android:background="#drawable/btn_rectangle_background"
android:src="#drawable/ic_action_search"
/>
However, If I put there android:text="blablabla" it won't shows up :/
ic_action_homework is .PNG icon, but btn_rectangle_background is XML file, which defines shape
That's what I would like to achieve
1st answer:
Must be structure of layout likes here:
<LinearLayout
android:widht_layout="80dp"
android:height_layout="80dp"
android:padding="10dp"
android:gravity="center"
android:layout_gravity="center"
android:bacgkground="your_color ARGB"
>
<ImageView />
<TextView />
</LinearLayout>
or 2nd answer:
Create custom view
public class customView extends View{
public customView(Context context){
super(context);
}
public customView(Context context, String s, Drawable d){
super(context);
// Set Width&Height for this view
this.measure(80,80);
// or layout params with specified height&width for this view
Resources r = getResources();
int width = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, **Your width**,
r.getDisplayMetrics());
int height = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, **your height**,
r.getDisplayMetrics());
ViewGroup.LayoutParams pp = new ViewGroup.LayoutParams(width,height);
this.setLayoutParams(pp);
TextView _text = new TextView(context);
ImageView _image = new ImageView(context);
_text.setText(s);
_image.setBackground(d);
this.addView(_image);
this.addView(_text);
}
public customView(Context context, String s, Bitmap b){
....
_image.setImageBitmap(b);
...
}
}
also add view into root view #id=content of layout from activity:
findByView(R.id.content).addView(new customView((Context)this,"Your Text",getResources().getDrawable(R.drawable.icon));
or with parametr bitmap by path:
findByView(R.id.content).addView(new customView((Context)this,"Your Text",BitmapFactory.decodeFile("/sdcard/file.png"));

android : How to show a text inline another

Is there any solution to have 2 textview like this :
image
in fact i want to show Text2 lines below the Text1 lines. For exmple to show the translation Text1.
Any solution?
You can use setLineSpacing to set a big enough gap in your TextViews and overlap them, then translate it so one shows up in between the lines of the other.
Try this code.
You can set lineSpacingExtra property of TextView and can use relative layout to overlap them. After this you cna set properties of your text view accordingly
In your xml define text view properties like this
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
tools:context=".MainActivity" >
<TextView
android:id="#+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is Text 1. This is Text1. This is Text 1. This is Text1. This is Text 1. This is Text1"
android:lineSpacingExtra="20dp"
android:textColor="#5F4C0B"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="#+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="26dp"
android:lineSpacingExtra="26dp"
android:text="This is Text 2. This is Text2. This is Text2. This is Text2. This is Text2. This is Text2"
android:textColor="#B40404"
android:textSize="20sp" />
</RelativeLayout>
You will get output like this
Accept the answer if you found it useful
You can dynamically create this view,
In Main Activity write the following code
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RelativeLayout rl = new RelativeLayout(this);
RelativeLayout.LayoutParams text1 = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
text1.setMargins(0, 0, 0, 0);
RelativeLayout.LayoutParams text2 = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
text2.setMargins(0, 26, 0, 0);
LinesTextView tv1 = new LinesTextView(this, null);
tv1.setText("This is Text 1. This is Text 1. This is Text 1. This is Text 1. This is Text 1. This is Text 1.");
tv1.setLineSpacing(20.0f, 1.0f);
tv1.setLayoutParams(text1);
tv1.setTextSize(24.0f);
tv1.setTextColor(getResources().getColor(android.R.color.darker_gray));
LinesTextView tv2 = new LinesTextView(this, null);
tv2.setText("This is Text 2. This is Text 2. This is Text 2. This is Text 2. This is Text 2. This is Text 2.");
tv2.setLineSpacing(25.0f, 1.0f);
tv2.setLayoutParams(text2);
tv2.setTextSize(18.0f);
tv2.setTextColor(getResources().getColor(android.R.color.black));
rl.addView(tv1);
rl.addView(tv2);
this.setContentView(rl);
}
}
In LinesTextView class write the following code
public class LinesTextView extends TextView
{
private Rect mRect;
private Paint mPaint;
// we need this constructor for LayoutInflater
public LinesTextView(Context context, AttributeSet attrs) {
super(context, attrs);
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0x800000FF);
}
#Override
protected void onDraw(Canvas canvas) {
int count = getLineCount();
Rect r = mRect;
Paint paint = mPaint;
for (int i = 0; i < count; i++) {
int baseline = getLineBounds(i, r);
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
}
super.onDraw(canvas);
}
}
Check Screen shot
Set Text color, style, margin and size accordingly.

setPadding(0,0,0,0) called several times on View after constructor

Good evening! I'm trying to setPadding on a custom View i built and the native setPadding() did nothing so i wrote my own... After a while i realized that setPadding gets called several times after my original call and i have no idea why... Please help :) (I realize that my custom setPadding maybe quite excessive ^^)
Here is the XML containing the View. It's the PieChart.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/PieDialog_llParent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="#+id/PieDialog_tvHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Header"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="#+id/PieDialog_tvDiv1"
android:layout_width="match_parent"
android:layout_height="2dp"
android:textSize="0sp"/>
<TextView
android:id="#+id/PieDialog_tvDiv2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="0sp" />
<com.SverkerSbrg.Spendo.Statistics.Piechart.PieChart
android:id="#+id/PieDialog_Pie"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/PieDialog_tvDiv3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="0sp" />
<FrameLayout
android:id="#+id/PieDialog_flClose"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/PieDialog_tvClose"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Large Text" />
</FrameLayout>
</LinearLayout>
And here is the code where i use the xml:
package com.SverkerSbrg.Spendo.Transaction.TransactionList.PieDialog;
imports...
public class PieDialog extends SpendoDialog{
private TransactionSet transactionSet;
private TransactionGroup transactionGroup;
private GUI_attrs gui_attrs;
private PieData pieData;
private PieChart pie;
private TextView tvHeader;
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.transaction_list_pie_dialog, null);
LinearLayout llParent = (LinearLayout) view.findViewById(R.id.PieDialog_llParent);
llParent.setBackgroundColor(gui_attrs.color_Z0);
tvHeader = (TextView) view.findViewById(R.id.PieDialog_tvHeader);
tvHeader.setTextSize(gui_attrs.textSize_header);
TextView tvDiv1 = (TextView) view.findViewById(R.id.PieDialog_tvDiv1);
tvDiv1.setBackgroundColor(gui_attrs.color_Z2);
TextView tvDiv2 = (TextView) view.findViewById(R.id.PieDialog_tvDiv2);
tvDiv2.setPadding(0, gui_attrs.padding_Z0, 0, 0);
PieChart pie = (PieChart) view.findViewById(R.id.PieDialog_Pie);
pie.setPadding(40, 10, 40, 10);
builder.setView(view);
AlertDialog ad = builder.create();
return ad;
}
public void initialize(GUI_attrs gui_attrs, TransactionSet transactionSet, long groupIdentifier){
this.gui_attrs = gui_attrs;
this.transactionSet = transactionSet;
}
}
Just to extrapolate on my comment, it is your custom View object's responsibility to respect the padding that is set. You can do something like the following to make sure that you handle that case:
onMeasure()
int desiredWidth, desiredHeight;
desiredWidth = //Determine how much width you need
desiredWidth += getPaddingLeft() + getPaddingRight();
desiredHeight = //Determine how much height you need
desiredHeight += getPaddingTop() + getPaddingBottom();
int measuredHeight, measuredWidth;
//Check against the MeasureSpec -- if it's MeasureSpec.EXACTLY, or MeasureSpec.AT_MOST
//follow those restrictions to determine the measured dimension
setMeasuredDimension(measuredWidth, measuredHeight);
onLayout()
int leftOffset = getPaddingLeft();
int topOffset = getPaddingTop();
//layout your children (if any) according to the left and top offsets,
//rather than just 0, 0
onDraw()
canvas.translate (getPaddingLeft(), getPaddingTop());
//Now draw your stuff as normal

Aligning drawableLeft with text of button

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" />

Categories

Resources