前言

EditText是大家比较熟悉的输入框控件,我们长按,可以使用系统自带的复制粘贴功能;另外获得焦点后,也能和输入法进行交互,实现文字的输入!
如果一个EditText,没有设置任何style,我们想在粘贴显示之前,做一些逻辑上的操作,比如一段文字是粗体或者倾斜的,我想粘贴过来也是粗体或者倾斜,该怎么做!还有如果我们想在输入法输入文字时,就想让输入法输入的文字是粗体或者倾斜的展示在EditText上,又该如何处理!
下边就这两种情况,说一下我解决的方案!
我们知道EditText是继承于TextView的,它本身并没有多少代码,大部分逻辑都是在TextView中的!

定制粘贴

首先,TextView有一个回调方法:onTextContextMenuItem(int id),这个方法就是用来处理系统复制粘贴事件的!所以我们通过重写这一方法来实现定制!这里以设置文字为粗体为例:
我们新建类MyNormalEditText继承EditText

@Override
    public boolean onTextContextMenuItem(int id) {
        if (id == android.R.id.paste) {
            //只设置粘贴文本
            int lastCursorPosion=getSelectionStart();
            //拿到粘贴板的文本,setSpan的时候第二个参数last+文本的长度
            ClipboardManager clip = (ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE);
            String text=clip.getPrimaryClip().getItemAt(0).getText().toString();
            //之后,设置光标的时候,填这第二个参数即可
            super.onTextContextMenuItem(android.R.id.paste);
            SpannableString ss=new SpannableString(getText());
            //这里之所以分两种情况是因为android系统的粘贴,为了用户体验,会在粘贴的文本前后加上空格,表示是粘贴的内容
            //如果在文本中间粘贴,会在粘贴文本前后都加上空格;如果在文末粘贴,会在粘贴文本前加上空格;如果空的内容中粘贴,则不加空格
            if(lastCursorPosion!=0){
                ss.setSpan(new StyleSpan(Typeface.BOLD), lastCursorPosion+1, lastCursorPosion+1+text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                setText(ss);
                setSelection(lastCursorPosion+1+text.length());
            }else{
                ss.setSpan(new StyleSpan(Typeface.BOLD), lastCursorPosion, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                setText(ss);
                setSelection(text.length());
            }
            return true;
        }
        return super.onTextContextMenuItem(id);
    }

这里有几个需要注意的地方:
①每一个事件都有一个唯一的id对应,比如粘贴事件的idandroid.R.id.paste
②上边注释也有说明,就是android系统的粘贴,为了用户体验,会在粘贴的文本前后加上空格,表示是粘贴的内容!如果在文本中间粘贴,会在粘贴文本前后都加上空格;如果在文末粘贴,会在粘贴文本前加上空格;如果空的内容中粘贴,则不加空格。
关于这个问题,我们可以在源码中看到,可以参考下面这篇文章:关于EditText选择文字后粘贴出现空格的解决思路
③setSpan()方法的第三个参数,这个不是这篇文章的内容,这里就提一下,可以看一下这篇文章,对这4个不同的参数有一个区分:
android中用Spannable在TextView中设置超链接、颜色、字体
④EditText在调用setText()方法之后,光标会自动跳到最前边,所以最好在粘贴之后设置一下光标的位置!

上边是仅仅对粘贴的文本做了处理,如果输入框中本来有文字则不受影响,如果想粘贴完之后,全部都是粗体:

super.onTextContextMenuItem(android.R.id.paste);
SpannableString ss=new SpannableString(getText());
ss.setSpan(new StyleSpan(Typeface.BOLD), 0, getText().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
setText(ss);
return true;

定制输入

在Android中,输入法和输入框是通过InputConnection这一接口进行交互的,InputConnection算是两者的一个通信的桥梁!
TextView中有一个回调方法:onCreateInputConnection(EditorInfo outAttrs),它返回一个InputConnection对象,所以如果要定制输入,需要重写这一方法!另外需要返回一个InputConnection对象,所以我们要自定义一个类返回,这里我们继承InputConnectionWrapper,这个实现InputConnection接口的类即可!
至于为什么这样就可以了,为什么要继承InputConnectionWrapper,输入法输入的文本怎么展示到EditText上边的,这是android输入法框架的内容,这里就不详细阐述了!
想要了解的童鞋,可以参考这篇文章:
Android输入法框架系统(下)

好了,下边说一下实现方案,还是以设置粗体为例:
首先还是继承EditText,重写onCreateInputConnection()方法,

@Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        return new MyNormalEditText.MyInputConnection(super.onCreateInputConnection(outAttrs),false);
    }

    class MyInputConnection extends InputConnectionWrapper {

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

        @Override
        public boolean commitText(CharSequence text, int newCursorPosition) {
            SpannableString ss=new SpannableString(text);
            ss.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            super.commitText(ss, newCursorPosition); 
            return true;
        }
    }

需要注意的几个问题:
①MyInputConnection构造方法的第二个参数boolean mutable源码中,这一参数就这一个地方有用到,InputConnectionWrapper构造方法中将其赋给了全局变量mMutable:

/**
 * Change the target of the input connection.
 */
public void setTarget(InputConnection target) {
    if (mTarget != null && !mMutable) {
        throw new SecurityException("not mutable");
    }
    mTarget = target;
}

从字面意思来看Mutable,可变的,setTarget(InputConnection target)传入一个InputConnection ,就是说是否设置InputConnection 可变,我的理解就是一个EditText和输入法就是一个InputConnection,如果在构造方法中传入了一个false,就意味着这个连接不可变!
然而在我们这个功能中,这个参数好像并没有什么卵用啊!

同样的,如果想输入时,全部文本都是粗体的,在commitText()中应该这样写:

//如果想设置全部文本,则要这样写
super.commitText(text, newCursorPosition);
//注意这里cursor已经是下一个字的下标了
int lastCursorPosion = MyNormalEditText.this.getSelectionStart()-1;
SpannableString ss=new SpannableString(getText());
ss.setSpan(new StyleSpan(Typeface.BOLD), 0, getText().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
setText(ss);
//这里要设置一下光标的位置,是因为EditText#setText()方法后,光标会自动显示在最前边
MyNormalEditText.this.setSelection(lastCursorPosion+newCursorPosition);
return true;

因为,输入字之后,光标要在前一个输入的字的后,所以在setSelection()的时候,传入的位置要特别注意一下!

参考文章:
Android 开发之拦截EditText的输入内容,定制输入内容