作者: 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) { // 按鍵盤確認鍵回撥此方法
Log.i("TEST", "------------->$text ")
}
/**
* 這裡當 觸控了 EditText 會回撥這,
* 如果不需要此回撥,可不用重寫
*/
override fun onTouchEditText(et: EditText) {
}
})
複製程式碼
使用就是這麼簡單, 如果有疑問請下方留言噢!
膜拜的大神:
- https://juejin.im/post/5ae0ff7ff265da0b9671e835 ,此處有詳細系統API解釋,我就沒有在文章中多講
- https://www.jianshu.com/p/b1973de976e4 , 效果和此大神類似,部分方法等也直接使用大神之作