先来看下截图
有时需要输入验证码或者数字密码的地方,为了安全和便捷,应用内通常会自定义一个数字键盘,来帮助用户完成输入。
那么我们来自己动手撸一个键盘出来;
1.首先在res目录里新建xml目录,然后在xml目录中新建num_keyboard.xml:
<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:horizontalGap="1dp"
android:keyHeight="8%p"
android:keyWidth="33.33333%p"
android:verticalGap="1dp">
<Row>
<Key
android:codes="49"
android:keyLabel="1" />
<Key
android:codes="50"
android:keyLabel="2" />
<Key
android:codes="51"
android:keyLabel="3" />
</Row>
<Row>
<Key
android:codes="52"
android:keyLabel="4" />
<Key
android:codes="53"
android:keyLabel="5" />
<Key
android:codes="54"
android:keyLabel="6" />
</Row>
<Row>
<Key
android:codes="55"
android:keyLabel="7" />
<Key
android:codes="56"
android:keyLabel="8" />
<Key
android:codes="57"
android:keyLabel="9" />
</Row>
<Row>
<Key
android:codes="-10"
android:keyLabel="取消" />
<Key
android:codes="48"
android:keyLabel="0" />
<Key
android:codes="-5"
android:keyIcon="@android:color/transparent" />
</Row>
</Keyboard>
上面代码中,定义了KeyBoard 的按键排位和键值,keyLabl等参数;
2.自定义属性
在res/values/attrs.xml 中新增如下内容, 没有的话就创建这个文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NumberKeyboardView">
<!-- 删除按键的图标 -->
<attr name="xnkv_deleteDrawable" format="reference" />
<!-- 删除按键图标的宽度 -->
<attr name="xnkv_deleteWidth" format="dimension|reference" />
<!-- 删除按键图标的高度 -->
<attr name="xnkv_deleteHeight" format="dimension|reference" />
<!-- 删除按键图标的颜色 -->
<attr name="xnkv_deleteBackgroundColor" format="color|reference" />
</declare-styleable>
</resources>
3.自定义KeyBoardView
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.util.AttributeSet;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* 自定义数字键盘
* kpl
*/
public class NumberKeyboardView extends KeyboardView implements KeyboardView.OnKeyboardActionListener {
// 用于区分左下角空白的按键
private static final int KEYCODE_EMPTY = -10;
private int mDeleteWidth;
private int mDeleteHeight;
private int mDeleteBackgroundColor;
private Drawable mDeleteDrawable;
private Rect mDeleteDrawRect;
private IOnKeyboardListener mOnKeyboardListener;
private boolean showCancel = true;
public NumberKeyboardView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public NumberKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NumberKeyboardView,
defStyleAttr, 0);
mDeleteDrawable = a.getDrawable(R.styleable.NumberKeyboardView_xnkv_deleteDrawable);
mDeleteBackgroundColor = a.getColor(
R.styleable.NumberKeyboardView_xnkv_deleteBackgroundColor, Color.TRANSPARENT);
mDeleteWidth = a.getDimensionPixelOffset(R.styleable.NumberKeyboardView_xnkv_deleteWidth,
-1);
mDeleteHeight = a.getDimensionPixelOffset(R.styleable.NumberKeyboardView_xnkv_deleteHeight,
-1);
a.recycle();
// 设置软键盘按键的布局
Keyboard keyboard = new Keyboard(context, R.xml.keybord_num_layout);
setKeyboard(keyboard);
setEnabled(true);
setPreviewEnabled(false); // 设置按键没有点击放大镜显示的效果
setOnKeyboardActionListener(this);
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 遍历所有的按键
List<Keyboard.Key> keys = getKeyboard().getKeys();
for (Keyboard.Key key : keys) {
// 如果是左下角空白的按键,重画按键的背景
if (key.codes[0] == KEYCODE_EMPTY) {
if (showCancel) {
key.label = "取消";
} else {
drawKeyBackground(key, canvas, mDeleteBackgroundColor);
}
}
// 如果是右下角的删除按键,重画按键的背景,并且绘制删除图标
else if (key.codes[0] == Keyboard.KEYCODE_DELETE) {
drawKeyBackground(key, canvas, mDeleteBackgroundColor);
drawDeleteButton(key, canvas);
}
}
}
// 绘制按键的背景
private void drawKeyBackground(Keyboard.Key key, Canvas canvas, int color) {
ColorDrawable drawable = new ColorDrawable(color);
drawable.setBounds(key.x, key.y, key.x + key.width, key.y + key.height);
drawable.draw(canvas);
}
public void setShowCancelButton(boolean isShow) {
this.showCancel = isShow;
invalidate();
}
/**
* 设置返回按键图标
*
* @param icon
*/
public void setBackKeyIcon(Drawable icon) {
mDeleteDrawable = icon;
invalidate();
}
// 绘制删除按键
private void drawDeleteButton(Keyboard.Key key, Canvas canvas) {
if (mDeleteDrawable == null) {
return;
}
// 计算删除图标绘制的坐标
if (mDeleteDrawRect == null || mDeleteDrawRect.isEmpty()) {
int drawWidth, drawHeight;
int intrinsicWidth = mDeleteDrawable.getIntrinsicWidth();
int intrinsicHeight = mDeleteDrawable.getIntrinsicHeight();
if (mDeleteWidth > 0 && mDeleteHeight > 0) {
drawWidth = mDeleteWidth;
drawHeight = mDeleteHeight;
} else if (mDeleteWidth > 0 && mDeleteHeight <= 0) {
drawWidth = mDeleteWidth;
drawHeight = drawWidth * intrinsicHeight / intrinsicWidth;
} else if (mDeleteWidth <= 0 && mDeleteHeight > 0) {
drawHeight = mDeleteHeight;
drawWidth = drawHeight * intrinsicWidth / intrinsicHeight;
} else {
drawWidth = intrinsicWidth;
drawHeight = intrinsicHeight;
}
// 限制图标的大小,防止图标超出按键
if (drawWidth > key.width) {
drawWidth = key.width;
drawHeight = drawWidth * intrinsicHeight / intrinsicWidth;
}
if (drawHeight > key.height) {
drawHeight = key.height;
drawWidth = drawHeight * intrinsicWidth / intrinsicHeight;
}
// 获取删除图标绘制的坐标
int left = key.x + (key.width - drawWidth) / 2;
int top = key.y + (key.height - drawHeight) / 2;
mDeleteDrawRect = new Rect(left, top, left + drawWidth, top + drawHeight);
}
// 绘制删除的图标
if (mDeleteDrawRect != null && !mDeleteDrawRect.isEmpty()) {
mDeleteDrawable.setBounds(mDeleteDrawRect.left, mDeleteDrawRect.top,
mDeleteDrawRect.right, mDeleteDrawRect.bottom);
mDeleteDrawable.draw(canvas);
}
}
@Override
public void onKey(int primaryCode, int[] keyCodes) {
// 处理按键的点击事件
// 点击了删除按键
if (primaryCode == Keyboard.KEYCODE_DELETE) {
if (mOnKeyboardListener != null)
mOnKeyboardListener.onDeleteKeyEvent();
}
// 点击了数字按键
else if (primaryCode != KEYCODE_EMPTY) {
if (mOnKeyboardListener != null) {
mOnKeyboardListener.onInsertKeyEvent(Character.toString(
(char) primaryCode));
}
} else if (showCancel && primaryCode == KEYCODE_EMPTY) {
if (mOnKeyboardListener != null)
mOnKeyboardListener.onCancel();
}
}
// 0-9 数字的 Character 值
private final List<Character> keyCodes = Arrays.asList('0', '1', '2', '3', '4', '5', '6', '7','8','9');
/**
* 随机打乱数字键盘上键位的排列顺序。
*/
public void shuffleKeyboard() {
Keyboard keyboard = getKeyboard();
if (keyboard != null && keyboard.getKeys() != null && keyboard.getKeys().size() > 0) {
Collections.shuffle(keyCodes); // 随机排序数字
// 遍历所有的按键
List<Keyboard.Key> keys = getKeyboard().getKeys();
int index = 0;
for (Keyboard.Key key : keys) {
// 如果按键是数字
if (key.codes[0] != KEYCODE_EMPTY && key.codes[0] != Keyboard.KEYCODE_DELETE) {
char code = keyCodes.get(index++);
key.codes[0] = code;
key.label = Character.toString(code);
}
}
setKeyboard(keyboard);
}
}
@Override
public void onPress(int primaryCode) {
}
@Override
public void onRelease(int primaryCode) {
}
@Override
public void onText(CharSequence text) {
}
@Override
public void swipeLeft() {
}
@Override
public void swipeRight() {
}
@Override
public void swipeDown() {
}
@Override
public void swipeUp() {
}
/**
* 设置键盘的监听事件。
*
* @param listener 监听事件
*/
public void setIOnKeyboardListener(IOnKeyboardListener listener) {
this.mOnKeyboardListener = listener;
}
/**
* 键盘的监听事件。
*/
public interface IOnKeyboardListener {
/**
* 点击数字按键。
*
* @param text 输入的数字
*/
void onInsertKeyEvent(String text);
/**
* 点击了删除按键。
*/
void onDeleteKeyEvent();
/**
* 点击了取消键
*/
void onCancel();
}
}
4.自定义包裹键盘的layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00000000"
android:orientation="vertical"
>
<View
android:id="@+id/view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#00000000" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#33000000"
/>
<com.kpl.test.numkeybordlib.NumberKeyboardView xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/view_keyboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#b0b0b0"
android:focusable="true"
android:focusableInTouchMode="true"
android:keyBackground="@drawable/selector_key_background"
android:keyTextColor="#000000"
android:shadowColor="@android:color/transparent"
android:shadowRadius="0"
app:xnkv_deleteBackgroundColor="#d2d2d2"
app:xnkv_deleteDrawable="@drawable/back"
app:xnkv_deleteWidth="22dp" />
</LinearLayout>
当然,布局是一个match_parent 的LinearLayout ,目的是为了使键盘能该在满界面的最前端, 除了键盘view , 还有两个view控件,其中一个完全透明, 用来铺满除键盘布局外剩余的屏幕空间;
android:keyBackground="@drawable/selector_key_background"
这里给键盘设置了状态选择器当背景,代码如下
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/holo_blue_dark" android:state_pressed="true" />
<item android:drawable="@android:color/white" />
</selector>
其他的还有几个自定义的属性,用来配置删除按钮;
5.NumberKeyboardLayout 对键盘的封装, 外部使用键盘的时候,只需要使用这个对象即可;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
/**
* kpl 自定义数字键盘 ,对外使用
*/
public class NumberKeyboardLayout extends LinearLayout {
private NumberKeyboardView keyboardView;
private View view;
public NumberKeyboardLayout(Context context) {
super(context);
init(context);
}
public NumberKeyboardLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public NumberKeyboardLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
View inflate = LayoutInflater.from(context).inflate(R.layout.keyboard_view, this, true);
keyboardView = inflate.findViewById(R.id.view_keyboard);
view = inflate.findViewById(R.id.view);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
hide();
}
});
}
public void hide() {
this.setVisibility(GONE);
}
public void show(Context context) {
//
this.setVisibility(VISIBLE);
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
// 强制隐藏软键盘
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
//设置删除按钮的图标
public void setBackKeyIcon(Drawable icon) {
keyboardView.setBackKeyIcon(icon);
}
//打乱键盘的按键顺序,并重新绘制键盘
public void shuffleKeyboard() {
keyboardView.shuffleKeyboard();
}
//设置键盘的按键监听
public void setIOnKeyboardListener(NumberKeyboardView.IOnKeyboardListener listener) {
keyboardView.setIOnKeyboardListener(listener);
}
}
6.使用
布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<EditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:onClick="bt"
android:text="aaa"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
这里键盘的控件是直接通过new 一个NumberKeyboardLayout 对象,加在了Activity的布局中
numberKeyboardLayout = new NumberKeyboardLayout(this);
numberKeyboardLayout.setIOnKeyboardListener(this);
addContentView(numberKeyboardLayout, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
至此,数字小键盘就撸完了
最后,送上两个隐藏系统软键盘的工具类
/**
* Hide the soft input.
*
* @param activity The activity.
*/
public static void hideSoftInput( Activity activity) {
InputMethodManager imm =
(InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
if (imm == null) return;
View view = activity.getCurrentFocus();
if (view == null) view = new View(activity);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
/**
* Hide the soft input.
*
* @param view The view.
*/
public static void hideSoftInput(Context context, View view) {
InputMethodManager imm =
(InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
//noinspection ConstantConditions
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
gitHub: