Android自定义安全键盘
在银行APP里经常要自定义键盘,例如实现下面这样的效果
首先在xml文件里定义键盘
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" android:keyWidth="30%p" android:horizontalGap="@dimen/keyboard_horizontalGap" android:verticalGap="@dimen/keyboard_verticalGap" android:keyHeight="47dp"> <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="-2" android:keyLabel="ABC" /> <Key android:codes="48" android:keyLabel="0" /> <Key android:codes="-35" android:isRepeatable="true"/> </Row> </Keyboard>
keyWidth:每一个按钮的宽度
keyHeight:每一个按钮高度,可以设置百分比
horizontalGap:水平间隔
verticalGap:竖直间隔
Row:一行
每一个按键都将会有一个 codes 值,代表键盘上的按键
KhKeyboardView
public class KhKeyboardView { private Activity mContext; private View parentView; private KeyboardView mLetterView; //字母键盘view private KeyboardView mNumberView; //数字键盘View private Keyboard mNumberKeyboard; // 数字键盘 private Keyboard mLetterKeyboard; // 字母键盘 private Keyboard mSymbolKeyboard; // 符号键盘 private boolean isNumber = true; // 是否数字键盘 public static boolean isUpper = false; // 是否大写 private boolean isSymbol = false; // 是否是符号 private EditText mEditText; private View headerView; public void setEditText(EditText text) { mEditText = text; } public KhKeyboardView(Activity context, View view) { mContext = context; parentView = view; mNumberKeyboard = new Keyboard(mContext, R.xml.keyboard_numbers); mLetterKeyboard = new Keyboard(mContext, R.xml.keyboard_word); mSymbolKeyboard = new Keyboard(mContext, R.xml.keyboard_symbol); mNumberView = (KeyboardView) parentView.findViewById(R.id.keyboard_view); mLetterView = (KeyboardView) parentView.findViewById(R.id.keyboard_view_2); mNumberView.setKeyboard(mNumberKeyboard); mNumberView.setEnabled(true); mNumberView.setPreviewEnabled(false); mNumberView.setOnKeyboardActionListener(listener); mLetterView.setKeyboard(mLetterKeyboard); mLetterView.setEnabled(true); mLetterView.setPreviewEnabled(true); mLetterView.setOnKeyboardActionListener(listener); headerView = parentView.findViewById(R.id.keyboard_header); } private KeyboardView.OnKeyboardActionListener listener = new KeyboardView.OnKeyboardActionListener() { /** * 按下,在onKey之前,可以在这里做一些操作,这里让有的没有按下的悬浮提示 * @param primaryCode */ @Override public void onPress(int primaryCode) { Log.d("primaryCode","onPress--"+primaryCode); if (primaryCode == Keyboard.KEYCODE_SHIFT) { mLetterView.setPreviewEnabled(false); } else if (primaryCode == Keyboard.KEYCODE_DELETE) { mLetterView.setPreviewEnabled(false); } else if (primaryCode == 32) { mLetterView.setPreviewEnabled(false); } else { mLetterView.setPreviewEnabled(true); } } /** * 松开 * @param primaryCode */ @Override public void onRelease(int primaryCode) { Log.d("primaryCode","onRelease--"+primaryCode); } /** * 按下 * @param primaryCode * @param keyCodes */ @Override public void onKey(int primaryCode, int[] keyCodes) { Log.d("primaryCode","onKey--"+primaryCode); try { if (mEditText == null) return; Editable editable = mEditText.getText(); int start = mEditText.getSelectionStart(); if (primaryCode == Keyboard.KEYCODE_CANCEL) { // 隐藏键盘 hideKeyboard(); } else if (primaryCode == Keyboard.KEYCODE_DELETE || primaryCode == -35) { // 回退键,删除字符 if (editable != null && editable.length() > 0) { if (start > 0) { editable.delete(start - 1, start); } } } else if (primaryCode == Keyboard.KEYCODE_SHIFT) { // 大小写切换 changeKeyboart(); mLetterView.setKeyboard(mLetterKeyboard); } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE) { // 数字与字母键盘互换 if (isNumber) { showLetterView(); showLetterView2(); } else { showNumberView(); } } else if (primaryCode == 90001) { // 字母与符号切换 if (isSymbol) { showLetterView2(); } else { showSymbolView(); } } else { // 输入键盘值 editable.insert(start, Character.toString((char) primaryCode)); } } catch (Exception e) { e.printStackTrace(); } } @Override public void onText(CharSequence text) { } @Override public void swipeLeft() { } @Override public void swipeRight() { } @Override public void swipeDown() { } @Override public void swipeUp() { } }; // 字母-符号,显示字母 private void showLetterView2() { if (mLetterView != null) { isSymbol = false; mLetterView.setKeyboard(mLetterKeyboard); } } // 字母-符号,显示符号 private void showSymbolView() { try { if (mLetterKeyboard != null) { isSymbol = true; mLetterView.setKeyboard(mSymbolKeyboard); } } catch (Exception e) { } } // 数字-字母,显示字母键盘 private void showLetterView() { try { if (mLetterView != null && mNumberView != null) { isNumber = false; mLetterView.setVisibility(View.VISIBLE); mNumberView.setVisibility(View.INVISIBLE); } } catch (Exception e) { e.printStackTrace(); } } // 数字-字母, 显示数字键盘 private void showNumberView() { try { if (mLetterView != null && mNumberView != null) { isNumber = true; mLetterView.setVisibility(View.INVISIBLE); mNumberView.setVisibility(View.VISIBLE); } } catch (Exception e) { e.printStackTrace(); } } /** * 切换大小写 */ private void changeKeyboart() { List<Keyboard.Key> keyList = mLetterKeyboard.getKeys(); if (isUpper) { // 大写切换小写 isUpper = false; for (Keyboard.Key key : keyList) { Drawable icon = key.icon; if (key.label != null && isLetter(key.label.toString())) { key.label = key.label.toString().toLowerCase(); key.codes[0] = key.codes[0] + 32; } } } else { // 小写切换成大写 isUpper = true; for (Keyboard.Key key : keyList) { if (key.label != null && isLetter(key.label.toString())) { key.label = key.label.toString().toUpperCase(); key.codes[0] = key.codes[0] - 32; } } } } /** * 判断是否是字母 */ private boolean isLetter(String str) { String wordStr = "abcdefghijklmnopqrstuvwxyz"; return wordStr.contains(str.toLowerCase()); } public void hideKeyboard() { try { int visibility = mLetterView.getVisibility(); if (visibility == View.VISIBLE) { headerView.setVisibility(View.GONE); mLetterView.setVisibility(View.GONE); } visibility = mNumberView.getVisibility(); if (visibility == View.VISIBLE) { headerView.setVisibility(View.GONE); mNumberView.setVisibility(View.GONE); } } catch (Exception e) { e.printStackTrace(); } } /** * 显示键盘 * * @param editText */ public void showKeyboard(EditText editText) { try { this.mEditText = editText; int visibility = 0; int inputText = mEditText.getInputType(); headerView.setVisibility(View.VISIBLE); switch (inputText) { case InputType.TYPE_CLASS_NUMBER: showNumberView(); break; case InputType.TYPE_CLASS_PHONE: showNumberView(); break; case InputType.TYPE_NUMBER_FLAG_DECIMAL: showNumberView(); break; default: showLetterView(); break; } } catch (Exception e) { e.printStackTrace(); } } }
布局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/rl_key" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#00000000" android:orientation="vertical"> <View android:id="@+id/keyboard_back_hide" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#7d7d7d" android:orientation="vertical"> <RelativeLayout android:id="@+id/keyboard_header" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="visible"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="智能安全加密键盘" android:textColor="#bfbfbf" android:textSize="15sp" /> <TextView android:id="@+id/keyboard_finish" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="10dp" android:padding="14dp" android:text="完成" android:textColor="#ffffff" android:textSize="15sp" /> </RelativeLayout> <ImageView android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginBottom="10dp" android:background="#555457" /> <FrameLayout android:id="@+id/keyboard_layer" android:layout_width="match_parent" android:layout_height="wrap_content"> <com.kh.keyboard.CustomKeyboardView android:id="@+id/keyboard_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#7d7d7d" android:focusable="true" android:focusableInTouchMode="true" android:keyBackground="@drawable/keyboard_number_selector_bg" android:keyPreviewLayout="@null" android:keyTextColor="#ffffff" android:visibility="gone" /> <com.kh.keyboard.CustomKeyboardView android:id="@+id/keyboard_view_2" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#7d7d7d" android:focusable="true" android:focusableInTouchMode="true" android:keyBackground="@drawable/keyboard_selector_bg" android:keyPreviewHeight="90dp" android:keyPreviewLayout="@layout/keyboard_key_preview_layout" android:keyPreviewOffset="45dp" android:keyTextColor="#ffffff" android:visibility="gone" /> </FrameLayout> </LinearLayout> </RelativeLayout>
keyPreviewLayout就是点击时键盘按键上的悬浮效果
这里自定义了KeyboardView,因为我需要按钮的背景颜色不一样,而使用keyBackground都是一样的
public class CustomKeyboardView extends KeyboardView { private Context context; public CustomKeyboardView(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); try { List<Keyboard.Key> keys = getKeyboard().getKeys(); for (Keyboard.Key key : keys) { // Log.e("KEY", "Drawing key with code " + key.codes[0]); if (key.codes[0] == -5) { Drawable dr = (Drawable) context.getResources().getDrawable(R.drawable.keyboard_word_del_layerlist); dr.setBounds(key.x, key.y, key.x + key.width, key.y + key.height); dr.draw(canvas); } else if (key.codes[0] == -35) { Drawable dr = (Drawable) context.getResources().getDrawable(R.drawable.keyboard_word_del_layerlist2); dr.setBounds(key.x, key.y, key.x + key.width, key.y + key.height); dr.draw(canvas); } else if (key.codes[0] == -1) { Drawable dr = (Drawable) context.getResources().getDrawable(R.drawable.keyboard_word_shift_layerlist); Drawable dr_da = (Drawable) context.getResources().getDrawable(R.drawable.keyboard_word_shift_layerlist_da); dr.setBounds(key.x, key.y, key.x + key.width, key.y + key.height); dr_da.setBounds(key.x, key.y, key.x + key.width, key.y + key.height); if (KhKeyboardView.isUpper) { dr_da.draw(canvas); } else { dr.draw(canvas); } } else if (key.codes[0] == -2 || key.codes[0] == 90001) { Drawable dr = (Drawable) context.getResources().getDrawable(R.drawable.keyboard_selector_blue_bg); dr.setBounds(key.x, key.y, key.x + key.width, key.y + key.height); dr.draw(canvas); drawText(canvas, key); } else { } } } catch (Exception e) { e.printStackTrace(); } } private void drawText(Canvas canvas, Keyboard.Key key) { try { Rect bounds = new Rect(); Paint paint = new Paint(); paint.setTextAlign(Paint.Align.CENTER); paint.setAntiAlias(true); paint.setColor(Color.WHITE); if (key.label != null) { String label = key.label.toString(); Field field; if (label.length() > 1 && key.codes.length < 2) { int labelTextSize = 0; try { field = KeyboardView.class.getDeclaredField("mLabelTextSize"); field.setAccessible(true); labelTextSize = (int) field.get(this); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } paint.setTextSize(labelTextSize); paint.setTypeface(Typeface.DEFAULT_BOLD); } else { int keyTextSize = 0; try { field = KeyboardView.class.getDeclaredField("mLabelTextSize"); field.setAccessible(true); keyTextSize = (int) field.get(this); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } paint.setTextSize(keyTextSize); paint.setTypeface(Typeface.DEFAULT); } paint.getTextBounds(key.label.toString(), 0, key.label.toString() .length(), bounds); canvas.drawText(key.label.toString(), key.x + (key.width / 2), (key.y + key.height / 2) + bounds.height() / 2, paint); } else if (key.icon != null) { key.icon.setBounds(key.x + (key.width - key.icon.getIntrinsicWidth()) / 2, key.y + (key.height - key.icon.getIntrinsicHeight()) / 2, key.x + (key.width - key.icon.getIntrinsicWidth()) / 2 + key.icon.getIntrinsicWidth(), key.y + (key.height - key.icon.getIntrinsicHeight()) / 2 + key.icon.getIntrinsicHeight()); key.icon.draw(canvas); } } catch (Exception e) { e.printStackTrace(); } } }
最后
还需要一个工具类来显示自定义的键盘,这里我使用了dialog
public class KeyBoardDialogUtils implements View.OnClickListener { protected View view; protected Dialog popWindow; protected Activity mContext; private EditText contentView; private List<String> contentList; private KhKeyboardView keyboardUtil; public KeyBoardDialogUtils(Activity mContext) { try { this.mContext = mContext; if (contentList == null) { contentList = new ArrayList<>(); } if (popWindow == null) { view = LayoutInflater.from(mContext).inflate(R.layout.keyboard_key_board_popu, null); view.findViewById(R.id.keyboard_finish).setOnClickListener(this); view.findViewById(R.id.keyboard_back_hide).setOnClickListener(this); } popWindow.setContentView(view); popWindow.setCanceledOnTouchOutside(true); Window mWindow = popWindow.getWindow(); mWindow.setWindowAnimations(R.style.keyboard_popupAnimation); mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); mWindow.setGravity(Gravity.BOTTOM | Gravity.FILL_HORIZONTAL); mWindow.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); popWindow.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { if (contentView != null && contentView.isFocused()) { contentView.clearFocus(); } } }); initView(); } catch (Exception e) { e.printStackTrace(); } } private void initView() { try { if (keyboardUtil == null) keyboardUtil = new KhKeyboardView(mContext, view); } catch (Exception e) { e.printStackTrace(); } } public void show(final EditText editText) { editText.setFocusable(true); editText.setFocusableInTouchMode(true); editText.requestFocus(); popWindow.show(); keyboardUtil.showKeyboard(editText); } public void dismiss() { if (popWindow != null && popWindow.isShowing()) { popWindow.dismiss(); } } @Override public void onClick(View v) { try { int i = v.getId(); if (i == R.id.keyboard_finish) { keyboardUtil.hideKeyboard(); dismiss(); } else if (i == R.id.keyboard_back_hide) { keyboardUtil.hideKeyboard(); dismiss(); } } catch (Exception e) { e.printStackTrace(); } } }
使用
et = (EditText) findViewById(R.id.et); keyBoardDialogUtils = new KeyBoardDialogUtils(this); et.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { keyBoardDialogUtils.show(et); } });
注意这里点击会先弹出系统键盘,因为弹出键盘会先于keyBoardDialogUtils.show(et)执行,所以设置EditText的focusableInTouchMode="false",在keyutil里我们再把它设为true。
项目源码: