自定义AppCompatTextView遇旋转抗锯齿问题,求文本替换方案
解决自定义AppCompatTextView的三个问题
让我们逐个拆解你遇到的问题,结合代码里的关键问题点给出修复方案:
1. 旋转后抗锯齿失效的问题
原因分析
你创建的mTxtPaint虽然构造时加了ANTI_ALIAS_FLAG,但有两个小疏漏:
- 你调用了
mTxtPaint.isAntiAlias()——这是个获取抗锯齿状态的方法,不是设置!等于白写; - 控件旋转后,硬件加速可能会弱化抗锯齿效果,需要确保paint的抗锯齿配置持续生效。
修复方案
在onDraw里明确设置抗锯齿,同时可以给控件禁用硬件加速(如果旋转后还是有锯齿的话):
// 在onDraw里替换掉isAntiAlias()的调用 mTxtPaint.setAntiAlias(true); // 可选:如果还是有锯齿,在构造方法里给控件禁用硬件加速 setLayerType(LAYER_TYPE_SOFTWARE, null);
2. layout_width设置导致的显示异常
原因分析
你现在的自定义绘制完全用了固定坐标(比如100, 100),完全没考虑控件本身的尺寸:
- 设为
match_parent时,控件变宽/高,但你的绘制内容还是固定在某个位置,自然显示异常; - 设为
wrap_content时,AppCompatTextView默认的onMeasure只会计算原生文本的尺寸,你的自定义内容不在计算范围内,导致控件尺寸过小,绘制内容超出控件边界,所以看不到。
修复方案
第一步:重写onMeasure,计算自定义内容的尺寸
这样wrap_content时控件会自动适配你的自定义绘制内容:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 计算自定义文本和背景矩形需要的尺寸 mTxtPaint.setTextSize(20 * getResources().getDisplayMetrics().density); float textWidth = mTxtPaint.measureText(str); Paint.FontMetrics fm = mTxtPaint.getFontMetrics(); float textHeight = fm.bottom - fm.top; // 加上margin和padding的尺寸 int margin = 5; int desiredWidth = (int) (textWidth + 25 + 2*margin + getPaddingLeft() + getPaddingRight()); int desiredHeight = (int) (textHeight + 2*margin + getPaddingTop() + getPaddingBottom()); // 结合系统的MeasureSpec计算最终尺寸 int width = resolveSize(desiredWidth, widthMeasureSpec); int height = resolveSize(desiredHeight, heightMeasureSpec); setMeasuredDimension(width, height); }
第二步:修改onDraw里的绘制坐标,基于控件尺寸动态计算
把固定坐标改成相对位置,比如基于控件中心或者padding:
@Override public void onDraw(Canvas canvas) { super.onDraw(canvas); // 先获取控件的可用区域,考虑padding int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight(); int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom(); Paint.FontMetrics fm = mTxtPaint.getFontMetrics(); mTxtPaint.setColor(Color.parseColor("#d00000")); mTxtPaint.setTextSize(20 * getResources().getDisplayMetrics().density); mTxtPaint.setAntiAlias(true); // 明确开启抗锯齿 mTxtPaint.getFontMetrics(fm); int margin = 5; float textWidth = mTxtPaint.measureText(str); // 计算矩形和文本的居中位置 float rectCenterX = availableWidth / 2f + getPaddingLeft(); float rectCenterY = availableHeight / 2f + getPaddingTop(); float rectWidth = textWidth + 25 + 2 * margin; float rectHeight = (fm.bottom - fm.top) + 2 * margin; float rectLeft = rectCenterX - rectWidth / 2f; float rectTop = rectCenterY - rectHeight / 2f + fm.top - margin; float rectRight = rectCenterX + rectWidth / 2f; float rectBottom = rectCenterY + rectHeight / 2f + fm.bottom + margin; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { canvas.drawRoundRect(rectLeft, rectTop, rectRight, rectBottom,35,35, mTxtPaint); } mTxtPaint.setColor(Color.WHITE); // 文本的Y坐标基于基线计算,保证垂直居中 float textX = rectLeft + 15 + margin; float textY = rectCenterY - (fm.ascent + fm.descent) / 2f; canvas.drawText(str, textX, textY, mTxtPaint); }
3. 读取XML中android:text的值替换自定义文本
原因分析
你现在的str默认是" ",完全没读取XML里设置的android:text属性。需要在构造方法里获取系统的text属性值。
修复方案
修改构造方法,添加读取android:text的逻辑:
HateTextView(Context context, AttributeSet attrs){ super(context, attrs); // 读取系统的android:text属性 String defaultText = ""; if (attrs != null) { TypedArray systemAttrs = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.text}); defaultText = systemAttrs.getString(0); systemAttrs.recycle(); } str = TextUtils.isEmpty(defaultText) ? " " : defaultText; // 读取你自定义的属性(如果有的话) TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.HateTextView); a.recycle(); // 提前初始化paint,避免每次onDraw都创建 mTxtPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTxtPaint.setAntiAlias(true); }
另外,记得导入android.text.TextUtils包。
修改后的完整HateTextView代码
import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Build; import android.support.v7.widget.AppCompatTextView; import android.util.AttributeSet; import android.text.TextUtils; public class HateTextView extends AppCompatTextView { Paint mTxtPaint; private String str = " "; // 补充无参构造,避免布局报错 HateTextView(Context context) { this(context, null); } HateTextView(Context context, AttributeSet attrs) { super(context, attrs); // 读取系统text属性 String defaultText = ""; if (attrs != null) { TypedArray systemAttrs = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.text}); defaultText = systemAttrs.getString(0); systemAttrs.recycle(); } str = TextUtils.isEmpty(defaultText) ? " " : defaultText; TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.HateTextView); a.recycle(); // 初始化paint mTxtPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTxtPaint.setAntiAlias(true); // 可选:禁用硬件加速解决旋转锯齿问题 setLayerType(LAYER_TYPE_SOFTWARE, null); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mTxtPaint.setTextSize(20 * getResources().getDisplayMetrics().density); float textWidth = mTxtPaint.measureText(str); Paint.FontMetrics fm = mTxtPaint.getFontMetrics(); float textHeight = fm.bottom - fm.top; int margin = 5; int desiredWidth = (int) (textWidth + 25 + 2 * margin + getPaddingLeft() + getPaddingRight()); int desiredHeight = (int) (textHeight + 2 * margin + getPaddingTop() + getPaddingBottom()); int width = resolveSize(desiredWidth, widthMeasureSpec); int height = resolveSize(desiredHeight, heightMeasureSpec); setMeasuredDimension(width, height); } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight(); int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom(); Paint.FontMetrics fm = mTxtPaint.getFontMetrics(); mTxtPaint.setColor(Color.parseColor("#d00000")); mTxtPaint.setTextSize(20 * getResources().getDisplayMetrics().density); int margin = 5; float textWidth = mTxtPaint.measureText(str); // 计算矩形和文本的居中位置 float rectCenterX = availableWidth / 2f + getPaddingLeft(); float rectCenterY = availableHeight / 2f + getPaddingTop(); float rectWidth = textWidth + 25 + 2 * margin; float rectHeight = (fm.bottom - fm.top) + 2 * margin; float rectLeft = rectCenterX - rectWidth / 2f; float rectTop = rectCenterY - rectHeight / 2f + fm.top - margin; float rectRight = rectCenterX + rectWidth / 2f; float rectBottom = rectCenterY + rectHeight / 2f + fm.bottom + margin; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { canvas.drawRoundRect(rectLeft, rectTop, rectRight, rectBottom, 35, 35, mTxtPaint); } mTxtPaint.setColor(Color.WHITE); float textX = rectLeft + 15 + margin; float textY = rectCenterY - (fm.ascent + fm.descent) / 2f; canvas.drawText(str, textX, textY, mTxtPaint); } public void setTextHate(String hate) { str = hate; invalidate(); // 刷新绘制 requestLayout(); // 重新计算尺寸 } }
额外注意点
- 调用
setTextHate后记得调用invalidate()和requestLayout(),这样控件会重新绘制和计算尺寸; - 如果不需要原生的TextView文本,可以在
onDraw里去掉super.onDraw(canvas);,避免原生文本显示。
内容的提问来源于stack exchange,提问作者user8110876
相关产品推荐
相关产品推荐

