Android Spannable - 表情符号删除 UTF 16

Android Spannable - Emoji deletion UTF 16

我对软键盘连接和 ankushsachdeva 表情符号键盘的自定义实现有问题,带有表情符号图像。

https://github.com/ankushsachdeva/emojicon

当我插入一些 UTF 16 格式的新表情符号时,就像这个家庭笑脸一样:http://www.unicode.org/Public/emoji/2.0//emoji-zwj-sequences.txt

或者不同肤色的笑脸。 unicode 被我的 ImageSpans 正确替换了。

当我在表情符号键盘上按下我的特殊后退按钮时,它会通过调用键事件删除完整的表情符号。

    mEmojiView.setOnEmojiconBackspaceClickedListener(new EmojiView.OnEmojiconBackspaceClickedListener() {
        @Override
        public void onEmojiconBackspaceClicked(View v) {
            KeyEvent event = new KeyEvent(
                    0, 0, 0, KeyEvent.KEYCODE_DEL, 0, 0, 0, 0, KeyEvent.KEYCODE_ENDCALL);
            send_text.dispatchKeyEvent(event);
        }
    });

但问题来了:

当我尝试按下默认键盘上的键盘退格键时,它会逐个字符地删除而不是整个表情符号。

并且由于较新的笑脸由多个 chars/unicode 替代品组成,我不得不多次按下退格按钮并面对一些奇怪的其他笑脸组合。

当我尝试通过长按 EditText 来 select 表情符号时,甚至会出现一些奇怪的行为,它只是 select 那个较长的表情符号的第一个字符,但标记了整个 ImageSpan .

有什么解决方法的建议吗?

这是因为软键盘的工作原理。他们中很少有人使用关键事件。他们更有可能使用 deleteSurroundingText 进行删除。您需要做的是为您的视图覆盖 inputConnection,并覆盖 deleteSurrounding 文本,这样如果删除区域中有表情符号,它会删除整个表情符号,而不仅仅是单个字符。

感谢 Gabe Sechan 的帮助。

这是一些对我有用的代码。随时添加改进。

修复自定义 EditText 中的选择:

LinkedList<EmojiData.EmojiTupel> mEmojis = new LinkedList<>();
boolean fromReselecting = false;
@Override
protected void onSelectionChanged(int selStart, int selEnd) {
    //Log.d("Selection changed", selStart + " " + selEnd + " length: "  + getText().length());
    if(!fromReselecting && selStart != selEnd){
        EmojiData.EmojiTupel toFix = inBetweenEmoji(selStart, selEnd);
        if(toFix != null){
                    fromReselecting = true;
                    setSelection(toFix.start, toFix.end);
        }
    }
    else{
        fromReselecting = false;
    }
}

private EmojiData.EmojiTupel inBetweenEmoji(int selStart, int selEnd){
    if(mEmojis == null){
        return null;
    }
    for (EmojiData.EmojiTupel tupel: mEmojis) {
        if((tupel.start < selStart && selEnd <= tupel.end) ||
                (tupel.start <= selStart && selEnd < tupel.end)
                ){
            //Log.d("InBetween ", "Selection: " + selStart + " " + selEnd + " Emoji: " + tupel.start + " "+ tupel.end );
            return tupel;
        }
    }
    return null;
}


@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
            mEmojis = EmojiUtils.insertEmojis(getContext(), getText(), mEmojiconSize);
}

还有在应该删除文本时使用 KeyEvents 的自定义 InputConnection。使用多个表情符号效果很好,因为选择是固定的。

@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
    return new EmojiInputConnection(super.onCreateInputConnection(outAttrs),
            true);
}

private class EmojiInputConnection extends InputConnectionWrapper {

    public EmojiInputConnection(InputConnection target, boolean mutable) {
        super(target, mutable);
    }


    @Override
    public boolean deleteSurroundingText(int beforeLength, int afterLength) {
        if (beforeLength == 1 && afterLength == 0) {
            return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
                    && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
        } else{
            int cursorPos = getSelectionStart();
            int cursorEnd = getSelectionEnd();
            if(cursorEnd == cursorPos && beforeLength == 2 && afterLength == 0){
                return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
                        && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
            }
        }

        return super.deleteSurroundingText(beforeLength, afterLength);
    }

}

其中 EmojiTupel 是来自另一个 class 的字符串中索引的包装器。

public static class EmojiTupel{
                public int start, end;
                public EmojiTupel(int start, int end){
                        this.start = start;
                        this.end = end;
                }
}