Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義軟鍵盤

Android自定義軟鍵盤

編輯:關於Android編程

前不久由於項目的需要,要做一個自定義的軟鍵盤,我也上網看了很多,都覺得很繁瑣,所以想自己動手實現個。以備不時之需把。我選擇了參考百度錢包的軟鍵盤,看起來還不錯:

這裡寫圖片描述vc+88rWlo6y+zbK7tuDLtcHLPC9wPg0KPHByZSBjbGFzcz0="brush:java;"> 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 ref;

    // 隱藏系統鍵盤
    public void hideSoftInputMethod(final List ets, WeakReference 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 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的內容進行加密處理,然後傳輸到服務端去,不然我們自定義鍵盤也沒有必要了,這裡我這步操作就省略了。





    


        

        


    

可以看到,其實我在AutoPopLayout外面還嵌套了一層RelativeLayout,因為我們的SoftInputBoard不能在AutoPopLayout裡面,不然,我們的SoftInputBoard就會隨著我們的AutoPopLayout上移了。


3.第三步,我們來處理下對於EditText的處理,當然界面層上的EditText要傳到我們這來,還需要個Activity的context,需要這個context去隱藏系統的軟鍵盤

  // 隱藏系統鍵盤
    public void hideSoftInputMethod(final List ets, WeakReference 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 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 ets = new ArrayList();
        ets.add(et1);
        ets.add(et2);

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


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

怎麼樣,是不是很簡單!!!下面看看效果圖:
這裡寫圖片描述

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved