Android图文居中显示控件使用方法详解
作者:折翅鵬 发布时间:2023-04-04 14:07:24
标签:Android,图文居中
最近项目中用到了文字图标的按钮,需要居中显示,如果用TextView实现的方式,必须同时设置padding和drawablePadding。如下:
<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);
}
}
}
感谢大家的支持,如有错误请指正。
来源:https://blog.csdn.net/szhupeng/article/details/108392513


猜你喜欢
- 前篇回顾:Spring源码解析容器初始化构造方法在上一篇文章中,我们介绍完了AnnotationConfigApplicationConte
- 前言今天来做个打方块的小游戏,继续熟悉kotlin的语法,更多关于kotlin的语法大家可以参考这篇文章:https://www.jb51.
- 用Canvas画贝塞尔曲线,要画贝塞尔曲线首先了解贝塞尔曲线:由于用计算机画图大部分时间是操作鼠标来掌握线条的路径,与手绘的感觉和效果有很大
- 这篇文章主要介绍了Spring自动装配Bean实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要
- 在实际应用中,大家使用的密码可以说多种多样,但是无论有多少,其组成不遑是有可打印字符组成的,我们可以认为class CreateDic{ p
- 每一个应用都是具备一个功能,那就是版本更新,我记得我之前在面试的时候,面试官让我介绍一下应用版本更新的一些具体操作。我当时因为做过这个功能,
- 本文实例为大家分享了Android自定义开关的具体代码,供大家参考,具体内容如下以 ToggleColorY 为例分析, ToggleIma
- 一、什么时候会加载类?使用到类中的内容时加载:有三种情况1.创建对象:new StaticCode();2.使用类中的静态成员:Static
- c#里面封装了几乎所有我们可以想到的和我们没有想到的类,流是读取文件的一般手段,那么你真的会用它读取文件中的数据了么?真的能读完全么?通常我
- 前言在这篇文章里,最后总结处,我说了会讲讲循环依赖中,其中一个类添加@Async有可能会导致注入失败而抛异常的情况,今天就分析一下。一、异常
- 原子数组原子数组有AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray,主要是用来
- 对于服务器端开发人员而言,调用第三方接口获取数据,将其“代理”转化并返给客户端几乎是家常便
- 反射和配置文件学习 Spring 的时候,虽然可以知道是通过反射和配置文件的方式来获取 JavaBean 对象,但是一直没有机会自己尝试一次
- 什么是NIO?线程在处理数据时,如果线程还处于将数据从channel读到buffer的这段时间内,线程可以去做别的事情,等数据都读到buff
- Android Fragment 动态创建Fragment是activity的界面中的一部分或一种行为。可以把多个Fragment组合到一个
- 前2天有读者问到是否有带分页功能的表格控件,今天分页功能的表格控件详细解析。PaginatedDataTablePaginatedDataT
- 一、前言做新应用就是这样,会遇到各种问题,昨天刚解决了加载某一个类时候抛出了 class is not visible from class
- 使用Aspose.Cells创建和读取Excel文件,供大家参考,具体内容如下1. 创建ExcelAspose.Cells.License
- 引言应用 Java 的开源库,编写一个搜索引擎,这个引擎能爬取一个网站的内容。并根据网页内容进行深度爬取,获取所有相关的网页地址和内容,用户
- 运行原理1、不同线程中所包含的栈帧是不允许存在相互引用的。2、如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给当前