Android自定义键盘

参考文章

SValence https://blog.csdn.net/svalence/article/details/80330392

小葱猫 https://blog.csdn.net/qq_29983773/article/details/79501658

思路

我的android的能力不是很强,一开始写自定义键盘的时候直接用PopWindows去画了一个键盘,非常的差,光标问题,还有各种字符串判断出错,最后还是写了出来,但效果不好,而且效率低。

后来才知道android提供了keyboardView专门来设计自定的键盘。查看了网上大部分的文章,很多都是在布局文件中写一个layout存放一个keyboradView需要的时候,就显示出来,不需要就因此。

这样对于只需要在关键地方使用自定义键盘的程序来说就已经足够了。可是我的项目需求是整个项目不允许使用系统键盘。吐血~~

为此,我在所有的布局中都存放一个keyboardView,太麻烦了。

想到之前一些悬浮框的做法,所以就想到用windowManager.addView()这个方法来做。

思路顺序:

  • 首先创建一个SafeBoard的工具类,用来创建视图,创建键盘,等初始化操作。
  • 然后新建类SafeBoardView继承KeyboardView,重写onDraw方法,有些删除键需要用图片替换掉。并且在SafeBoard中为SafeBoardView定义OnKeyboardActionListener。
  • 然后在这个工具类中获取到windowsManager,并且提供显示和隐藏的方法。
  • 新建一个SateEditText继承EditText,在构造方法中,添加OnTouchListener,重写ACTION_UP状态的方法,在手指离开的输入框的时候显示键盘。在重写返回键,如果自定义键盘存在,就先关闭,返回true,否则就返回false.

代码实现

SafeBoardView.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package com.example.sun.savekeyboard.util;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManager;

import com.example.sun.savekeyboard.R;

import java.lang.reflect.Field;
import java.util.List;

/**
* Create by Sun on 2019/6/20
* Desc:
*/
public class SafeKeyboardView extends KeyboardView {
private Context mContext;

public SafeKeyboardView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext=context;
}

public SafeKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext=context;

}


//特殊按键更换图片
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);

try {
List<Keyboard.Key> keys = getKeyboard().getKeys();
for (Keyboard.Key key : keys) {
if (key.codes[0] == -5 || key.codes[0] == -35 || key.codes[0] == -2
|| key.codes[0] == -1) {
drawSpecialKey(canvas, key);
}
}
} catch (Exception e) {
e.printStackTrace();
}

}


private void drawSpecialKey(Canvas canvas, Keyboard.Key key) {
if (key.codes[0] == -5) {
drawKeyBackground(R.drawable.back, canvas, key);
} else if (key.codes[0] == -35) {
drawKeyBackground(R.drawable.back, canvas, key);
} else if (key.codes[0] == -1) {
if (SafeKeyboard.isCapes) {
drawKeyBackground(R.drawable.letter_big, canvas, key);
} else {
drawKeyBackground(R.drawable.letter_smal, canvas, key);
}
}
drawText(canvas,key);

}

private void drawKeyBackground(int id, Canvas canvas, Keyboard.Key key) {
Drawable drawable = zoomImage(id,40,40);
int[] state = key.getCurrentDrawableState();
if (key.codes[0] != 0) {
drawable.setState(state);
}
//将图片的位置居中
drawable.setBounds(key.x + (key.width - drawable.getIntrinsicWidth()) / 2,
key.y + (key.height - drawable.getIntrinsicHeight()) / 2,
key.x + (key.width - drawable.getIntrinsicWidth()) / 2 + drawable.getIntrinsicWidth(),
key.y + (key.height - drawable.getIntrinsicHeight()) / 2 + drawable.getIntrinsicHeight());

drawable.draw(canvas);
}


//图片缩放
public Drawable zoomImage(int resId, int w, int h){
Resources res = mContext.getResources();
Bitmap oldBmp = BitmapFactory.decodeResource(res, resId);
Bitmap newBmp = Bitmap.createScaledBitmap(oldBmp,w, h, true);
Drawable drawable = new BitmapDrawable(res, newBmp);
return drawable;
}
private void drawText(Canvas canvas, Keyboard.Key key) {
try {
Paint paint = new Paint();
paint.setTextAlign(Paint.Align.CENTER);
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);

} catch (Exception e) {
e.printStackTrace();
}
}




}

SafeKeyboard.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
package com.example.sun.savekeyboard.util;

import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.os.Build;
import android.text.Editable;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.FrameLayout;

import com.example.sun.savekeyboard.R;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

/**
* Create by Sun on 2019/6/20
* Desc:
*/
public class SafeKeyboard {


public static boolean isCapes;
private Context mContext;
private Keyboard keyboardNumber; //数字键盘
private Keyboard keyboardLetter; //字母键盘
private Keyboard keyboardSymbol; //符号键盘
private View keyboardContanier;
private SafeKeyboardView keyboardView;
private SateEditText mEditText;
private int keyboardType;
private static SafeKeyboard instance;
private WindowManager.LayoutParams params;
private WindowManager mWindowManager;
private Activity activity;
private int srollSize;
private boolean hasShow;
private FrameLayout abc;

public static SafeKeyboard getInstance(Context context,SateEditText editText){
if(instance==null)
instance=new SafeKeyboard();
instance.init(context,editText);
return instance;
}
public void init(Context mContext, SateEditText mEditText) {
this.mEditText = mEditText;
this.activity= (Activity) mContext;
initKeyboard(mContext);
}

private void initKeyboard(Context context){
params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.BOTTOM|Gravity.CENTER;
params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
if(mContext==null || context!=mContext)
{
this.mContext = context;
mWindowManager= (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
keyboardContanier = LayoutInflater.from(mContext).inflate(R.layout.keyboardlayout, null);
keyboardContanier.setFocusable(true);
keyboardContanier.setFocusableInTouchMode(true);
keyboardNumber=new Keyboard(mContext, R.xml.number_keyboard);
keyboardLetter=new Keyboard(mContext, R.xml.letter_keyboard);
keyboardSymbol=new Keyboard(mContext, R.xml.char_keyboard);

//默认初始是字母键盘
keyboardView = keyboardContanier.findViewById(R.id.safeKeyboardLetter);
keyboardType = 1;

keyboardView.setKeyboard(keyboardLetter); //给键盘View设置键盘


keyboardView.setEnabled(true);
keyboardView.setPreviewEnabled(false);
keyboardView.setOnKeyboardActionListener(listener);
FrameLayout done = keyboardContanier.findViewById(R.id.keyboardDone);
done.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
hideKeyboard();
}
});


abc = keyboardContanier.findViewById(R.id.keyboardABC);
abc.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
keyboardType = 1;
switchKeyboard();
}
});


mWindowManager.addView(keyboardContanier,params);


}else {
//如果不移除再新增,会导致dialog中显示键盘时,被dialog给挡住。
mWindowManager.removeView(keyboardContanier);
mWindowManager.addView(keyboardContanier,params);

}


}


public boolean hasHide(){
return keyboardContanier.getVisibility()==View.GONE;
}

public void hideKeyboard() {
if(keyboardContanier.getVisibility()==View.GONE){
return;
}
mEditText.getmContentView().scrollBy(0,-srollSize);
keyboardContanier.setVisibility(View.GONE);
}
public void showKeyboard(final int x, final int y) {
if(keyboardContanier.getVisibility()==View.VISIBLE){
return;
}
keyboardView.post(new Runnable() {
@Override
public void run() {

//这一段是为了键盘不挡住输入框,但是针对dialog或则popwindows中的输入框暂无法解决。
// if(y>keyboardView.getHeight()){
// srollSize=keyboardView.getHeight()+mEditText.getHeight();
// mEditText.getmContentView().scrollTo(0,srollSize);
// }else {
// srollSize=0;
// }

Log.d("srcoll", "run: "+srollSize);
}
});
keyboardContanier.setVisibility(View.VISIBLE);

}
private boolean isLowCaseLetter(String str) {
String letters = "abcdefghijklmnopqrstuvwxyz";
return letters.contains(str);
}
private boolean isUpCaseLetter(String str) {
String letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return letters.contains(str);
}

private KeyboardView.OnKeyboardActionListener listener=new KeyboardView.OnKeyboardActionListener() {
@Override
public void onPress(int primaryCode) {

}

@Override
public void onRelease(int primaryCode) {

}

@Override
public void onKey(int primaryCode, int[] keyCodes) {
try {
Editable editable = mEditText.getText();
int start = mEditText.getSelectionStart();
int end = mEditText.getSelectionEnd();
if (primaryCode == Keyboard.KEYCODE_CANCEL) {
// 隐藏键盘
hideKeyboard();
} else if (primaryCode == Keyboard.KEYCODE_DELETE || primaryCode == -35) {
// 回退键,删除字符
if (editable != null && editable.length() > 0) {
if (start == end) { //光标开始和结束位置相同, 即没有选中内容
editable.delete(start - 1, start);
} else { //光标开始和结束位置不同, 即选中EditText中的内容
editable.delete(start, end);
}
}
} else if (primaryCode == Keyboard.KEYCODE_SHIFT) {
// 大小写切换
changeKeyboardLetterCase();
// 重新setKeyboard, 进而系统重新加载, 键盘内容才会变化(切换大小写)
keyboardType = 1;
switchKeyboard();
} else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE) {
// 数字与字母键盘互换
if (keyboardType == 3) { //当前为数字键盘
keyboardType = 1;
} else { //当前不是数字键盘
keyboardType = 3;
}
switchKeyboard();
} else if (primaryCode == 100860) {
// 字母与符号切换
if (keyboardType == 2) { //当前是符号键盘
keyboardType = 1;
} else { //当前不是符号键盘, 那么切换到符号键盘
keyboardType = 2;
}
switchKeyboard();
} else {
// 输入键盘值
editable.replace(start, end, 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 changeKeyboardLetterCase() {
List<Keyboard.Key> keyList = keyboardLetter.getKeys();
if (isCapes) {
for (Keyboard.Key key : keyList) {
if (key.label != null && isUpCaseLetter(key.label.toString())) {
key.label = key.label.toString().toLowerCase();
key.codes[0] += 32;
}
}
} else {
for (Keyboard.Key key : keyList) {
if (key.label != null && isLowCaseLetter(key.label.toString())) {
key.label = key.label.toString().toUpperCase();
key.codes[0] -= 32;
}
}
}
isCapes = !isCapes;
}

private void switchKeyboard() {
switch (keyboardType) {
case 1:
keyboardView.setKeyboard(keyboardLetter);
abc.setVisibility(View.GONE);
break;
case 2:
keyboardView.setKeyboard(keyboardSymbol);
abc.setVisibility(View.GONE);
break;
case 3:
keyboardView.setKeyboard(keyboardNumber);
abc.setVisibility(View.VISIBLE);

break;
default:
break;
}
}

//隐藏键盘,但是显示光标
public static void hideSysKeyboard(Activity activity, EditText editText){
activity.getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
try {
Class<EditText> cls = EditText.class;
Method setSoftInputShownOnFocus;
setSoftInputShownOnFocus = cls.getMethod("setShowSoftInputOnFocus", boolean.class);
setSoftInputShownOnFocus.setAccessible(true);
setSoftInputShownOnFocus.invoke(editText, false);
} catch (Exception e) {
e.printStackTrace();
}

}


}

SateEditText.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.example.sun.savekeyboard.util;

import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.widget.EditText;
import android.widget.Scroller;

/**
* Create by Sun on 2019/6/20
* Desc:
*/
public class SateEditText extends android.support.v7.widget.AppCompatEditText {
private SateEditText editText;
private Activity activity;
private View mContentView;
private Scroller scroller;
private EditText.OnTouchListener onTouchListener=new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_UP){
SafeKeyboard.getInstance(getContext(),editText).showKeyboard((int)event.getRawX(),(int)event.getRawY());
}
return false;
}
};

public SateEditText(Context context) {
super(context);
init(context);
}

public SateEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);



}

private void init(Context context){
scroller=new Scroller(context);
editText=this;
activity= (Activity) context;
SafeKeyboard.hideSysKeyboard(activity,this);
this.setOnTouchListener(onTouchListener);
this.mContentView=activity.getWindow().getDecorView();
}
public View getmContentView() {
return mContentView;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
if(SafeKeyboard.getInstance(getContext(),this).hasHide()){
return false;
}else {
SafeKeyboard.getInstance(getContext(),this).hideKeyboard();
return true;
}

}else {
return super.onKeyDown(keyCode, event);
}
}
}