Android自定义软键盘

前不久由于项目的需要,要做一个自定义的软键盘,我也上网看了很多,都觉得很繁琐,所以想自己动手实现个。以备不时之需把。我选择了参考百度钱包的软键盘,看起来还不错:

这里写图片描述

下面一起来实现它:


1.写一个键盘控件,这个实现起来比较简单,就不多说了

public class SoftInputBoard extends RelativeLayout
        implements View.OnClickListener{

    private Scroller mScroller;
    private int mScreenHeigh = 0;
    private int mScreenWidth = 0;
    private Boolean isMoving = false;
    private int viewHeight = 0;
    public boolean isShow = false;
    public boolean mEnabled = true;
    public boolean mOutsideTouchable = true;
    private int mDuration = 800;

    private boolean userIsLongPress = false;

    @Override
    public void onClick(View v) {

        String value = "";

        int id = v.getId();
        if(id == R.id.tv0){
            value = "0";
        }else if(id == R.id.tv1){
            value = "1";
        }else if(id == R.id.tv2){
            value = "2";
        }else if(id == R.id.tv3){
            value = "3";
        }else if(id == R.id.tv4){
            value = "4";
        }else if(id == R.id.tv5){
            value = "5";
        }else if(id == R.id.tv6){
            value = "6";
        }else if(id == R.id.tv7){
            value = "7";
        }else if(id == R.id.tv8){
            value = "8";
        }else if(id == R.id.tv9){
            value = "9";
        }else if(id == R.id.iv_delete){
            value = "delete";
        }else if(id == R.id.tvx){
            value = "X";
        }

        if(payListener != null){
            payListener.chooseWay(value);
        }

    }

    public interface ChoosePayWayListener{
        void chooseWay(String value);
    }

    public ChoosePayWayListener payListener;

    public void setOnChoosePayWayListener(ChoosePayWayListener payListener){
        this.payListener = payListener;
    }

    private final static String TAG = "SoftInputView";
    public SoftInputBoard(Context context) {
        super(context);
        init(context);
    }

    public SoftInputBoard(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public SoftInputBoard(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }


    private void init(Context context) {
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        setFocusable(true);
        mScroller = new Scroller(context);
        mScreenHeigh = BaseTools.getWindowHeigh(context);
        mScreenWidth = BaseTools.getWindowWidth(context);
        this.setBackgroundColor(Color.argb(0, 0, 0, 0));
        final View view = LayoutInflater.from(context).inflate(R.layout.view_soft_input, null);


        TextView tv0 = (TextView)view.findViewById(R.id.tv0);
        TextView tv1 = (TextView)view.findViewById(R.id.tv1);
        TextView tv2 = (TextView)view.findViewById(R.id.tv2);
        TextView tv3 = (TextView)view.findViewById(R.id.tv3);
        TextView tv4 = (TextView)view.findViewById(R.id.tv4);
        TextView tv5 = (TextView)view.findViewById(R.id.tv5);
        TextView tv6 = (TextView)view.findViewById(R.id.tv6);
        TextView tv7 = (TextView)view.findViewById(R.id.tv7);
        TextView tv8 = (TextView)view.findViewById(R.id.tv8);
        TextView tv9 = (TextView)view.findViewById(R.id.tv9);
        TextView tvx = (TextView)view.findViewById(R.id.tvx);

        final RelativeLayout ivDelete = (RelativeLayout)view.findViewById(R.id.iv_delete);
        ivDelete.setClickable(true);

        tv0.setOnClickListener(this);
        tv1.setOnClickListener(this);
        tv2.setOnClickListener(this);
        tv3.setOnClickListener(this);
        tv4.setOnClickListener(this);
        tv5.setOnClickListener(this);
        tv6.setOnClickListener(this);
        tv7.setOnClickListener(this);
        tv8.setOnClickListener(this);
        tv9.setOnClickListener(this);
        tvx.setOnClickListener(this);

        ivDelete.setOnClickListener(this);
        ivDelete.setOnLongClickListener(new OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if(payListener != null){
                    payListener.chooseWay("xdelete");
                }
                return false;
            }
        });


        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);
        addView(view, params);

        this.setBackgroundColor(Color.argb(0, 0, 0, 0));
        view.post(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                viewHeight = view.getHeight();
            }
        });

        SoftInputBoard.this.scrollTo(0, mScreenHeigh);
        ImageView btn_close = (ImageView)view.findViewById(R.id.btn_close);
        btn_close.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                dismiss();
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(!mEnabled){
            return false;
        }
        return super.onInterceptTouchEvent(ev);
    }

    public void startMoveAnim(int startY, int dy, int duration) {
        isMoving = true;
        mScroller.startScroll(0, startY, 0, dy, duration);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
            isMoving = true;
        } else {
            isMoving = false;
        }
        super.computeScroll();
    }

    public void show(){
        if(!isShow && !isMoving){
            SoftInputBoard.this.startMoveAnim(-viewHeight,   viewHeight, mDuration);
            isShow = true;
            Log.d("isShow", "true");
            changed();
        }
    }

    public int getSoftInputBoardHeight(){
        return viewHeight;
    }

    public void dismiss(){
        if(isShow && !isMoving){
            SoftInputBoard.this.startMoveAnim(0, -viewHeight, mDuration);
            isShow = false;
            Log.d("isShow", "false");
            changed();
            if(l!=null){
                l.dismiss();
            }
        }
    }

    public interface dismissListener{
        void dismiss();
    }

    private dismissListener l;

    public void setOnDismissListener(dismissListener l){
        this.l = l;
    }

    public boolean isShow(){
        return isShow;
    }

    public boolean isSlidingEnabled() {
        return mEnabled;
    }

    public void setSlidingEnabled(boolean enabled) {
        mEnabled = enabled;
    }

    public void setOnStatusListener(onStatusListener listener){
        this.statusListener = listener;
    }

    public void setOutsideTouchable(boolean touchable) {
        mOutsideTouchable = touchable;
    }


    public void changed(){
        if(statusListener != null){
            if(isShow){
                statusListener.onShow();
            }else{
                statusListener.onDismiss();
            }
        }
    }

    public onStatusListener statusListener;

    public interface onStatusListener{
        public void onShow();
        public void onDismiss();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // TODO Auto-generated method stub
        super.onLayout(changed, l, t, r, b);
    }
}

其实,开发了这么久的android了,觉得Scoller这个类还是很重要的,可以帮助我们实现很多效果,比如这个控件的出现和消失。关于Scoller这个类还不熟悉的可以看我之前总结的文章 — 带你彻彻底底弄懂Scroller


2.第二步,也是我在开发中遇到的问题,就是如果我们的EditText太靠底端了,就是说,当我们的SoftInputBoard 调用show()方法的时候,会把用户填写的EditText给遮挡住,我们不是继承系统给我们的KeyBoard这个类,所以这个问题还是得我们自己解决。这里我还是用Scroller这类把SoftInputBoard这个控件的外布局给滑动到上面去:

/**
 * Created by Nipuream on 2016/4/15 0015.
 */
public class AutoPopLayout extends RelativeLayout {


    private Scroller mScroller;
    private boolean isMove = false;
    private SoftInputBoard softInputBoard;

    private Context context;

    private int currentCursorIndex = 0;

    private static final int ADD = 0x45;
    private static final int DE = 0x46;

    //默认是加
    private int addOrde = ADD;


    public AutoPopLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        DecelerateInterpolator interpolator = new DecelerateInterpolator();
        mScroller = new Scroller(context,interpolator);


        post(new Runnable() {
            @Override
            public void run() {
                softInputBoard = new SoftInputBoard(AutoPopLayout.this.context);
                RelativeLayout rl = (RelativeLayout) AutoPopLayout.this.getParent();
                rl.addView(softInputBoard);
                RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) softInputBoard.getLayoutParams();
                params.width = LayoutParams.MATCH_PARENT;
                params.height = LayoutParams.WRAP_CONTENT;
                params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
                softInputBoard.setLayoutParams(params);


                softInputBoard.setOnDismissListener(new SoftInputBoard.dismissListener() {
                    @Override
                    public void dismiss() {
                        AutoPopLayout.this.startMoveAnim(AutoPopLayout.this.getScrollY(),- pixDistance,500);
                        pixDistance = 0;
                    }
                });

                softInputBoard.setOnChoosePayWayListener(new SoftInputBoard.ChoosePayWayListener() {
                    @Override
                    public void chooseWay(String value) {

                        try{
                            String preStr =  ed.getText().toString();

                            //银行帐号
                            if(!TextUtils.equals(value,"delete")){

                                //长按删除
                                if(TextUtils.equals(value,"xdelete")){
                                    ed.setText("");
                                    currentCursorIndex = 0;
                                    return;
                                }
                                addOrde = ADD;
                                currentCursorIndex = getEditTextCursorIndex(ed);
                                insertText(ed,value);

                                preStr = ed.getText().toString();

                                ed.setText(preStr);
                            }else{
                                if(!TextUtils.isEmpty(preStr)){
                                    addOrde = DE;
                                    currentCursorIndex = getEditTextCursorIndex(ed);
                                    deleteText(ed);
                                    preStr = ed.getText().toString();
                                    ed.setText(preStr);
                                }
                            }
                        }catch (Exception e){
                        }
                    }
                });
            }
        });

    }


    /**获取EditText光标所在的位置*/
    private int getEditTextCursorIndex(EditText mEditText){
        return mEditText.getSelectionStart();
    }
    /**向EditText指定光标位置插入字符串*/
    private void insertText(EditText mEditText, String mText){
        mEditText.getText().insert(getEditTextCursorIndex(mEditText), mText);
    }
    /**向EditText指定光标位置删除字符串*/
    private void deleteText(EditText mEditText){
        if(!TextUtils.isEmpty(mEditText.getText().toString())){
            mEditText.getText().delete(getEditTextCursorIndex(mEditText)-1, getEditTextCursorIndex(mEditText));
        }
    }


    private int pixDistance = 0;


    private void AutoPilled(final EditText view){

        currentCursorIndex = 0;

        view.post(new Runnable() {
            @Override
            public void run() {
//                int screenHeight = UIUtil.getScreenHeight(getActivity().getWindowManager());
                int screenHeight = BaseTools.getWindowHeigh(context);
                int softInputHeight = softInputBoard.getSoftInputBoardHeight();

                /**
                 *
                 *
                 * ---------------------------------------------------  <---- screenHeight-softInputHeight
                 * |   ---------------------------                   |
                 * |  |         输入框            |                  |
                 * |  ----------------------------  <---- bottom    |
                 * |                                               |
                 * |                                               |
                 * ---------------------------------------------------
                 *
                 *
                 */

                int bottom = 0;
                Rect rect = new Rect();
                boolean isGet = view.getGlobalVisibleRect(rect);
                if(isGet){
                    bottom = rect.bottom;
                }

                if(bottom != 0) {
                    if (bottom > (screenHeight - softInputHeight)) {
                        pixDistance = bottom - (screenHeight - softInputHeight);
                        AutoPopLayout.this.startMoveAnim(AutoPopLayout.this.getScrollY(),pixDistance,500);
                    }
                }
            }
        });
    }

    private EditText ed;
    private WeakReference<Activity> ref;

    // 隐藏系统键盘
    public void hideSoftInputMethod(final List<EditText> ets, WeakReference<Activity> ref){

        this.ref = ref;
        currentCursorIndex = 0;


        for(final EditText ed:ets){
            ed.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {

//                focus = WhichFocus.BANK_CARD;
//                AutoPopLayout.this.hideSoftInputMethod(view,ref);
                    AutoPopLayout.this.ed = ed;

                    if(!softInputBoard.isShow){
                        softInputBoard.show();
                    }

                    if(! AutoPopLayout.this.isMove()){
                        if(softInputBoard.isShow){
                            AutoPilled(ed);
                        }
                    }

                    return false;
                }
            });

            ed.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                }
                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                }
                @Override
                public void afterTextChanged(Editable s) {
                    String value = s.toString();
                    int length = value.length();

                    int index = getEditTextCursorIndex(ed);

                    try{
                        if(currentCursorIndex < length){
                            if(addOrde == ADD){
                                ed.setSelection(currentCursorIndex+1);
                            }else{
                                ed.setSelection(currentCursorIndex-1);
                            }
                        }else{
                            ed.setSelection(length);
                        }
                    }catch (Exception e){
                    }
                }
            });

            ref.get().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);

            int currentVersion = android.os.Build.VERSION.SDK_INT;
            String methodName = null;
            if(currentVersion >= 16){
                // 4.2
                methodName = "setShowSoftInputOnFocus";
            }
            else if(currentVersion >= 14){
                // 4.0
                methodName = "setSoftInputShownOnFocus";
            }

            if(methodName == null){
                ed.setInputType(InputType.TYPE_NULL);
            }
            else{
                Class<EditText> cls = EditText.class;
                Method setShowSoftInputOnFocus;
                try {
                    setShowSoftInputOnFocus = cls.getMethod(methodName, boolean.class);
                    setShowSoftInputOnFocus.setAccessible(true);
                    setShowSoftInputOnFocus.invoke(ed, false);
                } catch (NoSuchMethodException e) {
                    ed.setInputType(InputType.TYPE_NULL);
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }


    public void destroyAutoPopLayout(){
        if(ref != null){
            ref = null;
            this.removeAllViews();
        }
    }


    public void startMoveAnim(int startY, int dy, int duration) {
        isMove = true;
        mScroller.startScroll(0, startY, 0, dy, duration);
        invalidate();//通知UI线程的更新
    }


    @Override
    public void computeScroll() {
        //判断是否还在滚动,还在滚动为true
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            //更新界面
            postInvalidate();
            isMove = true;
        } else {
            isMove = false;
        }
        super.computeScroll();
    }


    public boolean isMove(){
        return isMove;
    }



}

上面的AutoPopLayout 里面除了解决SoftInputBoard对EditText遮挡的问题,还实现了大量的对EditText处理的问题,包括我点击SoftInputBoard,讲键盘TextView中的内容,setText()到EditText上面去,当然,你可以对TextView的内容进行加密处理,然后传输到服务端去,不然我们自定义键盘也没有必要了,这里我这步操作就省略了。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.hr.nipurem.softinputdemo.MainActivity"
    >


    <com.hr.nipurem.softinputdemo.view.AutoPopLayout
        android:id="@+id/autoPopLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >


        <EditText
            android:id="@+id/auto_et1"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_centerInParent="true"
            />

        <EditText
            android:id="@+id/auto_et2"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_below="@+id/auto_et1"
            />


    </com.hr.nipurem.softinputdemo.view.AutoPopLayout>
</RelativeLayout>

可以看到,其实我在AutoPopLayout外面还嵌套了一层RelativeLayout,因为我们的SoftInputBoard不能在AutoPopLayout里面,不然,我们的SoftInputBoard就会随着我们的AutoPopLayout上移了。


3.第三步,我们来处理下对于EditText的处理,当然界面层上的EditText要传到我们这来,还需要个Activity的context,需要这个context去隐藏系统的软键盘

  // 隐藏系统键盘
    public void hideSoftInputMethod(final List<EditText> ets, WeakReference<Activity> ref){

        this.ref = ref;
        currentCursorIndex = 0;


        for(final EditText ed:ets){
            ed.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {

//                focus = WhichFocus.BANK_CARD;
//                AutoPopLayout.this.hideSoftInputMethod(view,ref);
                    AutoPopLayout.this.ed = ed;

                    if(!softInputBoard.isShow){
                        softInputBoard.show();
                    }

                    if(! AutoPopLayout.this.isMove()){
                        if(softInputBoard.isShow){
                            AutoPilled(ed);
                        }
                    }

                    return false;
                }
            });

            ed.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                }
                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                }
                @Override
                public void afterTextChanged(Editable s) {
                    String value = s.toString();
                    int length = value.length();

                    int index = getEditTextCursorIndex(ed);

                    try{
                        if(currentCursorIndex < length){
                            if(addOrde == ADD){
                                ed.setSelection(currentCursorIndex+1);
                            }else{
                                ed.setSelection(currentCursorIndex-1);
                            }
                        }else{
                            ed.setSelection(length);
                        }
                    }catch (Exception e){
                    }
                }
            });

            ref.get().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);

            int currentVersion = android.os.Build.VERSION.SDK_INT;
            String methodName = null;
            if(currentVersion >= 16){
                // 4.2
                methodName = "setShowSoftInputOnFocus";
            }
            else if(currentVersion >= 14){
                // 4.0
                methodName = "setSoftInputShownOnFocus";
            }

            if(methodName == null){
                ed.setInputType(InputType.TYPE_NULL);
            }
            else{
                Class<EditText> cls = EditText.class;
                Method setShowSoftInputOnFocus;
                try {
                    setShowSoftInputOnFocus = cls.getMethod(methodName, boolean.class);
                    setShowSoftInputOnFocus.setAccessible(true);
                    setShowSoftInputOnFocus.invoke(ed, false);
                } catch (NoSuchMethodException e) {
                    ed.setInputType(InputType.TYPE_NULL);
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

这里我们的EditText是集合的形式传递过来的,因为界面上可能有多个EditText,我们通过弱引用的形式传递过来了,因为会导致内存泄漏,当然,你在Activity的onDestory()里面去把AutoPopLayout的context置为null,也是可以的。上面隐藏的方式我也是从网友那里借鉴过来的,不同版本都有不同的处理方式。

隐藏系统的软键盘之后,还需要处理光标,虽然你可以设置ed.setCursorVisible(true),但是它总是会显示在EditText的最前面,不管你有没有输入文字,不信,你可以尝试下。那怎么办呢,我们貌似只有监听EditText的addText的方法了:

     ed.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                }
                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                }
                @Override
                public void afterTextChanged(Editable s) {
                    String value = s.toString();
                    int length = value.length();

                    int index = getEditTextCursorIndex(ed);

                    try{
                        if(currentCursorIndex < length){
                            if(addOrde == ADD){
                                ed.setSelection(currentCursorIndex+1);
                            }else{
                                ed.setSelection(currentCursorIndex-1);
                            }
                        }else{
                            ed.setSelection(length);
                        }
                    }catch (Exception e){
                    }
                }
            });

在这里我们处理了一些可能,主要为了光标能够随着文字而动,但是问题又来了,如果用户假如不小心输漏了一位怎么办?当用户把光标移动到某个位置的时候,我们还是处理下这个Bug。

   softInputBoard.setOnChoosePayWayListener(new SoftInputBoard.ChoosePayWayListener() {
                    @Override
                    public void chooseWay(String value) {

                        try{
                            String preStr =  ed.getText().toString();

                            //银行帐号
                            if(!TextUtils.equals(value,"delete")){

                                //长按删除
                                if(TextUtils.equals(value,"xdelete")){
                                    ed.setText("");
                                    currentCursorIndex = 0;
                                    return;
                                }
                                addOrde = ADD;
                                currentCursorIndex = getEditTextCursorIndex(ed);
                                insertText(ed,value);

                                preStr = ed.getText().toString();

                                ed.setText(preStr);
                            }else{
                                if(!TextUtils.isEmpty(preStr)){
                                    addOrde = DE;
                                    currentCursorIndex = getEditTextCursorIndex(ed);
                                    deleteText(ed);
                                    preStr = ed.getText().toString();
                                    ed.setText(preStr);
                                }
                            }
                        }catch (Exception e){
                        }
                    }
                });
            }
        });

在SoftInputBoard传递给我们值的时候,我们之前要确定下光标的位置,然后在去设置,当然,删除亦是如此。当然还有很多细节没有描述,虽然看起来有点烦躁,幸运的是,我把他们全封装好了,直接在MainActivity调用就ok了:

public class MainActivity extends AppCompatActivity {

    private AutoPopLayout autoPopLayout;
    private EditText et1;
    private EditText et2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        autoPopLayout = (AutoPopLayout)findViewById(R.id.autoPopLayout);
        et1 = (EditText)findViewById(R.id.auto_et1);
        et2 = (EditText)findViewById(R.id.auto_et2);

        List<EditText> ets = new ArrayList<EditText>();
        ets.add(et1);
        ets.add(et2);

        autoPopLayout.hideSoftInputMethod(ets,new WeakReference<Activity>(this));
    }


    @Override
    protected void onDestroy() {
        try{
            autoPopLayout.destroyAutoPopLayout();
        }catch (Exception e){
        }
        super.onDestroy();
    }
}

怎么样,是不是很简单!!!下面看看效果图:
这里写图片描述


apk下载试运行:
SoftInputBoard.apk


代码下载:
android自定义软键盘

 

posted @ 2016-05-05 16:06  Nipuream  阅读(1031)  评论(0编辑  收藏  举报