You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:29:12