After many days, finally updated again.

Now that we know the basics of Span, let’s deal with the overall control of Span. At the time of the rich text editor, we often encounter some overall content input, such as “@” user, input the topic “what topic # #”, skip links “URL”, the Span different from ordinary words, when the input is the overall input, delete, and delete whole, also known among can’t insert text or modify, This is the “holistic control of Span”.

So, we need to put the following restrictions on Span:

  • The cursor cannot be inserted in the middle
  • The value is added as a whole
  • Delete as a whole

There are two ways to deal with this requirement. The first is to use a Span that is originally a whole, such as ImageSpan, which is the simplest method and the code is very simple, and the other is to use code processing and let plain text achieve the whole function.

Integrity is guaranteed through ImageSpan

The Span content is generated into ImageSpan for overall control. This scheme is simple and easy to implement. Let’s take the new “@ user” as an example.

  1. First, create an ATSpan that inherits from ImageSpan with @ data information
  2. Parse the rich text data to add, the content to be displayed, for example “@xuyisheng”, as text, and create a TextView to host it
  3. Convert the generated TextView to a Drawable, set it to ATSpan, and pass in @ data information
  4. Insert the ImageSpan into the Edittext for rich text insertion of a holistic Span

It can be found that the implementation steps of this scheme are relatively simple, but its determination is also obvious:

First of all, since it is ImageSpan, there will always be some errors in the alignment with normal text, both from textView-drawable conversion process and from ImageSpan alignment process, so there will be some problems in the alignment of style, and at the same time, Due to the integrity of textView-drawable, if the TextView has more than one line or there is not enough space left in the current line, the rest of the second line will be filled up by the View’s rectangular area, which will no longer be able to input text, as shown below.

This is due to the graphical limitations of the View, using ImageSpan is not able to solve the problem, so it can be seen that although ImageSpan is inherently holistic, it is only a compromise solution, not the best implementation.

Controlled by SpanWatcher

The second solution, we use plain text, but for ordinary text increase Span tags, and the Span integrity control, this scheme is complicated, to handle more, but because it is using a plain text, so on the style may be completely consistent, and other plain text visual styles should be the best.

coloring

First, let’s create a blue color for normal text. This is easier, you can use ClickableSpan or another Span to color the text. To facilitate the input and display of rich text, select ClickableSpan directly.

Control of the selected

Before we look at how to do holistic control over spans in plain text, let’s consider the problem of selection — how to make the interior of a holistic Span unselectable.

First, remember that the cursor in Edittext is also a Span. In other words, we can listen for cursor movement events and use Selection to make the cursor move back to the nearest edge of the Span when it moves inside the Span, so that the cursor can never be inserted inside the Span. This is the main idea.

So the question is, how do I listen to the Edittext cursor?

In TextView and Edittext, you want to listen for changes in the Text. You can use TextWatcher, which can call back when the Text changes. Similarly, In SpannableStringBuidler, there is a similar management class, SpanWatcher, that can also be used to call back when the Span changes.

SpanWatcher, the official introduction is as follows.

When an object of this type is attached to a Spannable, its methods will be called to notify it that other markup objects have been added, changed, or removed.
Copy the code

Inside the TextVIew, it renders Spannable data through DynamicLayout. Inside the TextVIew, SpanWatcher is set to listen for additions, modifications, and deletions of spans. When changes are heard, it calls its internal methods to refresh.

SpanWatcher, like TextWatcher, is inherited from NoCopySpan, one listening for text changes and one listening for Span changes.

After SpanWatcher, let’s take a look at Selection. Selection is a tool class for managing Selection for TextView and Edittext. With Selection, you can modify the Selection of a Span without depending on the particular View.

Selection has two states, Start and End, and the cursor Selection is the two states of Selection. When the two states overlap, it is the input state of the cursor.

Now the idea is pretty obvious. Just listen for the Start and End states of Selection in SpanWatcher’s onSpanChanged. Once the Start and End states of Selection are in our global Span, Move the Selection cursor to the nearest Span tag.

So how does SpanWatcher work?

Edittext provides editable. Factory customization to add SpanWatcher, which we simply pass in at initialization, as shown below.

class ExEditableFactory(private val spans: List<NoCopySpan>) : Factory() {
    override fun newEditable(source: CharSequence): Editable {
        val spannableStringBuilder = RepairSpannableStringBuilder(source)
        for (span in spans) {
            spannableStringBuilder.setSpan(span, 0, source.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE or Spanned.SPAN_PRIORITY)
        }
        return spannableStringBuilder
    }
}

val watchers = ArrayList<NoCopySpan>()
watchers.add(SelectionSpanWatcher(IntegratedSpan::class))
setEditableFactory(ExEditableFactory(watchers))
Copy the code

This completes the selected global function, and when our Selection is in the global Span (marked by an IntegratedSpan), the Selection position is automatically changed so that the cursor cannot be inserted in the middle of the global Span.

Control to delete

The only problem left, besides selection, is overall control of deletion.

KEYCODE_DEL and keyevent.action_down events can be listened for by setOnKeyListener.

When we detect these two events, according to the current Selection position, we can get whether the current “global Span” exists. If it is the “global Span”, then the whole can be removed when deleting.

It is important to note that getSpan passes in Start and End as closed intervals, meaning that getSpan can reach the entire Span even if the cursor is now at the End of the Span.

With that in mind, our code is easy, and the key code is shown below.

In addition to removing the holistic Span, you can even use removeSpan to remove the holistic Span and restore it to normal text, depending on your needs.

Ok, so here we have implemented a very important feature in a rich text editor, the overall control of Span.

I would like to recommend my website xuyisheng. Top/focusing on Android-Kotlin-flutter welcome you to visit