简体   繁体   English

通过将跨度设置为 EditText 有效地即时应用文本格式

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

I'm using an EditText control that I allow text formatting of (bold, italics etc.).我正在使用 EditText 控件,该控件允许文本格式设置(粗体、斜体等)。

To apply formatting, within my TextWatcher's AfterTextChanged event handler I detect whether a formatting style, such as bold, has been toggled on via the UI.为了应用格式,在我的 TextWatcher 的 AfterTextChanged 事件处理程序中,我检测是否通过 UI 切换了格式样式(例如粗体)。 If it is, I've tried two different approaches, neither of which are satisfactory for different reasons:如果是这样,我尝试了两种不同的方法,但由于不同的原因,这两种方法都不令人满意:

Approach 1方法一

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

For the start value, I've tried using _textView.SelectionStart - 1 or the starting position when the StyleSpan was first applied. 1或者第一次应用在StyleSpan起始位置-对于起始值,我一直在使用_textView.SelectionStart尝试。 And for the end value _textView.SelectionStart .对于最终_textView.SelectionStart

Although the text appears formatted fine using this method, it creates unnecessary StyleSpans when only the single would suffice.尽管使用此方法显示文本格式良好,但当只有单个就足够时,它会创建不必要的 StyleSpans。 This is clear when I try to save the text to my local db through a Html conversion:当我尝试通过 Html 转换将文本保存到本地数据库时,这一点很清楚:

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

For example, instead of <b>this is bold text</b> , I'm getting <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> .例如,而不是<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> So, clearly, I'm doing something wrong/being inefficient in this approach.所以,很明显,我在这种方法中做错了/效率低下。 What obviously this leads to is eventual slowdowns when both inputting text as well as retrieving at launch.显然,这会导致在输入文本以及在启动时检索时最终变慢。

Something I've considered is to check whether there's a Span on the preceding character ( _textView.SelectionStart - 1 ), and, if yes, to remove the span, and then add a span that starts at that point up until _textView.SelectionStart ie ensures there's only a single Span by constantly checking/removing/adding the necessary Span.我考虑过的事情是检查前面的字符( _textView.SelectionStart - 1 )上是否有跨度,如果是,则删除跨度,然后添加一个从该点开始直到_textView.SelectionStart的跨度,即通过不断检查/删除/添加必要的跨度来确保只有一个跨度。 But this seems like another inefficient method to handle this.但这似乎是处理此问题的另一种低效方法。

Approach 2方法二

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

So, this doesn't lead to the same inefficiencies as above, but because of the SpanTypes.ExclusiveInclusive flag, I'm unable to stop the style formatting to end when I toggle it off via the UI.因此,这不会导致与上述相同的低效率,但是由于SpanTypes.ExclusiveInclusive标志,当我通过 UI 将其关闭时,我无法停止样式格式设置以结束。 In other words, when I toggle the Bold style on, all text that follows will be formatted in bold styling, even when I've turned its toggle off.换句话说,当我打开粗体样式时,即使我关闭了它的切换,后面的所有文本都将以粗体样式进行格式化。

Of the two, this seems to me like the one that's the correct general approach, and so I'm wondering whether I can do anything to stop the style being applied as soon as I turn its toggle off.在这两者中,这在我看来是正确的一般方法,所以我想知道我是否可以做任何事情来阻止样式在我关闭它的切换时立即应用。 Or is there another way that I've missed altogether as best practice for handling this sort of requirement.或者我完全错过了另一种处理此类需求的最佳实践。

So, I ended up taking quite a different approach by moving the responsibility of setting the span to when the button on the Toolbar to active a style is toggled (as opposed to in any of the text changed listeners).因此,我最终采取了一种完全不同的方法,将设置跨度的责任转移到工具栏上激活样式的按钮被切换时(而不是在任何文本更改的侦听器中)。

For example, when the bold style is toggled on, its event handler runs hitting the following code:例如,当粗体样式被打开时,它的事件处理程序运行命中以下代码:

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

The span type needs to be ExclusiveInclusive as suggested above.如上所述,跨度类型需要为 ExclusiveInclusive。 The trick is to change this as soon as the style has been toggled off.诀窍是在样式关闭后立即更改此设置。 This is relatively straightforward if you're typing in bold and then turn the style off (just a matter of finding the span, removing it and then adding a new span with same start/end points but that's ExcExc).如果您以粗体输入然后关闭样式,这相对简单(只需找到跨度,将其删除,然后添加具有相同起点/终点的新跨度,但这是 ExcExc)。 But I needed the code to be more flexible and account for a situation whereby you may later decide to type within the span text of another style.但我需要代码更加灵活,并考虑到您可能稍后决定在另一种样式的跨度文本中键入的情况。 For example, let's say I start with:例如,假设我从以下开始:

This is bold text这是粗体文字

But then I edit and want to change it to:但后来我编辑并想将其更改为:

This is bold yes text这是粗体文本

In such a scenario, I need to make sure I create an ExclusiveExclusive bold span on either side of the "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);
        }
    }
}

This seems to be doing the job: spans are created/managed when the UI is interacted with (and not text changed) 👍这似乎在做这项工作:当 UI 交互(而不是文本更改)时创建/管理跨度👍

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM