将drawableLeft与按钮文本对齐

时间:2013-02-03 00:36:28

标签: android android-layout

这是我的布局:

enter image description here

我面临的问题是可绘制的复选标记。我如何在文本旁边对齐它们,它们都在按钮的中心?这是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”拉动文本并将其绘制在一起,但随后文本不再在中心对齐。

14 个答案:

答案 0 :(得分:62)

解决方案1 ​​

在第一个按钮内设置android:paddingLeft。这会强制drawableLeftpaddingLeft金额向右移动。这是快速/苛刻的解决方案。

解决方案2

使用包含textview和imageview的LinearLayout,而不是使用ButtonView。这是更好的解决方案。它为您提供了更大的选项标记定位灵活性。

使用以下代码替换ButtonView。您需要LinearLayoutTextView才能使用buttonBarButtonStyle,以便在选择时背景颜色正确且文字大小正确。您需要为子项设置android:background="#0000",以便只有LinearLayout处理背景着色。

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

以下是我尝试此操作时的一些截图。

enter image description here enter image description here enter image description here

答案 1 :(得分:31)

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

enter image description here

首先使用以下内容创建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" />

这适合我。

答案 2 :(得分:15)

这是一个干净简单的方法,没有做任何花哨的事情,以实现一个比图像和文本居中的内容宽得多的按钮的结果。

<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

答案 3 :(得分:9)

这是我的代码并且工作正常。

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

答案 4 :(得分:8)

在我们的例子中,我们想要使用默认的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);
    }
}

答案 5 :(得分: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);
    }
}

答案 6 :(得分:1)

我从@BobDickinson's answer开始,但它不能很好地应对多行。这种方法很好,因为你最终仍然可以正确地重用Button

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

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

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

答案 7 :(得分: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);
        }
    });

答案 8 :(得分:0)

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

此代码段检索文本和左/右drawable的宽度,并设置Button的左/右填充,以便只有足够的空间来绘制文本和drawable,并且不再添加填充。 这可以应用于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)onCreate上致电onViewCreated()

答案 9 :(得分: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"
    />

Using Padding can work

这里的问题是在较小的设备上,这种填充会导致不幸的问题,例如: enter image description here

其他解决方案都是&#34;在布局图像和文本视图中构建按钮&#34;。他们工作,但完全模仿按钮可能会很棘手。我提出了一个解决方案; &#34;在布局中构建一个按钮,一个图像,一个textview,和一个按钮&#34;

这里提供了与我提议的相同的按钮:

<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,因此即使它们在按钮后定义,它们仍然会在&#34;下面&#34;它在高处并且是不可见的。设置&#39; android:elevation&#39;到10解决了这个问题。
  2. 必须设置TextView的textAppearance,使其具有与按钮中相同的外观。

答案 10 :(得分:0)

我知道有些晚了,但是如果有人在寻找另一个答案,这是添加图标的另一种方式,而无需使用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才能使跨范围工作


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

button with imagespan

答案 11 :(得分:0)

您可以使用 head = csv.reader(open(f'{sys.argv[1]}.csv'))
https://material.io/develop/android/components/material-button/

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

<com.google.android.material.button.MaterialButton/>

答案 12 :(得分:0)

现在默认情况下,Material Buttonapp:iconGravity属性可用。但是,Material Button不允许将背景设置为可绘制(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()
  }
}

答案 13 :(得分:-4)

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

    <View
        android:layout_width="0dp"
        android:layout_height="fill_parent"
        android:layout_weight="1" />