Android 自定义输入软键盘

版权声明:尊重原创,转载请附本文链接。谢谢合作! https://blog.csdn.net/DeMonliuhui/article/details/84589936

前言

在日常开发中,有的时候我们需要用户输入指定范围的内容,除了给与充分的文本提示,更加人性化的就是定制一个自定义键盘。
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 View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                //多条件判断,防止重复显示
                if (editText.hasFocus() && !helper.isVisibility() && event.getAction() == MotionEvent.ACTION_DOWN) {
                    helper.show();
                }
                return false;
            }
        });
    }
}

隐藏系统键盘的问题

为了只显示我们自定义的键盘,4.0以上系统,我们隐藏系统键盘时,也会使光标不见,原因及解决方案如下。

public class KeyboardUtil {
    //隐藏系统键盘
    //安卓4.0以下隐藏软键盘只需setInputType(InputType.TYPE_NULL)即可
    //4.0及以上,4.2以下需要调用setSoftInputShownOnFocus方法
    //4.2以上需要调用setShowSoftInputOnFocus方法
    //由于sdk的不一致,此处使用反射进行处理
    public static void hideSoftInput(EditText ed) {
        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 (Exception e) {
                ed.setInputType(InputType.TYPE_NULL);
                e.printStackTrace();
            }
        }
    }

    //显示被隐藏的系统键盘
    public static void showSoftInput(EditText ed) {
        InputMethodManager inputManager = (InputMethodManager) ed.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        inputManager.showSoftInput(ed, 0);
    }
}

代码

GitHub:https://github.com/CustomView-DeMon/TempKeyboard
CSDN: https://download.csdn.net/download/demonliuhui/10814092

参考

https://blog.csdn.net/flueky/article/details/80088255
https://blog.csdn.net/qq_29983773/article/details/79501658
https://blog.csdn.net/tianzhaoai/article/details/64130332

猜你喜欢

转载自blog.csdn.net/DeMonliuhui/article/details/84589936