The development of chat function, the need to achieve @xxx function in the group chat, online did not find ready-made things can be directly used, then their own a good masturbation
Project address github.com/sunhapper/S… SpEditTool Usage Guide Welcome star, mention PR, issue
ScreenShot
Functional analysis
- You can insert a special string like @ XXX
- You need to have highlights and things like that
- A special string as a whole should be deleted at the same time. The cursor cannot go inside the special string
- The special string should correspond to a custom data structure that holds the id, name, and other information of the @ object
Implementation approach
Inheriting the EditText
You don’t want to use inheritance as an intrusive way to do this, but you need to listen for cursor changes, and the SDK doesn’t provide a way to set cursor listening.
Records the location and representation of a particular string
This is the key point to achieve the function, summed up the next online scheme
MentionEditText
The library uses regular expressions to match special strings in a string, and it must be strictly at the beginning and end of the @ space. This way it can’t handle special strings with @ or a space in the middle, and it can’t handle just treating @ as a normal character
RichEditor
The library maintains a List of the contents of a particular string. When deleting or changing the cursor, it iterates through the List to determine whether the cursor is at the position of the particular string. At first glance, the library can record the @ XXX data structure by adding a field to the List. However, there is a serious problem after simple use: string like @11 @1 with the same content before it is iterated over the wrong position, and it is easy to trigger recursive calls to setSelection, causing StackOverflow
SpEditTool
This library uses Spannable’s setSpan method to set an Object as a token for a particular string
- The position of the tag is maintained by the Editable object in EditText, and the position changes automatically when characters are inserted and special strings are deleted, which is lazy but effective
- Because there’s a one-to-one correspondence between a tag and a particular string, you don’t have to worry about matching errors no matter how the contents of the text box change, right
Main code:
/** * insert special string to provide to external calls * @param showContent Special string to display in the text box * @param rollBack Whether to delete one character, @customSpan = @param customSpan = @param customSpan = @param customSpan = @param customSpan = @param customSpan = @param customSpan = @param customSpan = @param customSpan = @param customSpan = @param customSpan = @param customSpan = @param customSpan = @param customSpan showContent, boolean rollBack, Object customData, Object customSpan) {if (TextUtils.isEmpty(showContent)) {
return; } int index = getSelectionStart(); Editable editable = getText(); SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(editable); SpData = new SpData(); SpData = new SpData(); spData.setShowContent(showContent); spData.setCustomData(customData); SpannableString spannableString = new SpannableString(showContent); spannableString .setSpan(spData, 0, spannableString.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); // Set custom stylesif(customSpan ! = null) { spannableString .setSpan(customSpan, 0, spannableString.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); } // Whether to delete a characterif (rollBack) {
spannableStringBuilder.delete(index - 1, index);
index--;
}
spannableStringBuilder.insert(index, spannableString);
setText(spannableStringBuilder); // Place the cursor at the end of the insertsetSelection(index + spannableString.length());
}
Copy the code
Gets the special string to insert
Use the getSpans method of the Spanned interface
public SpData[] getSpDatas() {
Editable editable = getText();
SpData[] spanneds = editable.getSpans(0, getText().length(), SpData.class);
if(spanneds ! = null && spanneds.length > 0) {for(SpData spData : spanneds) { int start = editable.getSpanStart(spData); int end = editable.getSpanEnd(spData); // Set the start and end positions of the current special string spdata.setend (end); spData.setStart(start); } sortSpans(editable, spanneds, 0, spanneds.length - 1); // The retrieved data may not be sorted, so please sort it out before returningreturn spanneds;
} else {
returnnew SpData[]{}; }}Copy the code
Monitor cursor change
Override the onSelectionChanged method
*/ @override protected void onSelectionChanged(int selStart, int selEnd) { super.onSelectionChanged(selStart, selEnd); SpData[] spDatas = getSpDatas();for (int i = 0; i < spDatas.length; i++) {
SpData spData = spDatas[i];
int startPostion = spData.start;
int endPostion = spData.end;
if (changeSelection(selStart, selEnd, startPostion, endPostion, false)) {
return; }}}Copy the code
Listening for delete events
Use the EditText setOnKeyListener to listen for the deletion event and delete the whole thing if a special string is encountered
setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
return onDeleteEvent();
}
return false; }});Copy the code
private boolean onDeleteEvent() {
int selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
if(selectionEnd! =selectionStart){return false;
}
SpData[] spDatas = getSpDatas();
for (int i = 0; i < spDatas.length; i++) {
SpData spData = spDatas[i];
int rangeStart = spData.start;
if (selectionStart == spData.end) {
getEditableText().delete(rangeStart, selectionEnd);
return true; }}return false;
}
Copy the code
Input in response to @ in the text box
EditText can add a TextWatcher to listen for changes to the text (not necessary, can handle itself externally)
addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence charSequence, int start, int before, Int count) {//reactKeys is a list of characters to respond to, not just @for (Character character : reactKeys) {
if(count == 1 && ! TextUtils.isEmpty(charSequence)) { char mentionChar = charSequence.toString().charAt(start);if(character.equals(mentionChar) && mKeyReactListener ! = null) { handKeyReactEvent(character); // Inside the EditText, so a callback notifies the outside world that a special character was enteredreturn; }}}} @override public void afterTextChanged(Editable s) {}});Copy the code
private void handKeyReactEvent(final Character character) {
post(new Runnable() {
@Override
public void run() { mKeyReactListener.onKeyReact(character.toString()); }}); }Copy the code
Tips:post(Runnable runnabe)
The use of POST (Runnable runnabe) in onTextChanged to invoke the external callback is because the onSelectionChanged callback for the @ and other characters that were originally inserted has not gone when onTextChanged is executed
Assuming @ is entered, post(Runnable) is not used Runnabe), call onKeyReact directly, insert @sunhapper in the callback and set the cursor position,onSelectionChanged is called in the order onSelectionChanged(10,10)–>onSelectionChanged (1,1) causes the cursor position to be before the insert string instead of after it, which is not expected
Use the post (Runnable Runnabe) would it be possible for the current thread code execution to call onKeyReact, order onSelectionChanged calls for onSelectionChanged (1, 1) – > onSelectionChanged (10, 10), the cursor position in line with expectations
conclusion
- Inheriting the EditText
- Use the setSpan method to bind custom data structures and styles to the inserted text
- Use the getSpans method to get the data you insert
- Monitor cursor changes and actively change cursor position to prevent the cursor from entering a particular string
- Listen for deletion events to delete a particular string as a whole
Once you’ve done that, you’ve got an EditText that allows you to insert @ # topics and everything else to highlight and delete
Welcome to use the existing wheels project address github.com/sunhapper/S… Welcome star, mention PR, issue