androidandroid-edittextgalaxy-nexus

EditText with SpannableStringBuilder and ImageSpan doesn't works fine


I'm trying to put emoticons inside a EditText. I've managed to do it and it works fine but I have a problem when I try to delete these emoticons from the EditText using the soft keyboard. I can't do this action with a single delete button's click. When I insert a new ImageSpan I replace an imageId for it but when I try to delete de icon I have to delete all the imageId characters before delete the image.

String fileName = "emoticon1.png";
Drawable d = new BitmapDrawable(getResources(), fileName);
String imageId = "[" + fileName + "]";
int cursorPosition = content.getSelectionStart();
int end = cursorPosition + imageId.length();
content.getText().insert(cursorPosition, imageId);

SpannableStringBuilder ss = new SpannableStringBuilder(content.getText());
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);
ss.setSpan(span, cursorPosition, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
content.setText(ss, TextView.BufferType.SPANNABLE);
content.setSelection(end);

I need to remove the emoticons with a single delete button's click. Could you help me, please?

Thanks!


Solution

  • This is the implementation to handle emoticons inside a EditText. This implementation uses the TextWatcher to monitor the EditText changes and detect if some emoticon was removed when some text is deleted.

    Note that this implementation also verifies if a text selection was deleted (not only the delete key).

    To avoid issues with text prediction when typing a text, it is recommended to surround the emoticon text with spaces (the text prediction can join the emoticon text with the adjacent text).

    package com.takamori.testapp;
    
    import java.util.ArrayList;
    
    import android.app.Activity;
    import android.graphics.drawable.Drawable;
    import android.os.Bundle;
    import android.text.Editable;
    import android.text.Spanned;
    import android.text.TextWatcher;
    import android.text.style.ImageSpan;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.widget.EditText;
    
    public class MainActivity extends Activity {
    
        private EmoticonHandler mEmoticonHandler;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            EditText editor = (EditText) findViewById(R.id.messageEditor);
            // Create the emoticon handler.
            mEmoticonHandler = new EmoticonHandler(editor);
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()) {
                case R.id.action_insert_emoticon:
                    // WARNING: The emoticon text shall be surrounded by spaces
                    // to avoid issues with text prediction.
                    mEmoticonHandler.insert(" :-) ", R.drawable.smile);
                    return true;
    
                default:
                    return super.onOptionsItemSelected(item);
            }
        }
    
        private static class EmoticonHandler implements TextWatcher {
    
            private final EditText mEditor;
            private final ArrayList<ImageSpan> mEmoticonsToRemove = new ArrayList<ImageSpan>();
    
            public EmoticonHandler(EditText editor) {
                // Attach the handler to listen for text changes.
                mEditor = editor;
                mEditor.addTextChangedListener(this);
            }
    
            public void insert(String emoticon, int resource) {
                // Create the ImageSpan
                Drawable drawable = mEditor.getResources().getDrawable(resource);
                drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
                ImageSpan span = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
    
                // Get the selected text.
                int start = mEditor.getSelectionStart();
                int end = mEditor.getSelectionEnd();
                Editable message = mEditor.getEditableText();
    
                // Insert the emoticon.
                message.replace(start, end, emoticon);
                message.setSpan(span, start, start + emoticon.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
    
            @Override
            public void beforeTextChanged(CharSequence text, int start, int count, int after) {
                // Check if some text will be removed.
                if (count > 0) {
                    int end = start + count;
                    Editable message = mEditor.getEditableText();
                    ImageSpan[] list = message.getSpans(start, end, ImageSpan.class);
    
                    for (ImageSpan span : list) {
                        // Get only the emoticons that are inside of the changed
                        // region.
                        int spanStart = message.getSpanStart(span);
                        int spanEnd = message.getSpanEnd(span);
                        if ((spanStart < end) && (spanEnd > start)) {
                            // Add to remove list
                            mEmoticonsToRemove.add(span);
                        }
                    }
                }
            }
    
            @Override
            public void afterTextChanged(Editable text) {
                Editable message = mEditor.getEditableText();
    
                // Commit the emoticons to be removed.
                for (ImageSpan span : mEmoticonsToRemove) {
                    int start = message.getSpanStart(span);
                    int end = message.getSpanEnd(span);
    
                    // Remove the span
                    message.removeSpan(span);
    
                    // Remove the remaining emoticon text.
                    if (start != end) {
                        message.delete(start, end);
                    }
                }
                mEmoticonsToRemove.clear();
            }
    
            @Override
            public void onTextChanged(CharSequence text, int start, int before, int count) {
            }
    
        }
    }