前言
在日常开发中,有的时候我们需要用户输入指定范围的内容,除了给与充分的文本提示,更加人性化的就是定制一个自定义键盘。
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>
注意
- 系统默认的几个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
- 当key中有keyOutputText属性时,点击键盘会触发监听函数的onText(CharSequence text) 方法。
- 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/MyAndroidDemo/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