Эффективное применение форматирования текста «на лету» путем установки интервалов на EditText.

Я использую элемент управления EditText, для которого разрешено форматирование текста (жирный, курсив и т. д.).

Чтобы применить форматирование, в обработчике событий AfterTextChanged моего TextWatcher я определяю, был ли стиль форматирования, например полужирный, включен через пользовательский интерфейс. Если это так, я пробовал два разных подхода, ни один из которых не является удовлетворительным по разным причинам:

Подход 1

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>. Итак, ясно, что я делаю что-то неправильно/неэффективен в этом подходе. Очевидно, что это приводит к возможным замедлениям как при вводе текста, так и при извлечении при запуске.

Что-то, что я рассмотрел, это проверить, есть ли диапазон на предыдущем символе (_textView.SelectionStart - 1), и, если да, удалить диапазон, а затем добавить диапазон, который начинается с этой точки до _textView.SelectionStart, т.е. гарантирует, что есть только один диапазон постоянно проверяя/удаляя/добавляя необходимый Span. Но это похоже на еще один неэффективный метод справиться с этим.

Подход 2

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

Таким образом, это не приводит к той же неэффективности, что и выше, но из-за флага SpanTypes.ExclusiveInclusive я не могу остановить форматирование стиля до конца, когда я отключаю его через пользовательский интерфейс. Другими словами, когда я включаю полужирный стиль, весь последующий текст будет выделен жирным шрифтом, даже если я выключил этот переключатель.

Из этих двух мне кажется, что это правильный общий подход, и поэтому мне интересно, могу ли я что-нибудь сделать, чтобы остановить применение стиля, как только я выключу его переключатель. Или есть другой способ, который я вообще пропустил как наилучшую практику для обработки такого рода требований.


person Barrrdi    schedule 14.02.2020    source источник
comment
Оба решения, подход 1 и подход 2, звучат правильно. Интересно, почему вы считаете, что решение, которое вы предложили для улучшения подхода 1, неэффективно. Это не звучит неэффективно, и я бы попытался увидеть, не отстает ли он, а также, если возможно, измерить (с помощью эталонной библиотеки?). Кроме того, вы можете использовать то же улучшение, когда в подходе 2 пользователь выключает жирный шрифт. В этот момент вы можете заменить существующий инклюзивный диапазон на exc_exc. Вам может понадобиться подход 1 для выбора пользователем текста и переключения стиля.   -  person Siyamed    schedule 15.02.2020
comment
Спасибо за подтверждение и предложение тоже Сиямед. После моего исходного сообщения и вашего ответа я действительно думал о том, что именно вы предложили с точки зрения замены диапазона ex_inc на диапазон exc_exc. Мое нежелание было вызвано исключительно чувством, что создание диапазона, а затем замена его на каждом тумблере кажется более неэффективным по сравнению со способом достижения того же самого, но без использования переключателя диапазона. Но я думаю, что нет лучшего (и, следовательно, более эффективного) способа осуществить это.   -  person Barrrdi    schedule 16.02.2020
comment
Таким образом, exc_exc выглядит здесь как правильное решение, но вы захотите отслеживать начало диапазона с помощью переключателя после ввода символа, чтобы начать этот диапазон. Вы не должны применять весь диапазон к отдельному символу. Используемый слушатель тоже в порядке, но я представляю, что в строке вы можете на мгновение увидеть неформатированную строку на экране? Мне было бы интересно узнать.   -  person childofthehorn    schedule 18.02.2020
comment
Нет, неформатированная строка не была заметна даже на мгновение. Ни раньше, когда диапазон применялся к каждому символу, ни сейчас, когда я вносил изменения, необходимые для оптимизации (о которых я напишу отдельно).   -  person Barrrdi    schedule 21.02.2020


Ответы (1)


Итак, в итоге я выбрал совершенно другой подход, перенеся ответственность за настройку диапазона на момент, когда кнопка на панели инструментов для активации стиля переключается (в отличие от любого из прослушивателей изменения текста).

Например, когда включен полужирный стиль, его обработчик событий запускается, выполняя следующий код:

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

Тип диапазона должен быть ExclusiveInclusive, как предложено выше. Хитрость заключается в том, чтобы изменить это, как только стиль будет отключен. Это относительно просто, если вы печатаете жирным шрифтом, а затем отключаете стиль (просто вопрос поиска диапазона, его удаления и последующего добавления нового диапазона с теми же начальными/конечными точками, но это ExcExc). Но мне нужно было, чтобы код был более гибким и учитывал ситуацию, когда позже вы можете решить ввести текст в диапазоне другого стиля. Например, скажем, я начинаю с:

Этот текст выделен жирным шрифтом

Но затем я редактирую и хочу изменить его на:

Выделено полужирным шрифтом да текст

В таком сценарии мне нужно убедиться, что я создал полужирный диапазон 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);
        }
    }
}

Кажется, это работает: промежутки создаются/управляются при взаимодействии с пользовательским интерфейсом (а не при изменении текста) ????

person Barrrdi    schedule 21.02.2020