1. 程式人生 > >Android 自定義輸入軟鍵盤

Android 自定義輸入軟鍵盤

前言

在日常開發中,有的時候我們需要使用者輸入指定範圍的內容,除了給與充分的文字提示,更加人性化的就是定製一個自定義鍵盤。
Android的自定義鍵盤常用於密碼輸入時的安全鍵盤,比如支付寶支付時。

這是一張網路圖片

如上圖,在輸入體溫時,彈出一個自定義的體溫鍵盤,這樣既能人性化服務,也能規避絕大多數非法數值的輸入。

實現

Keyboard

官方上對Keyboard的解釋:

載入鍵盤的XML描述並存儲鍵的屬性。
鍵盤由鍵行組成。
佈局檔案為鍵盤包含類似於以下程式碼段的XML。

屬性 型別 描述
keyHeight dimension/fractional Key高度,區分精確值(dp、px等)和相對值(%、%p)
keyWidth dimension/fractional Key寬度,同上
horizontalGap dimension/fractional Key水平間隙,同上
verticalGap dimension/fractional Key按鍵間隙(垂直),同上
Row
屬性 型別 描述
keyHeight dimension/fractional Key高度,區分精確值(dp、px等)和相對值(%、%p)
keyWidth dimension/fractional Key寬度,同上
horizontalGap dimension/fractional Key水平間隙,同上
verticalGap dimension/fractional Key按鍵間隙(垂直),同上
keyboardMode reference 鍵盤型別,如果該行的型別不符合鍵盤的型別,將跳過該行。
rowEdgeFlags enum 行邊界標記,top/bottom,鍵盤頂(底)部錨點。
Key
屬性 型別 描述
keyHeight dimension/fractional Key高度,區分精確值(dp、px等)和相對值(%、%p)
keyWidth dimension/fractional Key寬度,同上
horizontalGap dimension/fractional Key水平間隙,同上
verticalGap dimension/fractional Key按鍵間隙(垂直),同上
codes int Codes通常用來定義該鍵的鍵碼,按鍵對應的輸出值,可以為unicode值或則逗號(,)分割的多個值,也可以為一個字串。在字串中通過“\”來轉義特殊字元,例如 ‘\n’ 或則 ‘\uxxxx’ 。
iconPreview reference 彈出回顯的icon
isModifier boolean 是否功能修飾鍵,如:Alt/Shift
isSticky boolean 是否是開關按鍵
isRepeatable boolean 是否允許重複。true表示長按時重複執行。
keyEdgeFlags enum Key邊緣位置標記,left/right,鍵盤左(右)邊錨點。
keyIcon reference 替換label顯示在按鍵上的icon。
keyLabel reference 顯示在Key上的標籤。
keyOutputText string Key按下時輸出的字元或字串。
popupCharacters string 小鍵盤顯示的字元,用於顯示Key候選項。
popupKeyboard reference 按鍵候選小鍵盤的keyboard佈局。
keyboard_temp.xml
<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
    android:keyWidth="20%p"
    android:keyHeight="45dp">

    <Row>
        <Key
            android:keyLabel="33."
            android:keyOutputText="33." />
        <Key
            android:keyLabel="34."
            android:keyOutputText="34." />
        <Key
            android:codes="49"
            android:keyLabel="1" />
        <Key
            android:codes="50"
            android:keyLabel="2" />
        <Key
            android:codes="-3"
            android:keyIcon="@drawable/base_keyboard_hide" />
    </Row>

    <Row>
        <Key
            android:keyLabel="35."
            android:keyOutputText="35." />
        <Key
            android:keyLabel="36."
            android:keyOutputText="36." />
        <Key
            android:codes="51"
            android:keyLabel="3" />
        <Key
            android:codes="52"
            android:keyLabel="4" />
        <Key
            android:codes="-5"
            android:isRepeatable="true"
            android:keyIcon="@drawable/base_backspace" />
    </Row>
    <Row>
        <Key
            android:keyLabel="37."
            android:keyOutputText="37." />
        <Key
            android:keyLabel="38."
            android:keyOutputText="38." />
        <Key
            android:codes="53"
            android:keyLabel="5" />
        <Key
            android:codes="54"
            android:keyLabel="6" />
        <Key
            android:codes="-9"
            android:keyLabel="全選" />
    </Row>
    <Row>
        <Key
            android:keyLabel="39."
            android:keyOutputText="39." />
        <Key
            android:keyLabel="40."
            android:keyOutputText="40." />
        <Key
            android:codes="55"
            android:keyLabel="7" />
        <Key
            android:codes="56"
            android:keyLabel="8" />
        <Key
            android:codes="-7"
            android:keyLabel="上一項" />
    </Row>
    <Row>
        <Key
            android:keyLabel="41."
            android:keyOutputText="41." />
        <Key
            android:keyLabel="42."
            android:keyOutputText="42." />
        <Key
            android:codes="57"
            android:keyLabel="9" />
        <Key
            android:codes="48"
            android:keyLabel="0" />
        <Key
            android:codes="-8"
            android:keyLabel="下一項" />
    </Row>
</Keyboard>
注意
  1. 系統預設的幾個KeyCode,無需自定義。
    public static final int KEYCODE_SHIFT = -1; //shift
    public static final int KEYCODE_MODE_CHANGE = -2; //變換鍵盤
    public static final int KEYCODE_CANCEL = -3; //隱藏鍵盤
    public static final int KEYCODE_DONE = -4; //完成
    public static final int KEYCODE_DELETE = -5; //刪除
    public static final int KEYCODE_ALT = -6; //alt
  1. 當key中有keyOutputText屬性時,點選鍵盤會觸發監聽函式的onText(CharSequence text) 方法。
  2. codes屬性可以省略,預設使用keyLabel字元的Unicode值。功能鍵等其他自定義按鍵的keycode建議設定為第一無二的負數(為了不與預設及其他按鍵衝突,正數多為ASCll碼佔用)。

KeyboardView

屬性 型別 描述
keyBackground reference 按鍵的影象背景,必須包含多個狀態的drawable
verticalCorrection dimension 補充觸控y座標的偏移,用於偏差矯正
keyPreviewLayout reference 按鍵按下時預覽框的佈局
keyPreviewOffset dimension 按鍵按下時預覽框的偏移。>0 向下,<0 向上。
keyPreviewHeight dimension 按鍵按下時預覽框的高度。
keyTextSize dimension 按鍵文字大小。
keyTextColor color 按鍵文字顏色。
labelTextSize dimension 標籤文字大小,keylabel有多個字元且keycodes只有一個值時,該屬性生效。
popupLayout reference 按鍵候選小鍵盤的KeyboardView佈局。
shadowRadius float 按鍵文字陰影半徑
shadowColor color 按鍵文字陰影顏色,預設有陰影,無需陰影值為0即可
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:focusable="true"
    android:focusableInTouchMode="true">

    <EditText
        android:id="@+id/et"
        android:layout_width="match_parent"
        android:layout_height="50dp" />
    
    <android.inputmethodservice.KeyboardView
        android:id="@+id/keyboard_temp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:visibility="gone"
        android:background="@drawable/keyboard_background"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:keyBackground="@drawable/key_background"
        android:keyTextColor="#000000"
        android:paddingTop="2dp"
        android:paddingBottom="2dp"
        android:shadowRadius="0.0" />
</RelativeLayout>

配置及監聽

public class KeyboardTEMPHelper {
    public static final int LAST = -7;
    public static final int NEXT = -8;
    public static final int ALL = -9;
    private Context context;
    private KeyboardView keyboardView;
    private EditText editText; //顯示該鍵盤的EditText
    private Keyboard k1;// 自定義鍵盤
    private KeyboardCallBack callBack;//按鍵回撥監聽

    public KeyboardTEMPHelper(Context context, KeyboardView keyboardView) {
        this(context, keyboardView, null);
    }

    public KeyboardTEMPHelper(Context context, KeyboardView keyboardView, KeyboardCallBack callBack) {
        this.context = context;
        k1 = new Keyboard(context, R.xml.keyboard_temp);//據Keyboard的xml佈局繫結
        this.keyboardView = keyboardView;
        this.keyboardView.setOnKeyboardActionListener(listener);//設定鍵盤監聽
        this.keyboardView.setKeyboard(k1);//設定預設鍵盤
        this.keyboardView.setEnabled(true);
        this.keyboardView.setPreviewEnabled(false);
        this.callBack = callBack;
    }

    private KeyboardView.OnKeyboardActionListener listener = new KeyboardView.OnKeyboardActionListener() {

        @Override
        public void swipeUp() {
        }

        @Override
        public void swipeRight() {

        }

        @Override
        public void swipeLeft() {
        }

        @Override
        public void swipeDown() {
        }

        @Override
        public void onText(CharSequence text) {
            //當key中有keyOutputText屬性時,點選鍵盤會觸發該方法,回撥keyOutputText的值
            Editable editable = editText.getText();
            int end = editText.getSelectionEnd();
            editable.delete(0, end);
            editable.insert(0, text);
        }

        @Override
        public void onRelease(int primaryCode) {
        }

        @Override
        public void onPress(int primaryCode) {
        }

        @Override
        public void onKey(int primaryCode, int[] keyCodes) {
            //設定了codes屬性後,點選鍵盤會觸發該方法,回撥codes的值
            //codes值與ASCLL碼對應
            Editable editable = editText.getText();
            int start = editText.getSelectionStart();
            int end = editText.getSelectionEnd();
            switch (primaryCode) {
                case Keyboard.KEYCODE_DELETE:
                    if (editable != null && editable.length() > 0) {
                        if (start == end) {
                            editable.delete(start - 1, start);
                        } else {
                            editable.delete(start, end);
                        }
                    }
                    break;
                case Keyboard.KEYCODE_CANCEL:
                    keyboardView.setVisibility(View.GONE);
                    break;
                case ALL:
                    editText.selectAll();
                    break;
                case LAST:
                case NEXT:
                    break;
                default:
                    if (start != end) {
                        editable.delete(start, end);
                    }
                    editable.insert(start, Character.toString((char) primaryCode));
                    break;
            }
            if (callBack != null) {
                callBack.keyCall(primaryCode);
            }
        }
    };

    //在顯示鍵盤前應呼叫此方法,指定EditText與KeyboardView繫結
    public void setEditText(EditText editText) {
        this.editText = editText;
        //關閉進入該介面獲取焦點後彈出的系統鍵盤
        InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm != null) {
            imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
        }
        //隱藏該EditText獲取焦點而要彈出的系統鍵盤
        KeyboardUtil.hideSoftInput(editText);
    }

    //Activity中獲取焦點時呼叫,顯示出鍵盤
    public void show() {
        int visibility = keyboardView.getVisibility();
        if (visibility == View.GONE || visibility == View.INVISIBLE) {
            keyboardView.setVisibility(View.VISIBLE);
        }
    }

    //隱藏鍵盤
    public void hide() {
        int visibility = keyboardView.getVisibility();
        if (visibility == View.VISIBLE) {
            keyboardView.setVisibility(View.GONE);
        }
    }

    public boolean isVisibility() {
        if (keyboardView.getVisibility() == View.VISIBLE) {
            return true;
        } else {
            return false;
        }
    }

    public interface KeyboardCallBack {
        void keyCall(int code);
    }

    //設定回撥,用於自定義特殊按鍵在不同介面或EditText的處理
    public void setCallBack(KeyboardCallBack callBack) {
        this.callBack = callBack;
    }
}

使用

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private KeyboardTEMPHelper helper;
    private EditText editText;
    private KeyboardView keyboard;

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        keyboard = findViewById(R.id.keyboard_temp);
        editText = findViewById(R.id.et);
        //初始化KeyboardView
        helper = new KeyboardTEMPHelper(MainActivity.this, keyboard);
        //設定editText與KeyboardView繫結
        helper.setEditText(editText);
        helper.setCallBack(new KeyboardTEMPHelper.KeyboardCallBack() {
            @Override
            public void keyCall(int code) {
               //回撥鍵盤監聽,根據回撥的code值進行處理
            }
        });
        editText.setOnTouchListener(new