本篇文章将会介绍3种android自定义软键盘的应用场景以及代码编写,分别是:普通的自定义软键盘;每次弹出都会改变数字顺序的自定义密码键盘;能与webview交互的自定义密码键盘。以上说的这三种其实本质上没有太大区别,只是细节调用和写法上有略微区别,我们项目里用到是最后一种,但是网上对最后一种的介绍很少,这三种自定义软键盘的介绍顺序也是我的推导顺序,第一种是基础,有了基础后续推导会容易很多,如果你暂时用不到后面的可以只看第一种,有些印象就好。
一、普通基础自定义键盘
1.动态效果图:
2.设计思路:
- 首先在res下创建xml目录,在xml中定义数字键盘和英文键盘;
- 自定义EditText继承EditText并且实现KeyboardView.OnKeyboardActionListener接口用来监听软键盘的点击事件,从而控制软键盘的切换、预览以及输入框的内容变化;
- 判断软键盘弹出是否会遮挡输入框,如果会则使得页面上移到不会遮挡为止,软键盘隐藏时注意页面下移相同距离。
3.软键盘的相关属性介绍:
- key的相关属性:
- row的相关属性:
- KeyboardView的相关属性:
- ASCII码对应表:
4.字母键盘的xml文件编写:
<?xml version="1.0" encoding="utf-8"?> <Keyboard xmlns:android="http://schemas.android.com/apk/res/android" android:keyWidth="8%p" android:keyHeight="7.5%p" android:verticalGap="0px" android:horizontalGap="0px"> <Row android:verticalGap="1%p"> <Key android:codes="113" android:keyLabel="q" android:horizontalGap="1.81%p"> </Key> <Key android:codes="119" android:keyLabel="w" android:horizontalGap="1.81%p"> </Key> <Key android:codes="101" android:keyLabel="e" android:horizontalGap="1.81%p"> </Key> <Key android:codes="114" android:keyLabel="r" android:horizontalGap="1.81%p"> </Key> <Key android:codes="116" android:keyLabel="t" android:horizontalGap="1.81%p"> </Key> <Key android:codes="121" android:keyLabel="y" android:horizontalGap="1.81%p"> </Key> <Key android:codes="117" android:keyLabel="y" android:horizontalGap="1.81%p"> </Key> <Key android:codes="105" android:keyLabel="i" android:horizontalGap="1.81%p"> </Key> <Key android:codes="111" android:keyLabel="o" android:horizontalGap="1.81%p"> </Key> <Key android:codes="112" android:keyLabel="p" android:horizontalGap="1.81%p"> </Key> </Row> <Row android:verticalGap="1%p"> <Key android:codes="97" android:keyLabel="a" android:keyWidth="9%p" android:horizontalGap="5.5%p"> </Key> <Key android:codes="115" android:keyLabel="s" android:keyWidth="9%p" android:horizontalGap="1%p"> </Key> <Key android:codes="100" android:keyLabel="d" android:keyWidth="9%p" android:horizontalGap="1%p"> </Key> <Key android:codes="102" android:keyLabel="f" android:keyWidth="9%p" android:horizontalGap="1%p"> </Key> <Key android:codes="103" android:keyLabel="g" android:keyWidth="9%p" android:horizontalGap="1%p"> </Key> <Key android:codes="104" android:keyLabel="h" android:keyWidth="9%p" android:horizontalGap="1%p"> </Key> <Key android:codes="106" android:keyLabel="j" android:keyWidth="9%p" android:horizontalGap="1%p"> </Key> <Key android:codes="107" android:keyLabel="k" android:keyWidth="9%p" android:horizontalGap="1%p"> </Key> <Key android:codes="108" android:keyLabel="l" android:keyWidth="9%p" android:horizontalGap="1%p"> </Key> </Row> <Row android:verticalGap="1%p"> <Key android:codes="-1" android:keyLabel="大写" android:keyWidth="17%p" android:horizontalGap="1%p"> </Key> <Key android:codes="122" android:keyLabel="z" android:horizontalGap="1%p"> </Key> <Key android:codes="120" android:keyLabel="x" android:horizontalGap="1%p"> </Key> <Key android:codes="99" android:keyLabel="c" android:horizontalGap="1%p"> </Key> <Key android:codes="118" android:keyLabel="v" android:horizontalGap="1%p"> </Key> <Key android:codes="98" android:keyLabel="b" android:horizontalGap="1%p"> </Key> <Key android:codes="110" android:keyLabel="n" android:horizontalGap="1%p"> </Key> <Key android:codes="109" android:keyLabel="m" android:horizontalGap="1%p"> </Key> <Key android:codes="-5" android:isRepeatable="true" android:keyIcon="@mipmap/ic_delete" android:keyWidth="17%p" android:horizontalGap="1%p"> </Key> </Row> <Row> <Key android:codes="-2" android:keyLabel="123" android:keyWidth="20%p" android:horizontalGap="1%p"> </Key> <Key android:codes="32" android:keyLabel="space" android:keyWidth="48%p" android:horizontalGap="5%p"> </Key> <Key android:codes="-4" android:keyLabel="完成" android:keyWidth="20%p" android:horizontalGap="5%p"> </Key> </Row> </Keyboard>
5.字母键盘的xml文件编写:
<?xml version="1.0" encoding="utf-8"?> <Keyboard xmlns:android="http://schemas.android.com/apk/res/android" android:horizontalGap="0px" android:verticalGap="0px" android:keyHeight="7.5%p" android:keyWidth="30%p"> <Row android:verticalGap="1%p"> <Key android:codes="49" android:keyLabel="1" android:horizontalGap="2%p"> </Key> <Key android:codes="50" android:keyLabel="2" android:horizontalGap="2%p"> </Key> <Key android:codes="51" android:keyLabel="3" android:horizontalGap="2%p"> </Key> </Row> <Row android:verticalGap="1%p"> <Key android:codes="52" android:keyLabel="4" android:horizontalGap="2%p"> </Key> <Key android:codes="53" android:keyLabel="5" android:horizontalGap="2%p"> </Key> <Key android:codes="54" android:keyLabel="6" android:horizontalGap="2%p"> </Key> </Row> <Row android:verticalGap="1%p"> <Key android:codes="55" android:keyLabel="7" android:horizontalGap="2%p"> </Key> <Key android:codes="56" android:keyLabel="8" android:horizontalGap="2%p"> </Key> <Key android:codes="57" android:keyLabel="9" android:horizontalGap="2%p"> </Key> </Row> <Row> <Key android:codes="-2" android:keyLabel="abc" android:horizontalGap="2%p"> </Key> <Key android:codes="48" android:keyLabel="0" android:horizontalGap="2%p"> </Key> <Key android:codes="-5" android:isRepeatable="true" android:keyIcon="@mipmap/ic_delete" android:horizontalGap="2%p"> </Key> </Row> </Keyboard>
6.自定义软键盘相关代码及注释:
package com.example.mycustomkeyboard; import android.content.Context; import android.hardware.input.InputManager; import android.inputmethodservice.Keyboard; import android.inputmethodservice.KeyboardView; import android.support.v7.widget.AppCompatEditText; import android.text.Editable; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Created by zd on 2018/4/2. */ public class KeyBoardEditText extends AppCompatEditText implements KeyboardView.OnKeyboardActionListener { /**数字键盘*/ private Keyboard keyboardNumber; /**字母键盘*/ private Keyboard keyboardLetter; private ViewGroup viewGroup; private KeyboardView keyboardView; /**是否发生键盘切换*/ private boolean changeLetter = false; /**是否为大写*/ private boolean isCapital = false; private int[] arrays = new int[]{Keyboard.KEYCODE_SHIFT, Keyboard.KEYCODE_MODE_CHANGE, Keyboard.KEYCODE_CANCEL, Keyboard.KEYCODE_DONE, Keyboard.KEYCODE_DELETE, Keyboard.KEYCODE_ALT, 32}; private List<Integer> noLists = new ArrayList<>(); private OnKeyboardStateChangeListener listener; public KeyBoardEditText(Context context) { super(context); initEditView(); } public KeyBoardEditText(Context context, AttributeSet attrs) { super(context, attrs); initEditView(); } public KeyBoardEditText(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initEditView(); } /**初始化数字和字母键盘*/ private void initEditView() { keyboardNumber = new Keyboard(getContext(), R.xml.keyboard_num); keyboardLetter = new Keyboard(getContext(), R.xml.keyboard_letter); for (int i=0; i<arrays.length; i++) { noLists.add(arrays[i]); } } /** * 设置软键盘刚弹出的时候显示字母键盘还是数字键盘 * @param vg 包裹KeyboardView的ViewGroup * @param kv KeyboardView * @param keyboard_num 是否显示数字键盘 */ public void setKeyboardType (ViewGroup vg, KeyboardView kv, boolean keyboard_num) { viewGroup = vg; keyboardView = kv; if (keyboard_num) { keyboardView.setKeyboard(keyboardNumber); changeLetter = false; } else { keyboardView.setKeyboard(keyboardLetter); changeLetter = true; } //显示预览 keyboardView.setPreviewEnabled(true); //为KeyboardView设置按键监听 keyboardView.setOnKeyboardActionListener(this); } public void setOnKeyBoardStateChangeListener(OnKeyboardStateChangeListener listener) { this.listener = listener; } @Override public void onPress(int primaryCode) { canShowPreview(primaryCode); } /** * 判断是否需要预览Key * * @param primaryCode keyCode */ private void canShowPreview(int primaryCode) { if (noLists.contains(primaryCode)) { keyboardView.setPreviewEnabled(false); } else { keyboardView.setPreviewEnabled(true); } } @Override public void onRelease(int primaryCode) { } @Override public void onKey(int primaryCode, int[] keyCodes) { Editable editable = getText(); int start = getSelectionStart(); switch (primaryCode) { case Keyboard.KEYCODE_DELETE://删除 if (editable != null && editable.length() > 0 && start > 0) { editable.delete(start-1, start); } break; case Keyboard.KEYCODE_MODE_CHANGE://字母键盘与数字键盘切换 changeKeyBoard(!changeLetter); break; case Keyboard.KEYCODE_DONE://完成 keyboardView.setVisibility(View.GONE); viewGroup.setVisibility(GONE); if (listener != null) { listener.hide(); } break; case Keyboard.KEYCODE_SHIFT://大小写切换 changeCapital(!isCapital); keyboardView.setKeyboard(keyboardLetter); break; default: editable.insert(start, Character.toString((char)primaryCode)); break; } } /**切换键盘大小写*/ private void changeCapital(boolean b) { isCapital = b; List<Keyboard.Key> lists = keyboardLetter.getKeys(); for (Keyboard.Key key: lists) { if (key.label != null && isKey(key.label.toString())) { if (isCapital) { key.label = key.label.toString().toUpperCase(); key.codes[0] = key.codes[0] - 32; } else { key.label = key.label.toString().toLowerCase(); key.codes[0] = key.codes[0] + 32; } } else if (key.label != null && key.label.toString().equals("小写")) { key.label = "大写"; } else if (key.label != null && key.label.toString().equals("大写")) { key.label = "小写"; } } } /** * 判断此key是否正确,且存在 * * @param key * @return */ private boolean isKey(String key) { String lowercase = "abcdefghijklmnopqrstuvwxyz"; if (lowercase.indexOf(key.toLowerCase()) > -1) { return true; } return false; } /**切换键盘类型*/ private void changeKeyBoard(boolean b) { changeLetter = b; if (changeLetter) { keyboardView.setKeyboard(keyboardLetter); } else { keyboardView.setKeyboard(keyboardNumber); } } @Override public void onText(CharSequence text) { } @Override public void swipeLeft() { } @Override public void swipeRight() { } @Override public void swipeDown() { } @Override public void swipeUp() { } public interface OnKeyboardStateChangeListener { void show(); void hide(); } @Override public boolean onTouchEvent(MotionEvent event) { hideSystemSoftInput(); if (event.getAction() == MotionEvent.ACTION_UP) { if (keyboardView.getVisibility() != VISIBLE) { keyboardView.setVisibility(VISIBLE); viewGroup.setVisibility(VISIBLE); if (listener != null) listener.show(); } } return true; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && (viewGroup.getVisibility() != GONE || keyboardView.getVisibility() != GONE)) { viewGroup.setVisibility(GONE); keyboardView.setVisibility(GONE); if (listener != null) listener.hide(); return true; } return super.onKeyDown(keyCode, event); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); hideSystemSoftInput(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); hideSystemSoftInput(); } /**隐藏系统软键盘*/ private void hideSystemSoftInput() { InputMethodManager manager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); manager.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } }
7.使用时的页面布局以及调用方法:
- view_keyboard_preview.xml
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/holo_red_light" android:textColor="@android:color/white" android:gravity="center" android:textSize="24sp"> </TextView>
- content_keyboard.xml
<?xml version="1.0" encoding="utf-8"?> <android.inputmethodservice.KeyboardView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/view_keyboard" android:background="#999999" android:focusable="true" android:focusableInTouchMode="true" android:keyBackground="@drawable/selector_keyboard_key" android:keyPreviewHeight="64dip" android:keyPreviewLayout="@layout/view_keyboard_preview" android:keyTextColor="@android:color/black" android:keyTextSize="24sp" android:labelTextSize="18sp" android:paddingTop="8dip" android:paddingBottom="8dip" android:shadowColor="#FFFFFF" android:shadowRadius="0.0" android:visibility="gone"> </android.inputmethodservice.KeyboardView>
- activity_main.xml
<?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:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:id="@+id/layout_root" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <View android:layout_width="match_parent" android:layout_height="400dip"></View> <com.example.mycustomkeyboard.KeyBoardEditText android:id="@+id/ed_main" android:layout_width="match_parent" android:layout_height="50dip" android:background="@android:color/holo_orange_dark"/> </LinearLayout> <LinearLayout android:id="@+id/layout_main" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_alignParentBottom="true" android:background="#999999" android:visibility="gone"> <include layout="@layout/content_keyboard"></include> </LinearLayout> </RelativeLayout>
- 调用方法和键盘遮挡输入框时的移动内容代码:
public class MainActivity extends AppCompatActivity { private KeyBoardEditText text; private KeyboardView keyboardView; private LinearLayout layout; private LinearLayout root; private int height = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text = (KeyBoardEditText) findViewById(R.id.ed_main); keyboardView = (KeyboardView) findViewById(R.id.view_keyboard); layout = (LinearLayout) findViewById(R.id.layout_main); root = (LinearLayout) findViewById(R.id.layout_root); text.setKeyboardType(layout, keyboardView, true); text.setOnKeyBoardStateChangeListener(new KeyBoardEditText.OnKeyboardStateChangeListener() { @Override public void show() { root.post(new Runnable() { @Override public void run() { int[] pos = new int[2]; //获取编辑框在整个屏幕中的坐标 text.getLocationOnScreen(pos); //编辑框的Bottom坐标和键盘Top坐标的差 height = (pos[1] + text.getHeight()) - (getScreenHeight(MainActivity.this) - keyboardView.getHeight()); if (height > 0) { root.scrollBy(0, height + dp2px(MainActivity.this, 16)); } } }); } @Override public void hide() { if (height > 0) { root.scrollBy(0, -(height + dp2px(MainActivity.this, 16))); } } }); //Log.i("zhangdi", getLngAndLat(this)); } /** * 获得屏幕高度 * * @param context * @return */ public static int getScreenHeight(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics displayMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(displayMetrics); return displayMetrics.heightPixels; } /** * dp转px * * @param context * @param dpVal * @return dip */ public static int dp2px(Context context, float dpVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources().getDisplayMetrics()); }
二、每次弹出都会改变数字顺序的自定义密码键盘
1.动态效果图:
2.设计思路:
- 在res下创建xml目录,在xml中定义数字键盘;
- 自定义键盘继承KeyboardView并且实现KeyboardView.OnKeyboardActionListener接口,每次显示键盘的时候使键盘上的数字随机排列显示,用接口回调控制输入框显示;
- 每次显示的时候判断软键盘是否会遮挡输入框,如果遮挡则使输入框所在部分内容上移,软键盘隐藏的时候把内容移回原位;
3,遇到的问题:
- 软键盘左右不能都有空隙,如果软键盘左侧有空隙,软键盘宽度不能占满整个屏幕。解决办法:让软键盘左侧有空隙,在软键盘的布局文件外面嵌套一层viewgroup布局,为这个布局设置与软键盘布局相同的底色;
- 如果在xml里设置‘自定义软键盘’和‘完成’,没办法使得‘自定义软键盘’这几个字居中,也控制不好‘完成’的位置。解决方法:把这两项不写在自定义数字键盘的xml文件里,而是写在页面布局嵌套软键盘布局的viewgroup中。
4.数字键盘的xml文件:
<?xml version="1.0" encoding="utf-8"?> <Keyboard xmlns:android="http://schemas.android.com/apk/res/android" android:horizontalGap="0px" android:verticalGap="0px" android:keyHeight="7.5%p" android:keyWidth="30%p"> <Row android:verticalGap="1%p"> <Key android:codes="49" android:keyLabel="1" android:horizontalGap="2.5%p"> </Key> <Key android:codes="50" android:keyLabel="2" android:horizontalGap="2.5%p"> </Key> <Key android:codes="51" android:keyLabel="3" android:horizontalGap="2.5%p"> </Key> </Row> <Row android:verticalGap="1%p"> <Key android:codes="52" android:keyLabel="4" android:horizontalGap="2.5%p"> </Key> <Key android:codes="53" android:keyLabel="5" android:horizontalGap="2.5%p"> </Key> <Key android:codes="54" android:keyLabel="6" android:horizontalGap="2.5%p"> </Key> </Row> <Row android:verticalGap="1%p"> <Key android:codes="55" android:keyLabel="7" android:horizontalGap="2.5%p"> </Key> <Key android:codes="56" android:keyLabel="8" android:horizontalGap="2.5%p"> </Key> <Key android:codes="57" android:keyLabel="9" android:horizontalGap="2.5%p"> </Key> </Row> <Row android:verticalGap="1%p"> <Key android:codes="-5" android:isRepeatable="true" android:keyIcon="@mipmap/ic_delete" android:horizontalGap="2.5%p"> </Key> <Key android:codes="48" android:keyLabel="0" android:horizontalGap="2.5%p"> </Key> <Key android:codes="-3" android:keyLabel="隐藏" android:horizontalGap="2.5%p"> </Key> </Row> </Keyboard>
5.自定义数字键盘代码和注释:
package com.example.mycustomkeyboard; import android.content.Context; import android.inputmethodservice.Keyboard; import android.inputmethodservice.KeyboardView; import android.text.Editable; import android.text.TextUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.TextView; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Random; /** * Created by zd on 2018/4/24. * 自定义键盘在布局文件中须确保在整体布局的底部,不要和输入框在相同的根布局内 */ public class MyKeyBoardView extends KeyboardView implements KeyboardView.OnKeyboardActionListener { //数字键盘 private Keyboard keyboard; //对应的输入框 private EditText editText; //为防止自定义键盘覆盖输入框,根布局向上的移动高度 private int height = 0; //输入框所在的根布局 private ViewGroup root; //自定义软键盘所在的根布局 private ViewGroup keyBoardRoot; //完成按钮 private TextView complete; public MyKeyBoardView(Context context, AttributeSet attrs) { super(context, attrs); } public MyKeyBoardView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * 关联自定义键盘与输入框,以及输入框所在的根布局 * 需要注意此方法需要在输入框的OnTouchListener中当MotionEvent为MotionEvent.ACTION_UP时调用, * 否则无法正确阻止系统软键盘的弹出 * @param et 输入框 * @param root 输入框所在的根布局 */ public void setAttachToEditText(EditText et, ViewGroup root, ViewGroup keyBoardRoot) { if (keyboard == null) { keyboard = new Keyboard(getContext(), R.xml.keyboard_random_num); } this.editText = et; this.root = root; this.keyBoardRoot = keyBoardRoot; complete = keyBoardRoot.findViewById(R.id.complete); complete.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { hideKeyBoard(); } }); editText.requestFocus(); hideSystemSoftInput(); showMyKeyBoard(); } /**显示自定随机数键盘*/ private void showMyKeyBoard() { randomKeyboardNumber(); setKeyboard(keyboard); setEnabled(true); setPreviewEnabled(false); showResize(); keyBoardRoot.setVisibility(VISIBLE); setVisibility(VISIBLE); setOnKeyboardActionListener(this); } /**根据输入框的底部坐标与自定义键盘的顶部坐标之间的差值height, * 判断自定义键盘是否覆盖住了输入框,如果覆盖则使输入框所在的根布局移动height*/ private void showResize() { root.post(new Runnable() { @Override public void run() { int[] pos = new int[2]; //获取编辑框在整个屏幕中的坐标 editText.getLocationOnScreen(pos); //编辑框的Bottom坐标和键盘Top坐标的差 height = (pos[1] + editText.getHeight()) - (getScreenHeight(getContext()) - keyBoardRoot.getHeight()); if (height > 0) { root.scrollBy(0, height + dp2px(getContext(), 16)); } } }); } /**自定义键盘隐藏时,判断输入框所在的根布局是否向上移动了height,如果移动了则需再移回来*/ private void hideResize() { if (height > 0) { root.scrollBy(0, -(height + dp2px(getContext(), 16))); } } /**获取手机屏幕高度*/ public static int getScreenHeight(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics displayMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(displayMetrics); return displayMetrics.heightPixels; } /**将px转换成dp*/ public static int dp2px(Context context, float dpVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources().getDisplayMetrics()); } /**打乱数字键盘顺序*/ private void randomKeyboardNumber() { List<Keyboard.Key> keyList = keyboard.getKeys(); // 查找出0-9的数字键 List<Keyboard.Key> newkeyList = new ArrayList<Keyboard.Key>(); for (int i = 0; i < keyList.size(); i++) { if (keyList.get(i).label != null && isNumber(keyList.get(i))) { newkeyList.add(keyList.get(i)); } } // 数组长度 int count = newkeyList.size(); // 结果集 List<KeyModel> resultList = new ArrayList<KeyModel>(); // 用一个LinkedList作为中介 LinkedList<KeyModel> temp = new LinkedList<KeyModel>(); // 初始化temp for (int i = 0; i < count; i++) { temp.add(new KeyModel(48 + i, i + "")); } // 取数 Random rand = new Random(); for (int i = 0; i < count; i++) { int num = rand.nextInt(count - i); resultList.add(new KeyModel(temp.get(num).getCode(), temp.get(num).getLable())); temp.remove(num); } for (int i = 0; i < newkeyList.size(); i++) { newkeyList.get(i).label = resultList.get(i).getLable(); newkeyList.get(i).codes[0] = resultList.get(i) .getCode(); } } private class KeyModel { private int code; private String lable; public KeyModel(int code, String lable) { this.code = code; this.lable = lable; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getLable() { return lable; } public void setLable(String lable) { this.lable = lable; } } /**判断key是数字键还是完成键*/ private boolean isNumber(Keyboard.Key key) { if (key.codes[0] < 0) { return false; } return true; } /**隐藏系统键盘*/ private void hideSystemSoftInput() { InputMethodManager manager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); manager.hideSoftInputFromWindow(editText.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } @Override public void onPress(int primaryCode) { } @Override public void onRelease(int primaryCode) { } @Override public void onKey(int primaryCode, int[] keyCodes) { Editable editable = editText.getText(); //获取焦点光标的所在位置 int start = editText.getSelectionStart(); switch (primaryCode) { case Keyboard.KEYCODE_DELETE://删除 if (editable != null && editable.length() > 0 && start > 0) { editable.delete(start-1, start); } break; case Keyboard.KEYCODE_DONE://完成 break; case Keyboard.KEYCODE_CANCEL://取消、隐藏 hideKeyBoard(); break; default://插入数字 editable.insert(start, Character.toString((char)primaryCode)); } } /**隐藏键盘*/ private void hideKeyBoard() { if (getVisibility() == VISIBLE) { keyBoardRoot.setVisibility(GONE); setVisibility(GONE); hideResize(); } } @Override public void onText(CharSequence text) { } @Override public void swipeLeft() { } @Override public void swipeRight() { } @Override public void swipeDown() { } @Override public void swipeUp() { } }
6.使用时的布局文件和调用方法:
my_keyboard_view.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/mykeyboard_root" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#999999" android:layout_alignParentBottom="true" android:visibility="gone"> <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="自定义软键盘" android:gravity="center" android:textSize="18sp" android:textColor="@android:color/white" android:paddingTop="10dp"/> <TextView android:id="@+id/complete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="完成" android:textSize="18sp" android:layout_alignParentRight="true" android:layout_marginRight="10dp" android:layout_marginTop="10dp" android:textColor="@color/colorPrimary"/> <com.example.mycustomkeyboard.MyKeyBoardView android:id="@+id/mykeyboard" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#999999" android:layout_marginTop="10dp" android:keyBackground="@drawable/key_drawable" android:keyTextColor="@android:color/black" android:layout_below="@id/title" android:visibility="gone"/> </RelativeLayout>
activity_main2.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.mycustomkeyboard.Main2Activity"> <LinearLayout android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:id="@+id/et1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="30dp"/> <EditText android:id="@+id/et2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="30dp"/> <EditText android:id="@+id/et3" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="30dp"/> <EditText android:id="@+id/et4" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="30dp"/> <EditText android:id="@+id/et" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="30dp"/> </LinearLayout> <include layout="@layout/my_keyborad_view"/> </RelativeLayout>
使用方法:
package com.example.mycustomkeyboard; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RelativeLayout; public class Main2Activity extends AppCompatActivity implements View.OnTouchListener { private EditText et; private EditText et1; private EditText et2; private EditText et3; private EditText et4; private MyKeyBoardView keyBoardView; private LinearLayout root; private RelativeLayout keyboardRoot; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); et = (EditText) findViewById(R.id.et); et1 = (EditText) findViewById(R.id.et1); et2 = (EditText) findViewById(R.id.et2); et3 = (EditText) findViewById(R.id.et3); et4 = (EditText) findViewById(R.id.et4); root = (LinearLayout) findViewById(R.id.root); keyBoardView = (MyKeyBoardView) findViewById(R.id.mykeyboard); keyboardRoot = (RelativeLayout) findViewById(R.id.mykeyboard_root); et.setOnTouchListener(this); et1.setOnTouchListener(this); et2.setOnTouchListener(this); et3.setOnTouchListener(this); et4.setOnTouchListener(this); } @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { keyBoardView.setAttachToEditText((EditText) v, root, keyboardRoot); } return true; } }
三、能与webview交互的自定义密码键盘
1.动态效果图:
2.设计思路:
- 与第二种的密码键盘设计思路基本一致,数字键盘的xml文件直接用第二种的就可以,不过软键盘按键控制输入框显示的时候需要调用html中的方法进行显示;
- 判断软件盘是否遮挡输入框时也需要html传递输入框所在位置参数载进行计算,需要注意的是html中传递的输入框位置是相对于页面左上角的,所以需要减去webview的滑动距离,才能计算出输入框相对于屏幕左上角的位置,才能计算出软键盘是否会遮挡输入框。
3.自定义数字键盘代码以及注释:
package com.example.mycustomkeyboard; import android.app.Activity; import android.content.Context; import android.inputmethodservice.Keyboard; import android.inputmethodservice.KeyboardView; import android.text.Editable; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.webkit.WebView; import android.widget.EditText; import android.widget.TextView; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Random; /** * Created by zd on 2018/4/24. * 自定义键盘在布局文件中须确保在整体布局的底部,不要和输入框在相同的根布局内 */ public class MyKeyBoardViewWeb extends KeyboardView implements KeyboardView.OnKeyboardActionListener { //数字键盘 private Keyboard keyboard; //为防止自定义键盘覆盖输入框,根布局向上的移动高度 private int height = 0; //输入框所在的根布局 private ViewGroup root; //自定义软键盘所在的根布局 private ViewGroup keyBoardRoot; //完成按钮 private TextView complete; private WebView web; public MyKeyBoardViewWeb(Context context, AttributeSet attrs) { super(context, attrs); } public MyKeyBoardViewWeb(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * 关联自定义键盘与输入框,以及输入框所在的根布局 * @param root 输入框所在的根布局 */ public void setAttach(WebView web, int height, ViewGroup root, ViewGroup keyBoardRoot) { if (keyboard == null) { keyboard = new Keyboard(getContext(), R.xml.keyboard_random_num); } this.web = web; this.keyBoardRoot = keyBoardRoot; this.root = root; complete = keyBoardRoot.findViewById(R.id.complete); complete.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { hideKeyBoard(); } }); hideSystemSoftInput(); showMyKeyBoard(height); } /**显示自定随机数键盘*/ private void showMyKeyBoard(int height) { randomKeyboardNumber(); setKeyboard(keyboard); setEnabled(true); setPreviewEnabled(false); showResize(height); keyBoardRoot.setVisibility(VISIBLE); setVisibility(VISIBLE); setOnKeyboardActionListener(this); } /**根据输入框的底部坐标与自定义键盘的顶部坐标之间的差值height, * 判断自定义键盘是否覆盖住了输入框,如果覆盖则使输入框所在的根布局移动height*/ private void showResize(final int h) { root.post(new Runnable() { @Override public void run() { //获取屏幕高度 int screenHeight = getScreenHeight(getContext()); //获取软键盘高度 int keyHeight = keyBoardRoot.getMeasuredHeight(); //获取编辑框底部距离页面顶部的高度 int etHeight = dp2px(getContext(), h); //获取webview的内容滚动距离 int scrollY = web.getScrollY(); //编辑框底部高度去除webview内容滚动距离获取编辑框底部与屏幕顶部之间的高度 // ,与软键盘与屏幕顶部之间的高度差,如果差值大于0则证明软键盘覆盖住编辑框了,需要内容上移。 height = etHeight - scrollY - (screenHeight - keyHeight); if (height > 0) { root.scrollBy(0, height + dp2px(getContext(), 32)); } } }); } /**自定义键盘隐藏时,判断输入框所在的根布局是否向上移动了height,如果移动了则需再移回来*/ private void hideResize() { if (height > 0) { root.scrollBy(0, -(height + dp2px(getContext(), 32))); } } /**获取手机屏幕高度*/ public static int getScreenHeight(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics displayMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(displayMetrics); return displayMetrics.heightPixels; } /**将px转换成dp*/ public static int dp2px(Context context, float dpVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources().getDisplayMetrics()); } public static int px2dp(Context context, float dpVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, dpVal, context.getResources().getDisplayMetrics()); } /**打乱数字键盘顺序*/ private void randomKeyboardNumber() { List<Keyboard.Key> keyList = keyboard.getKeys(); // 查找出0-9的数字键 List<Keyboard.Key> newkeyList = new ArrayList<Keyboard.Key>(); for (int i = 0; i < keyList.size(); i++) { if (keyList.get(i).label != null && isNumber(keyList.get(i))) { newkeyList.add(keyList.get(i)); } } // 数组长度 int count = newkeyList.size(); // 结果集 List<KeyModel> resultList = new ArrayList<KeyModel>(); // 用一个LinkedList作为中介 LinkedList<KeyModel> temp = new LinkedList<KeyModel>(); // 初始化temp for (int i = 0; i < count; i++) { temp.add(new KeyModel(48 + i, i + "")); } // 取数 Random rand = new Random(); for (int i = 0; i < count; i++) { int num = rand.nextInt(count - i); resultList.add(new KeyModel(temp.get(num).getCode(), temp.get(num).getLable())); temp.remove(num); } for (int i = 0; i < newkeyList.size(); i++) { newkeyList.get(i).label = resultList.get(i).getLable(); newkeyList.get(i).codes[0] = resultList.get(i) .getCode(); } } private class KeyModel { private int code; private String lable; public KeyModel(int code, String lable) { this.code = code; this.lable = lable; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getLable() { return lable; } public void setLable(String lable) { this.lable = lable; } } /**判断key是数字键还是完成键*/ private boolean isNumber(Keyboard.Key key) { if (key.codes[0] < 0) { return false; } return true; } /**隐藏系统键盘*/ public void hideSystemSoftInput() { InputMethodManager manager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); manager.hideSoftInputFromWindow(((Activity)getContext()).getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } @Override public void onPress(int primaryCode) { } @Override public void onRelease(int primaryCode) { } @Override public void onKey(int primaryCode, int[] keyCodes) { switch (primaryCode) { case Keyboard.KEYCODE_DELETE://删除 web.loadUrl("javascript:del()"); break; case Keyboard.KEYCODE_DONE://完成 break; case Keyboard.KEYCODE_CANCEL://取消、隐藏 hideKeyBoard(); break; default://插入数字 String content = Character.toString((char)primaryCode); web.loadUrl("javascript:insert("+content+")"); } } /**隐藏键盘*/ private void hideKeyBoard() { if (getVisibility() == VISIBLE) { keyBoardRoot.setVisibility(GONE); setVisibility(GONE); hideResize(); } } @Override public void onText(CharSequence text) { } @Override public void swipeLeft() { } @Override public void swipeRight() { } @Override public void swipeDown() { } @Override public void swipeUp() { } }
4.使用的html相关代码:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0 , maximum-scale=1.0, user-scalable=0"> <title>Show.html</title> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="this is my page"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!--<link rel="stylesheet" type="text/css" href="./styles.css">--> <script type="text/javascript"> var name; function init(s) { name = s; setLoseFocus(); sub(); } function setFocus() {//设置焦点 var et = document.getElementById(name); et.focus(); } function setLoseFocus() {//取消焦点 var et = document.getElementById(name); et.blur(); } function sub(){ var et = document.getElementById(name); var height = getPos(et); console.info(height); window.demo.showInput(height);//调用android方法,弹出软键盘 <!--console.info("哈哈");--> } function getPos(o) //取元素坐标 { var y = o.offsetTop; y += o.offsetHeight; return y; } function del() {//删除 var str = document.getElementById(name).value; var strNew = str.substring(0,str.length-1); document.getElementById(name).value = strNew; } function insert(str) {//插入 var s = document.getElementById(name).value; var strNew = s + str; document.getElementById(name).value = strNew; } </script> </head> <body> <br/> <br/> <br/> <input type="text" id="txt1" οnclick="init('txt1')" value=""> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <input type="text" id="txt2" οnclick="init('txt2')" value=""> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <input type="text" id="txt" οnclick="init('txt')" value=""> </body> </html>
5.使用时的布局以及调用方法:
my_keyboard_view_web.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/mykeyboard_root" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#999999" android:layout_alignParentBottom="true" android:visibility="gone"> <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="自定义软键盘" android:gravity="center" android:textSize="18sp" android:textColor="@android:color/white" android:paddingTop="10dp"/> <TextView android:id="@+id/complete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="完成" android:textSize="18sp" android:layout_alignParentRight="true" android:layout_marginRight="10dp" android:layout_marginTop="10dp" android:textColor="@color/colorPrimary"/> <com.example.mycustomkeyboard.MyKeyBoardViewWeb android:id="@+id/mykeyboard" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#999999" android:layout_marginTop="10dp" android:keyBackground="@drawable/key_drawable" android:keyTextColor="@android:color/black" android:layout_below="@id/title" android:visibility="gone"/> </RelativeLayout>
activity_webview.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.mycustomkeyboard.WebviewActivity"> <RelativeLayout android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent"> <WebView android:id="@+id/web" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout> <include layout="@layout/my_keyborad_view_web"/> </RelativeLayout>
调用方法:
package com.example.mycustomkeyboard; import android.app.Activity; import android.os.Bundle; import android.text.Editable; import android.view.View; import android.webkit.JavascriptInterface; import android.webkit.WebView; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RelativeLayout; public class WebviewActivity extends Activity { private WebView web; private MyKeyBoardViewWeb keyBoardView; private RelativeLayout root; private RelativeLayout keyboardRoot; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview); root = (RelativeLayout) findViewById(R.id.root); keyBoardView = (MyKeyBoardViewWeb) findViewById(R.id.mykeyboard); keyboardRoot = (RelativeLayout) findViewById(R.id.mykeyboard_root); web = findViewById(R.id.web); web.getSettings().setJavaScriptEnabled(true); web.getSettings().setDomStorageEnabled(true); web.addJavascriptInterface(new DemoJavaScriptInterface(),"demo"); web.loadUrl("file:///android_asset/index.html"); } public class DemoJavaScriptInterface { @JavascriptInterface public void showInput(final int height) { runOnUiThread(new Runnable() { @Override public void run() { if (keyBoardView.getVisibility() != View.VISIBLE) { keyBoardView.setAttach(web, height, root, keyboardRoot); } } }); } } }最后附上demo下载地址,使用android studio添加moudle可以直接运行看看效果: 下载demo