Android自定义键盘详解、自定义输入法简介
来源:互联网 发布:软件质量测评报告 编辑:程序博客网 时间:2024/05/20 06:26
概述
Android中有两个系统类是用来实现键盘的,分别是Keyboard和KeyboardView。
Keyboard有个内部类Key,用于记录每个键的信息,如code、width、height等。而KeyBorad则持有一个List用于存储所有Key,并对外提供接口。
KeyBoardView则是负责绘制所有Key,监听Touch事件,根据Touch处的坐标,计算出点击的是哪个Key,然后通过OnKeyboardActionListener通知调用处,点击的是哪个Key、code是多少,调用处再把该Key对应的string值设置到EidtText中。
大致流程就是这样,但是有很多细节、坑需要注意,本文的主要内容就是来分析这些细节和坑。
这两个类的源码都很短,只有1000行左右,但是这里并不准备对所有源码进行分析,为什么呢?因为这两个类里面的成员变量、方法几乎都是private的,完全无法供我们修改、自定义。所以对于自定义键盘要求很高、很炫酷的需求,还是得完全重写特定的“KeyBoradView”,KeyBorad倒是可以直接用。
自定义键盘
下面以一个最简单的例子来讲解如何使用,该注意的点就直接在代码中以注释形势给出。
1、首先在res目录下创建xml目录,在xml目录下创建qwer.xml文件,用于键盘布局,代码如下:
<?xml version="1.0" encoding="UTF-8"?><Keyboard android:keyWidth="10%p" android:keyHeight="45dp" android:horizontalGap="3px" android:verticalGap="3px" xmlns:android="http://schemas.android.com/apk/res/android">// KeyBoard标签下的keyWidth="10%p"表示下面的每个键宽度占parent宽度的10%,xxxGap属性是键与键之间的水平和垂直间隔 <Row> <Key android:codes="113" android:keyEdgeFlags="left" android:keyLabel="q" /> <Key android:codes="119" android:keyLabel="w" /> <Key android:codes="101" android:keyLabel="e" /> <Key android:codes="114" android:keyLabel="r" /> <Key android:codes="116" android:keyLabel="t" /> <Key android:codes="121" android:keyLabel="y" /> <Key android:codes="117" android:keyLabel="u" /> <Key android:codes="105" android:keyLabel="i" /> <Key android:codes="111" android:keyLabel="o" /> <Key android:codes="112" android:keyEdgeFlags="right" android:keyLabel="p" /> </Row>// codes属性就是该Key对应的ASCII码(其实也可以不一定得是ASCII码,反正后面会在监听事件中返回给你这个codes值,你想怎么处理都行),keyLabel属性就是该Key显示在键盘上的string,还有个keyIcon属性是该Key显示在键盘上的drawable <Row> <Key android:horizontalGap="5%p" android:codes="97" android:keyEdgeFlags="left" android:keyLabel="a" /> <Key android:codes="115" android:keyLabel="s" /> <Key android:codes="100" android:keyLabel="d" /> <Key android:codes="102" android:keyLabel="f" /> <Key android:codes="103" android:keyLabel="g" /> <Key android:codes="104" android:keyLabel="h" /> <Key android:codes="106" android:keyLabel="j" /> <Key android:codes="107" android:keyLabel="k" /> <Key android:codes="108" android:keyEdgeFlags="right" android:keyLabel="l" /> </Row>// Key标签下的horizontalGap属性表示距离parent的距离 <Row> <Key android:keyWidth="15p" android:codes="-1" android:keyEdgeFlags="left" android:keyLabel="Shift" /> <Key android:codes="122" android:keyLabel="z" /> <Key android:codes="120" android:keyLabel="x" /> <Key android:codes="99" android:keyLabel="c" /> <Key android:codes="118" android:keyLabel="v" /> <Key android:codes="98" android:keyLabel="b" /> <Key android:codes="110" android:keyLabel="n" /> <Key android:codes="109" android:keyLabel="m" /> <Key android:keyWidth="15p" android:codes="-5" android:keyEdgeFlags="right" android:isRepeatable="true" android:keyLabel="Delete" /> </Row>// Key标签下的keyWidth属性会覆盖parent中赋的值,isRepeatable属性表示是长按时是否重复输入 <Row android:rowEdgeFlags="bottom"> <Key android:keyWidth="20%p" android:codes="-2" android:keyLabel="12#" /> <Key android:keyWidth="15%p" android:codes="44" android:keyLabel="," /> <Key android:keyWidth="30%p" android:codes="32" android:isRepeatable="true" android:keyLabel="Space" /> <Key android:keyWidth="15%p" android:codes="46" android:keyLabel="." /> <Key android:keyWidth="20%p" android:codes="-3" android:keyEdgeFlags="right" android:keyLabel="完成" /> </Row></Keyboard>
2、创建自定义的MyKeyBoradView,主要用于处理逻辑,让外界使用更加方便。网上很多文章没有创建自定义View,用的XXXUtil也是一样的。个人觉得创建自定义的View封装好这些处理逻辑更好一些。
public class MyKeyBoardView extends LinearLayout { private EditText mEditText; public MyKeyBoardView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context) { setOrientation(VERTICAL); LayoutInflater.from(context).inflate(R.layout.lyt_keyboard, this, true); KeyboardView kv = (KeyboardView) findViewById(R.id.kv_lyt_keyboard); // 加载上面的qwer.xml键盘布局,new出KeyBoard对象 Keyboard kb = new Keyboard(context, R.xml.qwer); kv.setKeyboard(kb); kv.setEnabled(true); // 设置是否显示预览,就是某个键时,上面弹出小框显示你按下的键,称为预览框 kv.setPreviewEnabled(true); // 设置监听器 kv.setOnKeyboardActionListener(mListener); } // 设置接受字符的EditText public void setStrReceiver(EditText et) { mEditText = et; } private KeyboardView.OnKeyboardActionListener mListener = 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) { } @Override public void onRelease(int primaryCode) { // 手指离开该键(抬起手指或手指移动到其它键)时回调 } @Override public void onPress(int primaryCode) { // 当某个键被按下时回调,只有press_down时会触发,长按不会多次触发 } @Override public void onKey(int primaryCode, int[] keyCodes) { // 键被按下时回调,在onPress后面。如果isRepeat属性设置为true,长按时会连续回调 Editable editable = mEditText.getText(); int selectionPosition = mEditText.getSelectionStart(); if (primaryCode == Keyboard.KEYCODE_DELETE) { // 如果按下的是delete键,就删除EditText中的str if (editable != null && editable.length() > 0) { if (selectionPosition > 0) { editable.delete(selectionPosition - 1, selectionPosition); } } } else { // 把该键对应的string值设置到EditText中 editable.insert(selectionPosition, Character.toString((char) primaryCode)); } // 其实还有很多code的判断,比如“完成”键、“Shift”键等等,这里就不一一列出了 // 对于Shift键被按下,需要做两件事,一件是把键盘显示字符全部切换为大写,调用setShifted()方法就可以了;另一件是把Shift状态下接收到的正常字符(Shift、完成、Delete等除外)的code值-32再转换成相应str,插入到EidtText中 } };}
3、自定义的MyKeyBoardView对应的layout文件,以及预览框对应的layout文件:
<?xml version="1.0" encoding="utf-8"?><merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <android.inputmethodservice.KeyboardView android:id="@+id/kv_lyt_keyboard" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#999" android:focusable="true" android:focusableInTouchMode="true" android:keyBackground="@color/colorWhite" android:keyTextColor="#333" android:shadowColor="#00000000" android:keyPreviewHeight="100dp" android:keyPreviewLayout="@layout/lyt_preview"/> // background是整个键盘View的背景,设置了键的xxxgap后,中间的缝隙就会是这个背景色 // keyBackground是每个键的背景 // keyTextColor是每个键中字符的textColor // shadowColor是每个键中字符的shadow,请设置为全透明!!! // keyPreviewHeight是预览框的高度!!! // keyPreviewLayout是预览框对应的布局,跟布局必须是TextView!!!</merge>
lyt_preview文件代码:
<?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="@drawable/bg_preview" android:gravity="center" android:paddingLeft="100dp" android:paddingRight="100dp" android:textColor="#000" android:textSize="30sp" android:textStyle="bold"/>
4、然后就是最后一个步骤,Activity中调用了。Activity的布局文件就不贴出了,就是最上面一个EditText,最下面一个MyKeyBoardView。
ublic class MainActivity extends Activity { private MyKeyBoardView mKeyView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); EditText editText = (EditText) findViewById(R.id.et_main); mKeyView = (MyKeyBoardView) findViewById(R.id.kv_main); mKeyView.setStrReceiver(editText); editText.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(v.getWindowToken(), 0); mKeyView.setVisibility(View.VISIBLE); return true; } }); }}
遇到的问题
1、预览框的显示效果
为了不让文章太长,我就不列出各种可能的坑了,直接列出最正确的做法,这里就得分析下源码了。下面所有源码都是android7.0的源码,其他版本可能会有细微不同。
看KeyBoardView中的showKey()方法:
private void showKey(final int keyIndex) { final PopupWindow previewPopup = mPreviewPopup; final Key[] keys = mKeys; if (keyIndex < 0 || keyIndex >= mKeys.length) return; Key key = keys[keyIndex]; if (key.icon != null) { mPreviewText.setCompoundDrawables(null, null, null, key.iconPreview != null ? key.iconPreview : key.icon); mPreviewText.setText(null); } else { mPreviewText.setCompoundDrawables(null, null, null, null); mPreviewText.setText(getPreviewText(key)); if (key.label.length() > 1 && key.codes.length < 2) { mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize); mPreviewText.setTypeface(Typeface.DEFAULT_BOLD); } else { mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge); mPreviewText.setTypeface(Typeface.DEFAULT); } } mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); // 计算预览框的宽度,由TextView和Key宽度中较大的决定 int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight()); // 确定预览框的高度。这里的mPreviewHeight是在构造方法中获取xml属性中的值,对应的属性就是keyPreviewHeight // 是不是很坑爹,宽度在TextView中设置,高度在KeyBoardView的xml属性中设置。。。 final int popupHeight = mPreviewHeight; LayoutParams lp = mPreviewText.getLayoutParams(); if (lp != null) { lp.width = popupWidth; lp.height = popupHeight; } // mPreviewCentered默认为false,7.0版本源码中没有地方会改变其值,所有一定会进入下面的判断 if (!mPreviewCentered) { // 以Key的x坐标为基准,计算预览框的x坐标 // 这里注意了,如果预览TextView直接设置宽度,由于预览框和Key的左边是对齐的,如果预览框和Key的宽度不一样,显示就很丑; // 所以正确的设置预览框宽度的方法是通过设置padding值的大小来控制预览框的宽度 mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft; // 以Key的y坐标为基准,计算预览框的y坐标 // 这里又要注意了,mPreviewOffset是从xml属性中取的值,如果不设置该属性就会取默认值,不同android版本的默认值是不一样的。。。所以一定要设置一下这个属性 mPopupPreviewY = key.y - popupHeight + mPreviewOffset; } else { // TODO: Fix this if centering is brought back mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2; mPopupPreviewY = - mPreviewText.getMeasuredHeight(); } mHandler.removeMessages(MSG_REMOVE_PREVIEW); getLocationInWindow(mCoordinates); mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero // Set the preview background state mPreviewText.getBackground().setState( key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); mPopupPreviewX += mCoordinates[0]; mPopupPreviewY += mCoordinates[1]; // If the popup cannot be shown above the key, put it on the side getLocationOnScreen(mCoordinates); if (mPopupPreviewY + mCoordinates[1] < 0) { // If the key you're pressing is on the left side of the keyboard, show the popup on // the right, offset by enough to see at least one key to the left/right. if (key.x + key.width <= getWidth() / 2) { mPopupPreviewX += (int) (key.width * 2.5); } else { mPopupPreviewX -= (int) (key.width * 2.5); } mPopupPreviewY += popupHeight; } if (previewPopup.isShowing()) { previewPopup.update(mPopupPreviewX, mPopupPreviewY, popupWidth, popupHeight); } else { previewPopup.setWidth(popupWidth); previewPopup.setHeight(popupHeight); previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY, mPopupPreviewX, mPopupPreviewY); } mPreviewText.setVisibility(VISIBLE); }
2、键盘字体显示模糊
KeyBoardView会遍历所有key,然后绘制其键盘字符,下面看下绘制逻辑:
if (label != null) { // For characters, use large font. For labels like "Done", use small font. if (label.length() > 1 && key.codes.length < 2) { paint.setTextSize(mLabelTextSize); paint.setTypeface(Typeface.DEFAULT_BOLD); } else { paint.setTextSize(mKeyTextSize); paint.setTypeface(Typeface.DEFAULT); } // Draw a drop shadow for the text // 看到没,会在字符上面绘制一层shadow。。。所以在xml属性中一定要将该属性设置为全透明 paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor); // Draw the text canvas.drawText(label,(key.width - padding.left - padding.right) / 2+ padding.left,(key.height - padding.top - padding.bottom) / 2+ (paint.getTextSize() - paint.descent()) / 2 + padding.top,paint); // Turn off drop shadow paint.setShadowLayer(0, 0, 0, 0);}
自定义输入法
输入法以后再写吧。。。。
- Android自定义键盘详解、自定义输入法简介
- ANDROID自定义输入法-自定义键盘
- ANDROID自定义输入法-自定义键盘
- ANDROID自定义输入法-自定义键盘
- ANDROID自定义输入法-自定义键盘
- android定义输入法-自定义键盘(转)
- 通过自定义android键盘实现车牌号输入法
- Android自定义表情键盘与输入法键盘冲突
- Android学习 - 自定义输入法
- Android 自定义输入法初探
- Android自定义密码键盘
- Android自定义密码键盘
- android自定义键盘
- android自定义密码键盘
- android自定义密码键盘
- android自定义密码键盘
- android自定义键盘
- Android自定义键盘
- ubuntu 14.04 install g++问题
- POJ 2310 Cubic Tick-Tack-Toe 笔记
- VC++下命名管道编程的原理及实现
- Android studio 开发实战笔记----(一)开发环境搭建
- javascript权威指南笔记
- Android自定义键盘详解、自定义输入法简介
- 测试一下 恶化if黑
- Java8-常用的流操作
- bzoj3994/洛谷P3327 莫比乌斯反演
- OC基础-对象和对象之间的关系09
- 浅谈协方差矩阵
- 变态最大值
- VMware下Ubuntu与宿主Windows共享文件夹
- NAND flash和NOR flash的区别详解