Android实现TextView双层描边遇无限重绘问题求解决方案
Android 实现双层描边TextView的可行方案
原生Android不支持TextView的描边效果(包括双层描边),参考相关方案实现的自定义StrokedTextView会因调用setTextColor()触发invalidate(),导致onDraw无限重绘;尝试标志位控制和反射修改私有字段的方案要么无效,要么在高版本API中失效。以下是无需触发重绘的可靠实现方案:
核心思路
避免调用setTextColor()(该方法会触发视图重绘),直接操作TextView的Paint对象修改颜色与样式,通过canvas.drawText()手动绘制描边层,同时用Paint的状态保存/恢复机制避免影响原始绘制逻辑。
单行文字实现代码
public class StrokedTextView extends androidx.appcompat.widget.AppCompatTextView { private int strokeColorW, strokeColorB; private float strokeWidthW, strokeWidthB; public StrokedTextView(Context context) { this(context, null, 0); } public StrokedTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public StrokedTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); strokeColorW = context.getColor(R.color.white); strokeWidthW = dpToPx(context, 2); strokeColorB = context.getColor(R.color.main_color_dark); strokeWidthB = dpToPx(context, 3); // 开启抗锯齿优化描边效果 getPaint().setAntiAlias(true); } @Override protected void onDraw(Canvas canvas) { Paint paint = getPaint(); // 保存Paint原始状态,避免后续修改影响其他绘制逻辑 paint.save(); // 1. 绘制文字填充层(使用原始文字颜色) paint.setStyle(Paint.Style.FILL); super.onDraw(canvas); // 2. 绘制外层深色描边 paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(strokeWidthB); paint.setColor(strokeColorB); // 获取单行文字的位置并绘制 String text = getText().toString(); float x = getLayout().getLineLeft(0); float y = getLayout().getLineBaseline(0); canvas.drawText(text, x, y, paint); // 3. 绘制内层白色描边 paint.setStrokeWidth(strokeWidthW); paint.setColor(strokeColorW); canvas.drawText(text, x, y, paint); // 恢复Paint原始状态 paint.restore(); } public static int dpToPx(Context context, float dp) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dp * scale + 0.5f); } }
多行文字兼容版本
如果需要支持多行文字描边,只需修改onDraw()方法,遍历每一行文字进行绘制:
@Override protected void onDraw(Canvas canvas) { Paint paint = getPaint(); paint.save(); // 绘制填充层 paint.setStyle(Paint.Style.FILL); super.onDraw(canvas); // 绘制外层描边 paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(strokeWidthB); paint.setColor(strokeColorB); drawAllTextLines(canvas, paint); // 绘制内层描边 paint.setStrokeWidth(strokeWidthW); paint.setColor(strokeColorW); drawAllTextLines(canvas, paint); paint.restore(); } // 遍历所有文字行并绘制 private void drawAllTextLines(Canvas canvas, Paint paint) { Layout layout = getLayout(); String text = getText().toString(); for (int lineIndex = 0; lineIndex < layout.getLineCount(); lineIndex++) { int lineStart = layout.getLineStart(lineIndex); int lineEnd = layout.getLineEnd(lineIndex); float lineX = layout.getLineLeft(lineIndex); float lineY = layout.getLineBaseline(lineIndex); canvas.drawText(text, lineStart, lineEnd, lineX, lineY, paint); } }
方案优势
- 彻底避免无限重绘:未调用任何触发
invalidate()的方法,所有绘制操作仅在一次onDraw周期内完成 - 全API版本兼容:无需依赖反射或私有字段,适配Android所有版本
- 性能更优:直接调用canvas.drawText绘制描边,避免多次调用super.onDraw带来的额外开销
内容的提问来源于stack exchange,提问作者Josemafuen
相关产品推荐
相关产品推荐

