56

这是我的布局:

在此处输入图像描述

我面临的问题是可绘制的复选标记。我将如何将它与文本对齐,它们都在按钮内居中?这是 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>

应用 android:gravity="center_vertical" 将文本和 drawable 拉到一起,但是文本不再居中对齐。

4

14 回答 14

76

解决方案 1

设置android:paddingLeft在您的第一个按钮内。这将迫使drawableLeftpaddingLeft数量向右。这是快速/hacky 的解决方案。

解决方案 2

不要使用 ButtonView,而是使用包含 textview 和 imageview 的 LinearLayout。这是一个更好的解决方案。它使您可以更灵活地定位复选标记。

将您的 ButtonView 替换为以下代码。您需要使用andLinearLayout以便选择时背景颜色正确且文本大小正确。您需要为孩子设置,以便只有 LinearLayout 处理背景颜色。TextViewbuttonBarButtonStyleandroid:background="#0000"

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

这是我在尝试时截取的一些屏幕截图。

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

于 2013-02-03T01:15:41.513 回答
36

如果不进行不可接受的权衡(创建包含视图的布局?不是一个好主意),这些解决方案都不能正常工作。那么为什么不自己动手呢?这就是我得到的:

在此处输入图像描述

首先用这个创建一个attrs.xml

<resources>
    <declare-styleable name="IconButton">
        <attr name="iconSrc" format="reference" />
        <attr name="iconSize" format="dimension" />
        <attr name="iconPadding" format="dimension" />
    </declare-styleable>
</resources>

这允许在我们的新视图中创建具有特定大小、文本填充和图像的图标。视图代码如下所示:

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;
    }
}

然后它可以像这样使用:

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

这对我有用。

于 2015-03-23T22:01:57.073 回答
19

这是一种干净简单的方法,无需做任何花哨的操作,即可获得比居中的 Image 和 Text 内容更宽的 Button 的结果。

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

在此处输入图像描述

于 2015-04-02T19:35:01.667 回答
17

您可以使用 <com.google.android.material.button.MaterialButton/>.
https://material.io/develop/android/components/material-button/

它最终允许设置图标重力。

 <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" />
于 2019-03-18T15:47:36.050 回答
11

在我们的例子中,我们想要使用默认的 Button 类(继承它的各种样式和行为)并且我们需要能够在代码中创建按钮。此外,在我们的例子中,我们可以有文本、图标(左侧可绘制),或两者兼而有之。

目标是在按钮宽度大于 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);
    }
}
于 2016-05-29T21:22:01.747 回答
8

这是我的代码并且工作完美。

<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" />
于 2014-05-08T06:41:13.847 回答
3
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);
    }
}
于 2013-12-09T13:08:52.853 回答
3

现在,默认情况下,该属性在材质按钮中可用。app:iconGravity但是,材质按钮不允许将背景设置为可绘制对象(RIP 渐变)。

我将上面@BobDickinson@David-Medenjak的答案转换为kotlin,效果很好。

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()
  }
}
于 2019-10-22T17:52:10.423 回答
2

我知道这有点晚了,但是如果有人在寻找另一个答案,这是另一种添加图标的方法,而无需使用 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>

*需要将 textAllCaps 设置为 false 以使 spannable 工作


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

带有图像跨度的按钮

于 2018-08-23T04:10:07.673 回答
1

我从@BobDickinson 的回答开始,但它不能很好地处理多行。这种方法很好,因为您最终仍然会得到一个Button可以正确重用的方法。

这是一个经过调整的解决方案,如果按钮有多行也可以使用(请不要问为什么。)

只需在 中扩展Button并使用以下内容onDrawgetLineRight()用于查找每行的实际长度。

@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();
}
于 2016-09-19T08:18:38.927 回答
0

我遇到了同样的问题,我想出了一个不需要 XML 更改或自定义视图的解决方案。

此代码片段检索文本和左/右可绘制对象的宽度,并设置按钮的左/右填充,因此只有足够的空间来绘制文本和可绘制对象,并且不会添加更多填充。这可以应用于 Buttons 以及 TextViews,它们的超类。

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;
    }
}

请注意:

  • 它实际上仍然留下了比需要更多的空间(对于我的目的来说已经足够了,但你可能会寻找错误)
  • 它会覆盖那里的任何左/右填充。我想解决这个问题并不难。

要使用它,只需调用TextViewUtils.setPaddingForCompoundDrawableNextToText(button)您的onCreateor onViewCreated()

于 2016-10-03T13:25:24.117 回答
0

这个问题有几种解决方案。也许在某些设备上最简单的方法是使用paddingRightpaddingLeft移动图像和文本,如下所示。

原始按钮

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

使用 Padding 可以工作

这里的问题是在较小的设备上,这种填充可能会导致不幸的问题,例如: 在此处输入图像描述

其他解决方案都是“从布局、图像和文本视图中构建按钮”的某个版本。它们可以工作,但完全模拟一个按钮可能会很棘手。我提出了另一种解决方案;“从一个图像、一个文本视图和一个按钮的布局中构建一个按钮”

这是我建议的相同按钮:

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

如您所见,按钮现在处于相对布局中,但它的 text 和 drawableLeft 不是按钮的一部分,它们位于按钮顶部的单独布局中。有了这个,按钮仍然像一个按钮。问题是:

  1. 对于较新版本的 Android,内部布局需要提升。按钮本身的海拔高于 ImageView 和 TextView,因此即使它们是在 Button之后定义的,它们仍然会在海拔“下方”并且不可见。将 'android:elevation' 设置为 10 可以解决此问题。
  2. 必须设置 TextView 的 textAppearance,使其具有与按钮相同的外观。
于 2017-09-18T23:34:42.907 回答
0

这是另一个解决方案:

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

和事件:

    LinearLayout btnCall = (LinearLayout) findViewById(R.id.llButton);
    btnCall.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            call(runid.Phone);
        }
    });
于 2015-08-26T06:09:52.200 回答
-4

另一个相当老套的选择是weight="1"在按钮的每一侧添加空白间隔视图。我不知道这会如何影响性能。

    <View
        android:layout_width="0dp"
        android:layout_height="fill_parent"
        android:layout_weight="1" />
于 2013-11-21T11:10:31.303 回答