作者: Jooyer, 时间: 2018.6 .8
Github地址,欢迎点赞,fork
只需要四个类 + 几个 XML 文件即可,即拷即用
接下来我们依次讲解:
- NumberKeyboardView
- KeyboardContainer
- key_board_container.xml
- KeyboardUtil
- OnDoneListener
- 其他几个 XML 文件
首先我们看看 NumberKeyboardView 真面目:
// 这里必须继承自 KeyboardView,至于构造方法和获取属性则是一般套路
public class NumberKeyboardView extends KeyboardView {
private static final String TAG = NumberKeyboardView.class.getSimpleName();
private Drawable rKeyBackground;
private Paint mPaint;
private int rLabelTextSize;
private int rKeyTextSize;
private int rKeyTextColor;
private float rShadowRadius;
private int rShadowColor;
public NumberKeyboardView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}
public NumberKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context,attrs);
}
// 获取自定义的属性,很多 demo 使用反射,其他我们可以换个套路,自己定义属性就好了
private void init(Context context, AttributeSet attrs) {
TypedArray arr = context.obtainStyledAttributes(attrs,R.styleable.NumberKeyboardView);
rKeyBackground = arr.getDrawable(R.styleable.NumberKeyboardView_keyBackground);
if (null == rKeyBackground){
rKeyBackground = context.getResources().getDrawable(R.drawable.key_number_bg);
}
rLabelTextSize = arr.getDimensionPixelSize(R.styleable.NumberKeyboardView_labelTextSize,18);
rKeyTextSize = arr.getDimensionPixelSize(R.styleable.NumberKeyboardView_keyTextSize,18);
rKeyTextColor = arr.getColor(R.styleable.NumberKeyboardView_keyTextColor,0xFF000000);
rShadowColor = arr.getColor(R.styleable.NumberKeyboardView_shadowColor,0);
rShadowRadius = arr.getFloat(R.styleable.NumberKeyboardView_shadowRadius,0f);
arr.recycle();
// 初始化画笔
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(rKeyTextSize);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setAlpha(255);
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
onRefreshKey(canvas);
}
/**
* onRefreshKey是对父类的private void onBufferDraw()进行的重写.
* 只是在对key的绘制过程中进行了重新设置.
*/
private void onRefreshKey(Canvas canvas) {
final int kbdPaddingLeft = getPaddingLeft();
final int kbdPaddingTop = getPaddingTop();
Drawable keyBackground = null;
mPaint.setColor(rKeyTextColor);
List<Keyboard.Key> keys = getKeyboard().getKeys();
// 遍历每一个 key ,key 来自于 res/xml/ 中的文件定义的
for (Keyboard.Key key : keys) {
keyBackground = key.iconPreview;
if (null == keyBackground) {
keyBackground = rKeyBackground;
}
int[] drawableState = key.getCurrentDrawableState();
keyBackground.setState(drawableState);
CharSequence keyLabel = key.label;
String label = keyLabel == null ? null : adjustCase(keyLabel).toString();
final Rect bounds = keyBackground.getBounds();
if (key.width != bounds.right ||
key.height != bounds.bottom) {
keyBackground.setBounds(0, 0, key.width, key.height);
}
canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
// 绘制每一个 key 背景
keyBackground.draw(canvas);
if (label != null) { // 绘制每一个 key 上面的文本信息
mPaint.setColor(rKeyTextColor);
if (key.codes[0] == getKeyCode(R.integer.action_done)) {
mPaint.setTextSize(dp2px(rLabelTextSize));
mPaint.setTypeface(Typeface.DEFAULT);
mPaint.setColor(Color.WHITE);
} else if (key.codes[0] == getKeyCode(R.integer.line_feed)) {
mPaint.setTextSize(dp2px(16));
mPaint.setTypeface(Typeface.DEFAULT);
} else if (label.length() > 1 && key.codes.length < 2) {
mPaint.setTextSize(dp2px(rLabelTextSize));
mPaint.setTypeface(Typeface.DEFAULT);
} else {
mPaint.setTextSize(dp2px(rKeyTextSize));
mPaint.setTypeface(Typeface.DEFAULT);
}
// 绘制每一个 key(没有设置图片的) 底部阴影
mPaint.setShadowLayer(rShadowRadius, 0, 0, rShadowColor);
// Draw the text
canvas.drawText(label,
key.width/ 2,
key.height / 2 + (mPaint.getTextSize() - mPaint.descent()) / 2,
mPaint);
// Turn off drop shadow
mPaint.setShadowLayer(0, 0, 0, 0);
} else if (key.icon != null) { // 绘制键盘上的图标
final int drawableX = (key.width
- key.icon.getIntrinsicWidth()) / 2 ;
final int drawableY = (key.height
- key.icon.getIntrinsicHeight()) / 2 ;
canvas.translate(drawableX, drawableY);
key.icon.setBounds(0, 0,
key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
key.icon.draw(canvas);
canvas.translate(-drawableX, -drawableY);
}
canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
}
}
private int getKeyCode(@IntegerRes int redId) {
return getContext().getResources().getInteger(redId);
}
private int dp2px(int def) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, def, getResources().getDisplayMetrics());
}
// 参考了大神们的作品,其实这里都是数字键,这个方法没有什么作用,具体参考链接,文章末尾给出
private CharSequence adjustCase(CharSequence label) {
if (getKeyboard().isShifted() && label != null && label.length() < 3
&& Character.isLowerCase(label.charAt(0))) {
label = label.toString().toUpperCase();
}
return label;
}
// 动态设置 如 确定 这种文本大小
public void setTextSize(int textSize) {
rLabelTextSize = textSize;
}
}
复制代码
其实就是根据 xml 中数字和文本,图片绘制到 canvas 中.
然后我们看看 KeyboardContainer
public class KeyboardContainer extends ConstraintLayout {
private NumberKeyboardView mNumberKeyboardView;
public KeyboardContainer(Context context) {
this(context, null);
}
public KeyboardContainer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public KeyboardContainer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
View view = LayoutInflater.from(context).inflate(R.layout.key_board_container, this, true);
mNumberKeyboardView = (NumberKeyboardView) view.findViewById(R.id.number_keyboard_view);
mNumberKeyboardView.setEnabled(true);
mNumberKeyboardView.setPreviewEnabled(false);
}
public void setOnKeyboardActionListener(KeyboardView.OnKeyboardActionListener listener) {
mNumberKeyboardView.setOnKeyboardActionListener(listener);
}
public NumberKeyboardView getKeyboardView() {
return mNumberKeyboardView;
}
public void setKeyboardType(KeyboardType type) {
Keyboard keyboard = new Keyboard(getContext(), type == KeyboardType.NORMAL ?
R.xml.keyboard_number : R.xml.keyboard_dot_number);
mNumberKeyboardView.setTextSize(type == KeyboardType.NORMAL ? 24 : 18);
mNumberKeyboardView.setKeyboard(keyboard);
}
public enum KeyboardType {
NORMAL, DOT
}
}
复制代码
这里就是一个简单的自定义组合控件
接着,瞅瞅那个布局文件: key_board_container.xml
<?xml version="1.0" encoding="utf-8"?>
<cn.molue.jooyer.numberkeyboard.NumberKeyboardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/number_keyboard_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#d8d3cf"
android:focusable="true"
android:focusableInTouchMode="true"
app:keyBackground="@drawable/key_number_bg"
app:keyTextColor="#333333"
app:shadowColor="#ffffff"
app:shadowRadius="0"
/>
复制代码
app: 都是一些自定义的属性了, 布局也是非常的简单
接着看看比较重要的一个东西: KeyboardUtil
public class KeyboardUtil {
private static final String TAG = KeyboardUtil.class.getSimpleName();
private KeyboardView mKeyboardView;
private EditText mEditText;
private ViewGroup mRootView;
private Rect mRect = new Rect();
private KeyboardContainer mKeyboardContainer;
private FrameLayout.LayoutParams mKeyboardContainerParams;
private int dp2px(Context context, int def) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, def, context.getResources().getDisplayMetrics());
}
public KeyboardUtil(final Activity context, KeyboardContainer.KeyboardType type) {
mRootView = (ViewGroup) context.getWindow().getDecorView().findViewById(android.R.id.content);
mKeyboardContainer = new KeyboardContainer(context);
mKeyboardContainer.setOnKeyboardActionListener(mKeyboardActionListener);
mKeyboardContainerParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
mKeyboardContainerParams.gravity = Gravity.BOTTOM;
mKeyboardContainer.setKeyboardType(type);
}
@SuppressLint("ClickableViewAccessibility")
public void bindEditText(EditText editText) {
mEditText = editText;
mEditText.setTag(0);
mEditText.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (0 == ((int) v.getTag())) {
if (null != mOnDoneListener) {
mOnDoneListener.onTouchEditText(mEditText);
}
showSoftKeyboard();
}
if (mEditText.getText().length() > 0
&& mEditText.getSelectionStart() != mEditText.getText().length()) {
mEditText.setSelection(mEditText.getText().length());
}
return false;
}
});
mKeyboardContainer.setOnKeyboardActionListener(mKeyboardActionListener);
hideSystemSoftKeyboard();
}
private void showSoftKeyboard() {
mEditText.setTag(1);
mRootView.addOnLayoutChangeListener(mOnLayoutChangeListener);
mKeyboardContainer.setPadding(
dp2px(mEditText.getContext(), 0),
dp2px(mEditText.getContext(), -1),
dp2px(mEditText.getContext(), 0),
dp2px(mEditText.getContext(), 0));
if (mRootView.indexOfChild(mKeyboardContainer) == -1) { // 这个思路不错哦
if (null != mKeyboardContainer.getParent()) {
((ViewGroup) mKeyboardContainer.getParent()).removeView(mKeyboardContainer);
}
mRootView.addView(mKeyboardContainer, mRootView.getChildCount(), mKeyboardContainerParams);
} else {
mKeyboardContainer.setVisibility(View.VISIBLE);
}
mKeyboardContainer.setAnimation(AnimationUtils.loadAnimation(mEditText.getContext(),
R.anim.anim_bottom_in));
}
public void hideSoftKeyboard() {
if (null != mEditText && -1 != mRootView.indexOfChild(mKeyboardContainer)) {
mEditText.setTag(0);
mKeyboardContainer.startAnimation(AnimationUtils.loadAnimation(mEditText.getContext(),
R.anim.anim_bottom_out));
mKeyboardContainer.postDelayed(new Runnable() {
@Override
public void run() {
mRootView.removeView(mKeyboardContainer);
}
}, 400);
}
}
private void hideSystemSoftKeyboard() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mEditText.setShowSoftInputOnFocus(false);
} else {
mEditText.setInputType(InputType.TYPE_NULL);
}
}
private int getKeyCode(Context context, @IntegerRes int redId) {
return context.getResources().getInteger(redId);
}
private final KeyboardView.OnKeyboardActionListener mKeyboardActionListener = new KeyboardView.OnKeyboardActionListener() {
@Override
public void onPress(int primaryCode) {
// 按下 key 时执行
}
@Override
public void onRelease(int primaryCode) {
// 释放 key 时执行
}
// 点击 key 时执行
@Override
public void onKey(int primaryCode, int[] keyCodes) {
Editable editable = mEditText.getText();
int start = mEditText.getSelectionStart();
if (primaryCode == Keyboard.KEYCODE_CANCEL
|| primaryCode == getKeyCode(mEditText.getContext(), R.integer.hide_keyboard)) {
hideSoftKeyboard();
mOnDoneListener.onHide();
} else if (primaryCode == Keyboard.KEYCODE_DELETE) { // 回退
if (editable != null && editable.length() > 0) {
if (start > 0) {
editable.delete(start - 1, start);
}
}
} else if (primaryCode == getKeyCode(mEditText.getContext(), R.integer.action_done)) { // 确定
if (null != mOnDoneListener) {
mOnDoneListener.onDone(mEditText.getText().toString());
}
} else if (primaryCode == getKeyCode(mEditText.getContext(), R.integer.line_feed)) { // 换行
editable.insert(start, "\n");
} else { // 输入键盘值
editable.insert(start, Character.toString((char) primaryCode));
}
}
@Override
public void onText(CharSequence text) {
}
@Override
public void swipeLeft() {
}
@Override
public void swipeRight() {
}
@Override
public void swipeDown() {
}
@Override
public void swipeUp() {
}
};
private final View.OnLayoutChangeListener mOnLayoutChangeListener = new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
int hasMoved = 0;
Object rootViewTag = mRootView.getTag(R.id.root_view);
if (rootViewTag != null) {
hasMoved = (int) rootViewTag;
}
if (mKeyboardContainer.getVisibility() == View.GONE) {
mRootView.removeOnLayoutChangeListener(mOnLayoutChangeListener);
if (hasMoved > 0) {
mRootView.getChildAt(0).scrollBy(0, -1 * hasMoved);
mRootView.setTag(R.id.root_view, 0);
}
} else {
mRect.setEmpty();
mRootView.getWindowVisibleDisplayFrame(mRect);
int[] etLocation = new int[2];
mEditText.getLocationOnScreen(etLocation);
int keyboardTop = etLocation[1] + mEditText.getHeight()
+ mEditText.getPaddingTop() + mEditText.getPaddingBottom() + 1; //1px is a divider
Object anchorTag = mEditText.getTag(R.id.anchor_view);
View mShowAnchorView = null;
if (anchorTag != null && anchorTag instanceof View) {
mShowAnchorView = (View) anchorTag;
}
if (mShowAnchorView != null) {
int[] saLocation = new int[2];
mShowAnchorView.getLocationOnScreen(saLocation);
keyboardTop = saLocation[1] + mShowAnchorView.getHeight() + mShowAnchorView.getPaddingTop() + mShowAnchorView //1px is a divider
.getPaddingBottom() + 1;
}
int moveHeight = keyboardTop + mKeyboardContainer.getHeight() - mRect.bottom;
//height > 0 , rootView 需要继续上滑
if (moveHeight > 0) {
mRootView.getChildAt(0).scrollBy(0, moveHeight);
mRootView.setTag(R.id.root_view, hasMoved + moveHeight);
} else {
int moveBackHeight = Math.min(hasMoved, Math.abs(moveHeight));
if (moveBackHeight > 0) {
mRootView.getChildAt(0).scrollBy(0, -1 * moveBackHeight);
mRootView.setTag(R.id.root_view, hasMoved - moveBackHeight);
}
}
}
}
};
public KeyboardContainer getKeyboardContainer() {
return mKeyboardContainer;
}
private OnDoneListener mOnDoneListener;
public void setOnDoneListener(OnDoneListener onDoneListener) {
mOnDoneListener = onDoneListener;
}
}
复制代码
这里注释不多,大家看方法名,大体就明白啥意思了, 主要 mOnLayoutChangeListener ,当发现输入框被键盘遮挡时,会将输入框根视图上移,具体效果,可以打日志看哈
倒数第二就是看哈回调了,贴个代码,来凑行数....,哈哈
interface OnDoneListener {
// 当点击确定按钮时会回调此方法
fun onDone(text: String)
// 当 et 被点击后,需要操作可以在此处
fun onTouchEditText(et: EditText) {
}
}
复制代码
###最后就是几个我就一股脑都抛出来了,准备接招!!!
-
在 res/anim 中创建如下2个动画
anim_bottom_in.xml
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:duration="400" android:fromYDelta="100%p"/> </set> 复制代码
anim_bottom_out.xml
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:duration="400" android:toYDelta="100%p" /> </set> 复制代码
-
在 res/drawable 中创建如下几个 drawable 文件
keyboard_number.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@android:color/white"/> </shape> 复制代码
keyboard_number_pressed.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#b9afa9"/> </shape> 复制代码
key_number_bg.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" android:drawable="@drawable/keyboard_number_pressed"/> <item android:drawable="@drawable/keyboard_number"/> </selector> 复制代码
key_num_done.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" android:drawable="#9d472c"/> <item android:drawable="#c35b3a"/> </selector> 复制代码
-
在 res/values 下创建如下几个文件
keyboard_attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="NumberKeyboardView"> <!--键盘背景色 --> <attr name="keyBackground" format="color"/> <!-- 键盘文字颜色 --> <attr name="keyTextColor" format="color"/> <!-- 阴影颜色 --> <attr name="shadowColor" format="color"/> <!-- 例如 '完成' 这种文本 --> <attr name="labelTextSize" format="dimension"/> <!-- 键盘文字大小 --> <attr name="keyTextSize" format="dimension"/> <!-- 键盘阴影圆角 --> <attr name="shadowRadius" format="float"/> </declare-styleable> </resources> 复制代码
keyboard_codes.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <integer name="hide_keyboard">-10</integer> <integer name="action_done">-11</integer> <integer name="line_feed">-12</integer> </resources> 复制代码
keyboard_ids.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <item type="id" name="root_view"/> <item type="id" name="anchor_view" /> </resources> 复制代码
-
在 res/ xml (xml文件夹需要自己建立)创建如下2个文件
keyboard_dot_number.xml ,多了一个换行和 ','
<?xml version="1.0" encoding="UTF-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:horizontalGap="0.1333%p"
android:keyHeight="49dp"
android:keyWidth="24.9%p"
android:verticalGap="1px">
<Row>
<Key
android:codes="49"
android:keyEdgeFlags="left"
android:keyLabel="1"/>
<Key
android:codes="50"
android:keyLabel="2"/>
<Key
android:codes="51"
android:keyLabel="3"/>
<Key
android:codes="-5"
android:keyIcon="@mipmap/my_numkb_backspace"
android:isRepeatable="true"
/>
</Row>
<Row>
<Key
android:codes="52"
android:keyEdgeFlags="left"
android:keyLabel="4"/>
<Key
android:codes="53"
android:keyLabel="5"/>
<Key
android:codes="54"
android:keyLabel="6"/>
<Key
android:codes="44"
android:keyLabel=","/>
</Row>
<Row android:horizontalGap="1px">
<Key
android:codes="55"
android:keyEdgeFlags="left"
android:keyLabel="7"/>
<Key
android:codes="56"
android:keyLabel="8"/>
<Key
android:codes="57"
android:keyLabel="9"/>
<Key
android:codes="43"
android:keyLabel="+"/>
</Row>
<Row android:horizontalGap="1px">
<Key
android:codes="@integer/hide_keyboard"
android:keyIcon="@mipmap/my_numkb_hide"
android:keyEdgeFlags="left"/>
<Key
android:codes="48"
android:keyLabel="0"/>
<Key
android:codes="@integer/line_feed"
android:keyLabel="换行"/>
<Key
android:codes="@integer/action_done"
android:iconPreview="@drawable/key_num_done"
android:keyLabel="确定"/>
</Row>
</Keyboard>
复制代码
keyboard_number.xml
<?xml version="1.0" encoding="UTF-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:horizontalGap="0.1333%p"
android:keyHeight="49dp"
android:keyWidth="24.9%p"
android:verticalGap="1px">
<Row>
<Key
android:codes="49"
android:keyEdgeFlags="left"
android:keyLabel="1"/>
<Key
android:codes="50"
android:keyLabel="2"/>
<Key
android:codes="51"
android:keyLabel="3"/>
<Key
android:codes="-5"
android:keyIcon="@mipmap/my_numkb_backspace"
android:isRepeatable="true"
android:keyHeight="99dp"
/>
</Row>
<Row>
<Key
android:codes="52"
android:keyEdgeFlags="left"
android:keyLabel="4"/>
<Key
android:codes="53"
android:keyLabel="5"/>
<Key
android:codes="54"
android:keyLabel="6"/>
</Row>
<Row android:horizontalGap="1px">
<Key
android:codes="55"
android:keyEdgeFlags="left"
android:keyLabel="7"/>
<Key
android:codes="56"
android:keyLabel="8"/>
<Key
android:codes="57"
android:keyLabel="9"/>
<Key
android:codes="@integer/action_done"
android:iconPreview="@drawable/key_num_done"
android:keyHeight="99dp"
android:keyLabel="确定"/>
</Row>
<Row android:horizontalGap="1px">
<Key
android:codes="@integer/hide_keyboard"
android:keyIcon="@mipmap/my_numkb_hide"
android:keyEdgeFlags="left"/>
<Key
android:codes="48"
android:keyLabel="0"
android:keyWidth="49.9%p"/>
</Row>
</Keyboard>
复制代码
这样一个简单易用的键盘就完成了,可以根据需要自己定制.
下面看看基本用法
// KeyboardType 一般类型是没有逗号的,另一种有逗号
val keyboardUtil = KeyboardUtil(this,
KeyboardContainer.KeyboardType.NORMAL);
keyboardUtil.bindEditText(et_test) // 绑定一个 EditText
keyboardUtil.setOnDoneListener(object : OnDoneListener {
override fun onDone(text: String) { // 按键盘确认键回调此方法
keyboardUtil.hideSoftKeyboard()
Log.i("TEST", "------------->$text ")
}
/**
* 这里当 触摸了 EditText 会回调这,
* 如果不需要此回调,可不用重写
*/
override fun onTouchEditText(et: EditText) {
}
})
复制代码
使用就是这么简单, 如果有疑问请下方留言噢!
膜拜的大神:
- https://juejin.im/post/5ae0ff7ff265da0b9671e835 ,此处有详细系统API解释,我就没有在文章中多讲
- https://www.jianshu.com/p/b1973de976e4 , 效果和此大神类似,部分方法等也直接使用大神之作