You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android文本编辑器切换字体样式时下划线消失问题求助

解决Android文本编辑器下划线切换样式时消失/错位的问题

嘿,我仔细看了你的代码,发现几个核心问题导致了下划线在切换字体样式时消失或者错位,咱们一步步来修复:

问题根源分析

  • 静态变量underline误用:你把下划线状态设为static变量,所有TextArea实例都会共享这个状态,直接修改它会导致状态混乱,无法正确关联当前编辑的文本区域。
  • 样式切换未处理选中文本:当前changeTypeface方法只记录光标位置,没有对已选中的文本应用新样式和下划线状态,导致切换样式时只有后续输入的文本生效,之前的下划线被覆盖或丢失。
  • onTextChanged中Span范围错误:每次输入文本时直接把Span应用到从lastCursorPosition到文本末尾的范围,切换样式后下划线会错误转移到后续所有字符上,而非仅新输入内容。
  • 未清理旧Span:添加新的StyleSpanUnderlineSpan时没有先移除对应区域的旧Span,导致Span叠加冲突,样式显示异常。

修复后的代码实现

修改后的TextArea类

public class TextArea extends EditText {
    public static final int TYPEFACE_NORMAL = 0;
    public static final int TYPEFACE_BOLD = 1;
    public static final int TYPEFACE_ITALICS = 2;
    public static final int TYPEFACE_BOLD_ITALICS = 3;

    // 改为实例变量,每个TextArea独立管理下划线状态
    private boolean isUnderlineEnabled = false;
    private int currentTypeface = TYPEFACE_NORMAL;
    private int lastCursorPosition;

    public TextArea(Context context) {
        super(context);
        init();
    }

    public TextArea(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        lastCursorPosition = getSelectionStart();
    }

    public void changeTypeface(int tfId) {
        currentTypeface = tfId;
        // 对选中文本应用新样式
        applyStyleToSelectedText();
        lastCursorPosition = getSelectionStart();
    }

    // 切换下划线状态并应用到选中文本
    public void toggleUnderline() {
        isUnderlineEnabled = !isUnderlineEnabled;
        applyStyleToSelectedText();
        lastCursorPosition = getSelectionStart();
    }

    // 对选中区域应用当前样式和下划线状态
    private void applyStyleToSelectedText() {
        Spannable str = getText();
        int start = getSelectionStart();
        int end = getSelectionEnd();

        // 无选中文本时,记录光标位置用于后续输入
        if (start == end) {
            lastCursorPosition = start;
            return;
        }

        // 先移除旧Span避免叠加冲突
        removeSpansInRange(str, start, end);

        // 添加对应样式的Span
        StyleSpan styleSpan = getStyleSpanByType(currentTypeface);
        str.setSpan(styleSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

        // 下划线开启时添加对应Span
        if (isUnderlineEnabled) {
            str.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }

    // 根据类型获取对应的StyleSpan
    private StyleSpan getStyleSpanByType(int typefaceId) {
        switch (typefaceId) {
            case TYPEFACE_BOLD:
                return new StyleSpan(Typeface.BOLD);
            case TYPEFACE_ITALICS:
                return new StyleSpan(Typeface.ITALIC);
            case TYPEFACE_BOLD_ITALICS:
                return new StyleSpan(Typeface.BOLD_ITALIC);
            default:
                return new StyleSpan(Typeface.NORMAL);
        }
    }

    // 移除指定范围内的StyleSpan和UnderlineSpan
    private void removeSpansInRange(Spannable str, int start, int end) {
        StyleSpan[] styleSpans = str.getSpans(start, end, StyleSpan.class);
        for (StyleSpan span : styleSpans) {
            str.removeSpan(span);
        }
        UnderlineSpan[] underlineSpans = str.getSpans(start, end, UnderlineSpan.class);
        for (UnderlineSpan span : underlineSpans) {
            str.removeSpan(span);
        }
    }

    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        Spannable str = getText();

        // 仅对新输入的文本应用样式
        int newTextEnd = start + lengthAfter;
        if (lastCursorPosition != newTextEnd && lengthAfter > 0) {
            removeSpansInRange(str, lastCursorPosition, newTextEnd);

            StyleSpan styleSpan = getStyleSpanByType(currentTypeface);
            str.setSpan(styleSpan, lastCursorPosition, newTextEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            if (isUnderlineEnabled) {
                str.setSpan(new UnderlineSpan(), lastCursorPosition, newTextEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }

            lastCursorPosition = getSelectionStart();
        }
    }
}

修改后的MainActivity相关代码

// 初始化控件
bold = findViewById(R.id.bold);
italic = findViewById(R.id.italic);
boldItalic = findViewById(R.id.boldItalic);
editText = findViewById(R.id.editText);
normal = findViewById(R.id.normal);
underline = findViewById(R.id.underline);

int typefaceStyle = TextArea.TYPEFACE_NORMAL;
editText.changeTypeface(typefaceStyle);
boolean under_it = false;

normal.setOnClickListener(v -> {
    typefaceStyle = TextArea.TYPEFACE_NORMAL;
    editText.changeTypeface(typefaceStyle);
});

bold.setOnClickListener(v -> {
    typefaceStyle = TextArea.TYPEFACE_BOLD;
    editText.changeTypeface(typefaceStyle);
});

italic.setOnClickListener(v -> {
    typefaceStyle = TextArea.TYPEFACE_ITALICS;
    editText.changeTypeface(typefaceStyle);
});

boldItalic.setOnClickListener(v -> {
    typefaceStyle = TextArea.TYPEFACE_BOLD_ITALICS;
    editText.changeTypeface(typefaceStyle);
});

underline.setOnClickListener(v -> {
    under_it = !under_it;
    editText.toggleUnderline();
    // 更新按钮背景色
    underline.setBackgroundColor(under_it ? Color.parseColor("#fc0505") : Color.parseColor("#001919"));
});

关键修改点说明

  1. 下划线状态改为实例变量:每个TextArea独立管理自身下划线开关,避免全局状态混乱。
  2. 新增applyStyleToSelectedText方法:切换样式或下划线时,直接对选中文本应用样式,解决旧文本样式丢失的问题。
  3. 清理旧Span:应用新样式前先移除对应区域的旧Span,避免叠加导致的样式异常。
  4. 修正onTextChanged中的Span范围:仅对新输入的文本应用样式,确保下划线和字体样式作用在正确的文本区域,不会错位。

修改后,切换字体样式时已添加的下划线会保留在对应文本上,新输入的文本也会自动应用当前选中的样式和下划线状态。

内容的提问来源于stack exchange,提问作者Srinivas Nahak

火山引擎 最新活动