Aligning drawableLeft with text of button

后端 未结 14 1971
长情又很酷
长情又很酷 2021-01-30 06:41

Here is my layout:

\"enter

The issue I\'m facing is with the drawable checkmark. H

相关标签:
14条回答
  • 2021-01-30 07:08

    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();
    }
    
    0 讨论(0)
  • 2021-01-30 07:11

    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:

    enter image description here

    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.

    0 讨论(0)
  • 2021-01-30 07:11

    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()
      }
    }
    
    0 讨论(0)
  • 2021-01-30 07:13

    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" />
    
    0 讨论(0)
  • 2021-01-30 07:18

    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>
    

    enter image description here

    0 讨论(0)
  • 2021-01-30 07:18

    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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题