通过将 Spans 设置到 EditText 上来高效地即时应用文本格式

Efficiently applying text formatting on-the-fly by setting Spans onto EditText

我正在使用允许文本格式(粗体、斜体等)的 EditText 控件。

为了应用格式,在我的 TextWatcher 的 AfterTextChanged 事件处理程序中,我检测是否已通过 UI 启用了格式样式,例如粗体。如果是,我尝试了两种不同的方法,但由于不同的原因,这两种方法都不令人满意:

方法一

textView.EditableText.SetSpan(new StyleSpan(TypefaceStyle.Bold), start, end, SpanTypes.ExclusiveExclusive);

对于 start 值,我尝试使用 _textView.SelectionStart - 1 或 StyleSpan 时的起始位置第一次申请。而对于 end_textView.SelectionStart.

尽管使用此方法文本显示格式良好,但当只有单个就足够时,它会创建不必要的 StyleSpans。当我尝试通过 Html 转换将文本保存到我的本地数据库时,这一点很清楚:

string html = Html.ToHtml(new SpannableString(Fragment_Textarea.Instance().Textarea().EditableText));

例如,我得到的不是 <b>this is bold text</b>,而是 <b><b><b><b><b><b><b><b><b><b><b><b><b><b><b><b><b>this is bold text</b></b></b></b></b></b></b></b></b></b></b></b></b></b></b></b></b>。所以,很明显,我在用这种方法做一些 wrong/being 低效的事情。显然,这会导致输入文本和启动时检索速度最终变慢。

我考虑过的是检查前面的字符(_textView.SelectionStart - 1)是否有跨度,如果有,则删除跨度,然后添加从该点开始的跨度直到 _textView.SelectionStart 即通过不断 checking/removing/adding 必要的 Span 来确保只有一个 Span。但这似乎是另一种低效的处理方法。

方法二

textView.EditableText.SetSpan(new StyleSpan(TypefaceStyle.Bold), start, end, SpanTypes.ExclusiveInclusive);

因此,这不会导致与上面相同的低效率,但由于 SpanTypes.ExclusiveInclusive 标志,当我通过 [= 将其关闭时,我无法停止样式格式化以结束57=]。换句话说,当我打开粗体样式时,后面的所有文本都将以粗体样式设置格式,即使我已关闭它也是如此。

在我看来,这两种方法中的一种是正确的一般方法,所以我想知道我是否可以采取任何措施来阻止样式被应用为一旦我关闭它的开关。或者还有另一种我完全错过的方法作为处理此类要求的最佳实践。

因此,我最终采取了一种完全不同的方法,将设置跨度的责任转移到工具栏上用于激活样式的按钮被切换时(而不是在任何文本更改的侦听器中)。

例如,当打开粗体样式时,其事件处理程序运行时会遇到以下代码:

int start = _textarea.SelectionStart - 1;
var spanType = SpanTypes.ExclusiveInclusive;
_textarea.EditableText.SetSpan(new StyleSpan(TypefaceStyle.Bold), start, _textarea.SelectionStart, spanType);

跨度类型需要如上建议的那样为 ExclusiveInclusive。诀窍是在关闭样式后立即更改它。如果您输入粗体然后关闭样式,这是相对简单的(只需找到跨度,将其删除然后添加具有相同 start/end 点但那是 ExcExc 的新跨度)。但我需要代码更加灵活,并考虑到您稍后可能决定在另一种样式的 span 文本中键入的情况。例如,假设我开始于:

这是粗体文字

但后来我编辑并想将其更改为:

这是粗体正文

在这种情况下,我需要确保在 "yes" 的任一侧创建一个 ExclusiveExclusive 粗体跨度。 :

int start = -1;
int end = -1;
List<Tuple<int, int>> respans = new List<Tuple<int, int>>(); 

// go through all relevant spans that start from -1 indices ago
var spans = _textarea.EditableText.GetSpans(_textarea.SelectionStart - 1, _textarea.SelectionStart, Class.FromType(typeof(StyleSpan)));
if (spans.Length > 0)
{
    for (int u = 0; u < spans.Length; u++)
    {
        // found a matching span!
        if (((StyleSpan)spans[u]).Style == TypefaceStyle.Bold)
        {
            // get the starting and ending indices for the iterated span
            start = _textarea.EditableText.GetSpanStart(spans[u]);
            end = _textarea.EditableText.GetSpanEnd(spans[u]);

            // remove the span
            _textarea.EditableText.RemoveSpan(spans[u]);

            // if the current index is less than when this iterated span ended
            // and greater than when it started
            // then that means non-bold text is being inserted in the middle of a bold span
            // that needs to be split into 2 (before current index + after current index)
            if (_textarea.SelectionStart > start && _textarea.SelectionStart < end)
            {
                respans.Add(new Tuple<int, int>(start, _textarea.SelectionStart - 1));
                for(int c = _textarea.SelectionStart + 1; c < _textarea.Length(); c++ )
                {
                    if(_textarea.Text[c] != ' ' )
                    {
                        respans.Add(new Tuple<int, int>(c, end));
                        break;
                    }
                }
            }
            // otherwise, the recreated span needs to start and end when the iterated did
            // with one important change in relation to its span type
            else
            {
                respans.Add(new Tuple<int, int>(start, end));
            }
        }
    }

    // if there are 1 or more spans that need to be restored,
    // go through them and add them back according to start/end points set on their creation
    // as an ExclusiveExclusive span type
    if( respans.Count > 0 )
    {
        foreach( Tuple<int,int> tp in respans )
        {
            _textarea.EditableText.SetSpan(new StyleSpan(TypefaceStyle.Bold), tp.Item1, tp.Item2, SpanTypes.ExclusiveExclusive);
        }
    }
}

这似乎在做这项工作:跨度是 created/managed 当 UI 与 UI 交互(而不是文本更改)