1. 程式人生 > >仿支付寶密碼輸入以及細節總結

仿支付寶密碼輸入以及細節總結

網上已經有很多現成的輪子了,雖然說重複造輪子不好,但是對於初學者還是多寫多實現,瞭解原理最重要。

首先看下效果:

這裡寫圖片描述

需求

主要有以下幾個點:

  • 密碼輸入框由幾個方框組成

  • 當在軟鍵盤上輸入密碼時,在對應的方框內會用圓點作為代替

  • 當輸入完成以後,自動進行驗證和執行操作

  • 點選密碼框時自動彈出軟鍵盤,並且軟鍵盤是數字鍵盤,輸入其他字元無效,當焦點變化時自動隱藏軟鍵盤

  • 支援刪除操作

分析

有三種實現方式:

  • 在一個LinearLayout中佈局幾個方框,每個方框都是可編輯的,再對輸入監聽

  • 繼承自EditText,繪製方框並且去除下劃線等,對輸入監聽

  • 直接繼承自View,全定製,本文采用這種方法

對於方框本文采用先繪製整體的外圍矩形,再通過迴圈繪製每一格的分割線。

對於圓點,可以動態繪製,通過監聽輸入資料,獲取總的資料的大小,從而重繪。

對於刪除和軟鍵盤的彈入彈出,在下面邊附程式碼邊解釋。

程式碼詳解

方框的繪製

由於View中的方法很多都是返回的px,所以在View中去設定方框的長寬以及分隔線的大小並不好,建議在格式xml檔案中定義,然後在佈局xml中設定並賦值,在View的建構函式中匯入,這樣的設定耦合度小,也易於控制。

首先在ViewonSizeChanged()方法中獲取檢視的寬高,設定方框的大小:

    @Override
    protected
void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); textHeight = h; textWidth = w; rectF.set(0, 0, textWidth, textHeight); }

onDraw方法中繪製方框和分隔線:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRoundRect(rectF, rx, ry, textRectPaint);
        for
(int i=1; i<numberCount; i++){ canvas.drawLine(i*textWidth/numberCount, 0, i*textWidth/numberCount, textHeight, textRectPaint); } drawCircle(canvas); }

此外還有一個迴圈繪製圓點的方法,通過獲取資料的大小動態通知重繪:

    private void drawCircle(Canvas canvas){
        if (array.size()==0) {
            return;
        }
        for (int i=1; i<=array.size(); i++){
            canvas.drawCircle((float)((i-0.5)*textWidth/numberCount), textHeight/2, 20, pointPaint);
        }
    }

方框的繪製很簡單,主要就是注意檢視大小的獲取是在onSizeChanged方法中,該方法在檢視佈局變化時回撥,在ViewonMeasurelayout方法執行後會回撥。

此外也可以在layout方法中通過下面的方式獲取,注意必須在onMeasure方法之後呼叫

textWidth = getMeasuredWidth();
textHeight = getMeasuredHeight();

也可以在layout方法之後通過getWidth()getHeight()方法獲取檢視的長寬,具體原理見我的另一篇文章:《android中各種height和width總結》

軟鍵盤的觸控彈起和失去焦點隱藏

首先必須將該View設定為可獲得焦點並且在觸控模式下也是可以獲得焦點的,觸控模式下獲得焦點意味著在支援鍵盤輸入並且軟鍵盤輸入的時候該View仍然持有焦點,想要能夠獲取使用者輸入必須設定此屬性,在佈局xml檔案中新增如下兩行:

android:focusable="true"
android:focusableInTouchMode="true"

也可以在java程式碼中設定view.requestFocus()view.setFocusableInTouchMode(true),優先順序比xml中要高。

設定好了以後就可以獲取觸控事件彈起軟鍵盤了:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            input.showSoftInput(this, InputMethodManager.SHOW_FORCED);
            return true;
        }
        return super.onTouchEvent(event);
    }

input的初始化如下:

input = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);

上面的SHOW_FORCED意思是強制彈出軟鍵盤。下面看失去焦點後如何隱藏軟鍵盤。

這裡有兩種方法,一種是在根佈局中設定點選事件監聽器,如果點選事件發生的View為根佈局,就隱藏軟鍵盤,這種適合控制元件很少且不需要獲取單擊事件的情況,具體程式碼如下:

@Override  
public void onClick(View v) {  
    switch (v.getId()) {  
    case R.id.traceroute_rootview:  
         InputMethodManager imm = (InputMethodManager)  
         getSystemService(Context.INPUT_METHOD_SERVICE);  
         imm.hideSoftInputFromWindow(v.getWindowToken(), 0);  
        break;  
    }  
}  

第二種方法是通過Activity的事件分發機制,獲取當前焦點所在的View,也就是彈出軟鍵盤所在的View,判斷觸控事件是否發生在View的範圍以內,否就不處理,是就收起軟鍵盤。程式碼如下:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction()==MotionEvent.ACTION_DOWN) {
            touchView = getCurrentFocus();
            if (isShouldHideInput(touchView, ev)) {
                input = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                if (input!=null) {
                    input.hideSoftInputFromWindow(touchView.getWindowToken(), 0);
                }
            }
        }
        return super.dispatchTouchEvent(ev);
    }

    private boolean isShouldHideInput(View view, MotionEvent event){
        if (view!=null) {
            int[] locations = new int[2];
            view.getLocationInWindow(locations);
            int left = locations[0];
            int top = locations[1];
            int right = left+view.getWidth();
            int bottom = top+view.getHeight();
            if (event.getRawX()>=left && event.getRawX()<=right && event.getRawY()>=top && event.getRawY()<=bottom) {
                return false;
            }
            return true;
        }
        return false;
    }

注意此處用getRawX(),因為前面是獲取當前Window下的座標,所以不是相對於父容器的座標。執行完以後呼叫父類的方法,繼續分發事件,經驗證不影響其他控制元件的觸控事件。

限定彈出鍵盤的型別

為了更好的使用者體驗,彈出的鍵盤需要是數字鍵盤,避免使用者手動切換,程式碼如下:

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        outAttrs.inputType = InputType.TYPE_CLASS_NUMBER;
        outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
        return super.onCreateInputConnection(outAttrs);
    }

鍵盤輸入

彈出鍵盤以後,我們就可以輸入密碼了,但是我們還需要進行判斷,對不是數字的輸入不作響應。每次輸入一個數字,就將此數字加入到List中,一旦輸入完以後自動進行驗證和交易,這裡我就用一個Toast來代表驗證過程了。刪除可以通過監聽刪除鍵對List進行操作。具體監聽是繼承View.OnKeyListener類重寫onKey方法。程式碼如下:

    class MyOnKeyListener implements View.OnKeyListener{

        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (keyCode>=KeyEvent.KEYCODE_0 && keyCode<=KeyEvent.KEYCODE_9) {
                    if (list.size()<numberCount) {
                        list.add(keyCode-7);
                        invalidate();
                        if (list.size()==numberCount) {
                            ensureFinishInput();

                            //輸入結束後收起鍵盤
                            input.hideSoftInputFromWindow(MyEditText.this.getWindowToken(), 0);
                        }
                    }
                    return true;
                }
                if (keyCode==KeyEvent.KEYCODE_DEL) {
                    if (list.size()!=0) {
                        list.remove(list.size()-1);
                        invalidate();
                    }
                    return true;
                }
            }
            return false;
        }

        private void ensureFinishInput(){
            StringBuffer sb = new StringBuffer();
            sb.append(list.toString());
            Toast.makeText(getContext(),sb.toString(),Toast.LENGTH_SHORT).show();
        }
    }

總結一下,其實看上去挺簡單的,但是對於新手來說操作起來還是有很多細節的,不多說,繼續學習!