Android图文居中显示控件

最近项目中用到了文字图标的按钮,需要居中显示,如果用TextView实现的方式,必须同时设置paddingdrawablePadding。如下:

<androidx.appcompat.widget.AppCompatTextView
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:drawableLeft="@drawable/ic_xxx"
        android:drawablePadding="-60dp"
        android:minHeight="48dp"
        android:gravity="center"
        android:padding="80dp" />

这种方式需要自己做精确计算。比较麻烦。另外还有一种方式就是用线性布局包裹ImageView和TextView,但这样会增加布局层级。于是自己封装了一个控件DrawableCenterTextView。

attrs.xml文件中定义属性:

<declare-styleable name="DrawableCenterTextView">
        <attr name="android:text" />
        <attr name="android:textColor" />
        <attr name="android:textSize" />
        <attr name="android:textStyle" />
        <attr name="android:drawablePadding" />
        <attr name="android:drawableLeft" />
        <attr name="android:drawableTop" />
        <attr name="android:drawableRight" />
        <attr name="android:drawableBottom" />
</declare-styleable>

对应的Java代码如下:

public class DrawableCenterTextView extends View {

    static final int LEFT = 0;
    static final int TOP = 1;
    static final int RIGHT = 2;
    static final int BOTTOM = 3;

    private CharSequence mText;
    private ColorStateList mTextColor;
    private float mTextSize;
    private int mTextStyle;
    private int mDrawablePadding;
    private Drawable[] mCompoundDrawables;

    private Rect mTextBounds;
    private Rect mDrawableLeftBounds;
    private Rect mDrawableTopBounds;
    private Rect mDrawableRightBounds;
    private Rect mDrawableBottomBounds;

    private TextPaint mTextPaint;

    public DrawableCenterTextView(Context context) {
        this(context, null);
    }

    public DrawableCenterTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DrawableCenterTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        Drawable drawableLeft = null, drawableTop = null, drawableRight = null, drawableBottom = null;
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DrawableCenterTextView, defStyleAttr, 0);
        mText = ta.getText(R.styleable.DrawableCenterTextView_android_text);
        mTextColor = ta.getColorStateList(R.styleable.DrawableCenterTextView_android_textColor);
        mTextSize = ta.getDimensionPixelSize(R.styleable.DrawableCenterTextView_android_textSize, 15);
        mTextStyle = ta.getInt(R.styleable.DrawableCenterTextView_android_textStyle, 0);

        drawableLeft = ta.getDrawable(R.styleable.DrawableCenterTextView_android_drawableLeft);
        drawableTop = ta.getDrawable(R.styleable.DrawableCenterTextView_android_drawableTop);
        drawableRight = ta.getDrawable(R.styleable.DrawableCenterTextView_android_drawableRight);
        drawableBottom = ta.getDrawable(R.styleable.DrawableCenterTextView_android_drawableBottom);

        mDrawablePadding = ta.getDimensionPixelSize(R.styleable.DrawableCenterTextView_android_drawablePadding, 0);
        ta.recycle();

        if (mTextColor == null) {
            mTextColor = ColorStateList.valueOf(0xFF000000);
        }

        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.density = getResources().getDisplayMetrics().density;
        mTextPaint.setTextSize(mTextSize);
        setTypeface(Typeface.create(Typeface.DEFAULT, mTextStyle));

        setCompoundDrawablesWithIntrinsicBounds(drawableLeft, drawableTop, drawableRight, drawableBottom);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;

        //计算文本范围
        calcTextBounds();

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = mTextBounds.width();

            if (mCompoundDrawables != null) {
                if (mCompoundDrawables[TOP] != null) {
                    width = Math.max(width, mDrawableTopBounds.width());
                }

                if (mCompoundDrawables[BOTTOM] != null) {
                    width = Math.max(width, mDrawableBottomBounds.width());
                }

                //加上左右内边距及drawable宽度和drawable间距
                width += getCompoundPaddingLeft() + getCompoundPaddingRight();

                width = Math.max(width, getSuggestedMinimumWidth());

                if (widthMode == MeasureSpec.AT_MOST) {
                    width = Math.min(widthSize, width);
                }
            }
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = mTextBounds.height();

            if (mCompoundDrawables != null) {
                if (mCompoundDrawables[LEFT] != null) {
                    height = Math.max(height, mDrawableLeftBounds.height());
                }

                if (mCompoundDrawables[RIGHT] != null) {
                    height = Math.max(height, mDrawableRightBounds.height());
                }

                //加上上下内边距及drawable高度和drawable间距
                height += getCompoundPaddingTop() + getCompoundPaddingBottom();

                height = Math.max(height, getSuggestedMinimumHeight());

                if (heightMode == MeasureSpec.AT_MOST) {
                    height = Math.min(heightSize, height);
                }
            }
        }

        setMeasuredDimension(width, height);
    }

    public int getCompoundPaddingTop() {
        if (mCompoundDrawables == null || mCompoundDrawables[TOP] == null) {
            return getPaddingTop();
        } else {
            Rect rect = new Rect();
            mCompoundDrawables[TOP].copyBounds(rect);
            return getPaddingTop() + mDrawablePadding + rect.height();
        }
    }

    public int getCompoundPaddingBottom() {
        if (mCompoundDrawables == null || mCompoundDrawables[BOTTOM] == null) {
            return getPaddingBottom();
        } else {
            Rect rect = new Rect();
            mCompoundDrawables[BOTTOM].copyBounds(rect);
            return getPaddingBottom() + mDrawablePadding + rect.height();
        }
    }

    public int getCompoundPaddingLeft() {
        if (mCompoundDrawables == null || mCompoundDrawables[LEFT] == null) {
            return getPaddingLeft();
        } else {
            Rect rect = new Rect();
            mCompoundDrawables[LEFT].copyBounds(rect);
            return getPaddingLeft() + mDrawablePadding + rect.width();
        }
    }

    public int getCompoundPaddingRight() {
        if (mCompoundDrawables == null || mCompoundDrawables[RIGHT] == null) {
            return getPaddingRight();
        } else {
            Rect rect = new Rect();
            mCompoundDrawables[RIGHT].copyBounds(rect);
            return getPaddingRight() + mDrawablePadding + rect.width();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int vspace = getBottom() - getTop() - getCompoundPaddingBottom() - getCompoundPaddingTop(); //剩余垂直可绘制文本空间大小
        int hspace = getRight() - getLeft() - getCompoundPaddingRight() - getCompoundPaddingLeft(); //剩余水平可绘制文本空间大小

        if (mCompoundDrawables != null) {
            if (mCompoundDrawables[LEFT] != null) {
                canvas.save();
                canvas.translate((hspace - mTextBounds.width()) / 2.0f + getPaddingLeft(),
                        getCompoundPaddingTop() + (vspace - mDrawableLeftBounds.height()) / 2.0f);
                mCompoundDrawables[LEFT].draw(canvas);
                canvas.restore();
            }

            if (mCompoundDrawables[RIGHT] != null) {
                canvas.save();
                canvas.translate(getRight() - getLeft() - getPaddingRight() - (hspace - mTextBounds.width()) / 2.0f - mDrawableRightBounds.width(),
                        getCompoundPaddingTop() + (vspace - mDrawableRightBounds.height()) / 2.0f);
                mCompoundDrawables[RIGHT].draw(canvas);
                canvas.restore();
            }

            if (mCompoundDrawables[TOP] != null) {
                canvas.save();
                canvas.translate(getCompoundPaddingLeft()
                        + (hspace - mDrawableTopBounds.width()) / 2.0f, (vspace - mTextBounds.height()) / 2.0f + getPaddingTop());
                mCompoundDrawables[TOP].draw(canvas);
                canvas.restore();
            }

            if (mCompoundDrawables[BOTTOM] != null) {
                canvas.save();
                canvas.translate(getCompoundPaddingLeft()
                                + (hspace - mDrawableBottomBounds.width()) / 2.0f,
                        getBottom() - getTop() - getPaddingBottom() - (vspace - mTextBounds.height()) / 2.0f - mDrawableBottomBounds.height());
                mCompoundDrawables[BOTTOM].draw(canvas);
                canvas.restore();
            }
        }

        if (!TextUtils.isEmpty(mText)) {
            float startX = (hspace - mTextBounds.width()) / 2.0f + getCompoundPaddingLeft();
            //因为drawText以baseline为基准,因此需要向下移ascent
            float startY = (vspace - mTextBounds.height()) / 2.0f + getCompoundPaddingTop() - mTextPaint.getFontMetrics().ascent;
            mTextPaint.setColor(mTextColor.getColorForState(getDrawableState(), 0));
            canvas.drawText(mText, 0, mText.length(), startX, startY, mTextPaint);
        }
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();

        if (mTextColor != null && mTextColor.isStateful()) {
            mTextPaint.setColor(mTextColor.getColorForState(getDrawableState(), 0));
        }

        if (mCompoundDrawables != null) {
            final int[] state = getDrawableState();
            for (Drawable dr : mCompoundDrawables) {
                if (dr != null && dr.isStateful() && dr.setState(state)) {
                    invalidateDrawable(dr);
                }
            }
        }
    }

    public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
                                                        @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
        if (left != null) {
            left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
        }
        if (right != null) {
            right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
        }
        if (top != null) {
            top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
        }
        if (bottom != null) {
            bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
        }
        setCompoundDrawables(left, top, right, bottom);
    }

    public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
                                     @Nullable Drawable right, @Nullable Drawable bottom) {

        if (mCompoundDrawables == null) {
            mCompoundDrawables = new Drawable[4];
        } else {
            if (mCompoundDrawables[LEFT] != null && mCompoundDrawables[LEFT] != left) {
                mCompoundDrawables[LEFT].setCallback(null);
            }

            if (mCompoundDrawables[TOP] != null && mCompoundDrawables[TOP] != top) {
                mCompoundDrawables[TOP].setCallback(null);
            }

            if (mCompoundDrawables[RIGHT] != null && mCompoundDrawables[RIGHT] != right) {
                mCompoundDrawables[RIGHT].setCallback(null);
            }

            if (mCompoundDrawables[BOTTOM] != null && mCompoundDrawables[BOTTOM] != bottom) {
                mCompoundDrawables[BOTTOM].setCallback(null);
            }
        }

        if (left != null) {
            mDrawableLeftBounds = new Rect();
            left.copyBounds(mDrawableLeftBounds);
            left.setCallback(this);
            mCompoundDrawables[LEFT] = left;
        } else {
            mCompoundDrawables[LEFT] = null;
        }

        if (top != null) {
            mDrawableTopBounds = new Rect();
            top.copyBounds(mDrawableTopBounds);
            top.setCallback(this);
            mCompoundDrawables[TOP] = top;
        } else {
            mCompoundDrawables[TOP] = null;
        }

        if (right != null) {
            mDrawableRightBounds = new Rect();
            right.copyBounds(mDrawableRightBounds);
            right.setCallback(this);
            mCompoundDrawables[RIGHT] = right;
        } else {
            mCompoundDrawables[RIGHT] = null;
        }

        if (bottom != null) {
            mDrawableBottomBounds = new Rect();
            bottom.copyBounds(mDrawableBottomBounds);
            bottom.setCallback(this);
            mCompoundDrawables[BOTTOM] = bottom;
        } else {
            mCompoundDrawables[BOTTOM] = null;
        }

        invalidate();
        requestLayout();
    }

    public void setText(CharSequence text) {
        this.mText = text;

        invalidate();
        requestLayout();
    }

    public void setTextSize(float textSize) {
        this.mTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSize, getResources().getDisplayMetrics());

        invalidate();
        requestLayout();
    }

    public void setTextColor(@ColorInt int textColor) {
        this.mTextColor = ColorStateList.valueOf(textColor);

        invalidate();
    }

    public void setTypeface(@Nullable Typeface tf) {
        if (mTextPaint.getTypeface() != tf) {
            mTextPaint.setTypeface(tf);

            requestLayout();
            invalidate();
        }
    }

    private void calcTextBounds() {
        mTextBounds = new Rect();

        if (TextUtils.isEmpty(mText)) return;

        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
            mTextPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
        } else {
            int width = (int) Math.ceil(mTextPaint.measureText(mText.toString()));
            Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
            int height = (int) Math.ceil(fontMetrics.descent - fontMetrics.ascent);
            mTextBounds.set(0, 0, width, height);
        }
    }
}

感谢大家的支持,如有错误请指正,如需转载请标明原文出处!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值