Keyboard与KeyboardView --- 自定义键盘

【记录】记录点滴

场景: 实现自定义的身份证键盘

需求: 实现0-9数字键,X键,确认键(或其他),删除键

1. 利用Keyboard实现布局

在res/xml目录下创建keyboard的布局xml文件,如下:

<!-- keyWidth每个按键占的宽百分比,keyHeight按键高度 -->
<!-- verticalGap,horizontalGap 垂直/水平间隙-->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
    android:keyWidth="21.9%p"
    android:keyHeight="50dp"
    android:verticalGap="1px"
    android:horizontalGap="1px">
    <!-- 1,2,3,delete -->
    <Row>
        <!-- codes这里采用对应的ASCII值 -->
        <Key android:codes="49"
            android:keyEdgeFlags="left"
            android:keyLabel="1"
            android:isRepeatable="false" />
        <Key android:codes="50"
            android:keyLabel="2"
            android:isRepeatable="false"/>
        <Key android:codes="51"
            android:keyLabel="3"
            android:isRepeatable="false"/>
        <!-- 采用系统定义的值 -->
        <Key android:codes="-5"
            android:keyWidth="33.9%"
            android:keyHeight="100dp"
            android:isRepeatable="true"/>
    </Row>
    <!-- 4,5,6-->
    <Row>
        <Key android:codes="52"
            android:keyEdgeFlags="left"
            android:keyLabel="4"
            android:isRepeatable="false"/>
        <Key android:codes="53"
            android:keyLabel="5"
            android:isRepeatable="false"/>
        <Key android:codes="54"
            android:keyLabel="6"
            android:isRepeatable="false"/>
    </Row>
    <!-- 7,8,9,confirm -->
    <Row>
        <Key android:codes="55"
            android:keyEdgeFlags="left"
            android:keyLabel="7"
            android:isRepeatable="false"/>
        <Key android:codes="56"
            android:keyLabel="8"
            android:isRepeatable="false"/>
        <Key android:codes="57"
            android:keyLabel="9"
            android:isRepeatable="false"/>
        <Key android:codes="-4"
            android:keyWidth="33.9%"
            android:keyHeight="100dp"
            android:isRepeatable="false" />
    </Row>
    <!-- X,0 -->
    <Row>
        <Key android:codes="88"
            android:keyEdgeFlags="left"
            android:keyLabel="X"
            android:isRepeatable="false"/>
        <Key android:codes="48"
            android:keyLabel="0"
            android:keyWidth="43.9%p"
            android:isRepeatable="false"/>
    </Row>
</Keyboard>
/** 系统定义的值 */
public static final int EDGE_LEFT = 1;
public static final int EDGE_RIGHT = 2;
public static final int EDGE_TOP = 4;
public static final int EDGE_BOTTOM = 8;
public static final int KEYCODE_SHIFT = -1;
public static final int KEYCODE_MODE_CHANGE = -2;
public static final int KEYCODE_CANCEL = -3;
public static final int KEYCODE_DONE = -4;
public static final int KEYCODE_DELETE = -5
public static final int KEYCODE_ALT = -6;

 简单使用的话,只需要在布局文件中添加KeyboardView,并在代码中将它和上述的Keyboard布局关联起来,例如

<!-- xml布局 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="@android:color/darker_gray"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/et"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

    <android.inputmethodservice.KeyboardView
        android:id="@+id/kb_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:keyTextColor="#333333"/>

</LinearLayout>
    //Java代码
    Keyboard kb= new Keyboard(context, R.xml.keyboard);
    kbView.setKeyboard(kb);
    keyboardView.setEnabled(true);
    keyboardView.setPreviewEnabled(false);
    keyboardView.setOnKeyboardActionListener(new KeyboardView.OnKeyboardActionListener({
        ...
        ...
    });

 根据需求,在KeyboardView.OnKeyboardActionListener中实现onKey等

2. 基于KeyboardView实现自定义样式,如默认按键样式/点击效果

在我的场景中,需要将确认按钮的背景色设置为其他颜色,删除按钮用图案,按键实现按压效果,所以需要自定义样式

创建CustomKeyboardView继承KeyboardView,根据按键内容,状态绘制对应的按键

/** 只展示关键代码 */
public class CustomKeyboardView extends KeyboardView {

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Keyboard keyboard = getKeyboard();
        if(keyboard == null){
            return;
        }
        //获得全部按键
        List<Keyboard.Key> keys = keyboard.getKeys();
        if(keys != null && keys.size() > 0){
            for(Keyboard.Key key : keys){
                if(key.codes[0] == Keyboard.KEYCODE_DELETE){
                    //删除按键,绘制图案
                    Drawable drawable = getContext().getResources().getDrawable(R.drawable.common_keyboard_delete);
                    if(key.pressed){
                        //按压时
                        drawable.setAlpha(100);
                    } else {
                        drawable.setAlpha(255);
                    }
                    //根据key的坐标,计算图案的绘制位置
                    drawable.setBounds(key.x + 2*key.width/5, key.y + 2*key.height/5, (int) (key.x + key.width - 2.1f*key.width/5), (int) (key.y + key.height - 2.1f*key.height/5));
                    drawable.draw(canvas);
                } else if(key.codes[0] == Keyboard.KEYCODE_DONE){
                    //确认按键
                    Drawable drawable;
                    if(key.pressed){
                        drawable = getContext().getResources().getDrawable(R.drawable.common_shape_id_keyboard_check_pressed);
                    } else {
                        drawable = getContext().getResources().getDrawable(R.drawable.common_shape_id_keyboard_check_default);
                    }
                    drawable.setBounds(key.x, key.y, key.x + key.width, key.y + key.height);
                    drawable.draw(canvas);
                } else {
                    Drawable drawable;
                    if(key.pressed){
                        drawable = getContext().getResources().getDrawable(R.drawable.common_shape_id_keyboard_key_pressed);
                    } else {
                        drawable = getContext().getResources().getDrawable(R.drawable.common_shape_id_keyboard_key_default);
                    }
                    drawable.setBounds(key.x, key.y, key.x + key.width, key.y + key.height);
                    drawable.draw(canvas);
                }

                //现在这部分逻辑是拉出来的,但完全没有这个必要再判断一次
                //参照的其他博客,稍后列出
                if (key.label != null) {
                    //确认和删除按键没设置label
                    paint.setTextSize(textSize);
                    paint.setColor(getContext().getResources().getColor(android.R.color.black));
                    Rect rect = new Rect(key.x, key.y, key.x + key.width, key.y + key.height);
                    //计算字体的baseline
                    Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
                    int baseline = (rect.bottom + rect.top - fontMetrics.bottom - fontMetrics.top) / 2;
                    // 下面这行是实现水平居中
                    paint.setTextAlign(Paint.Align.CENTER);
                    canvas.drawText(key.label.toString(), rect.centerX(), baseline, paint);
                } else if(key.codes[0] == Keyboard.KEYCODE_DONE){
                    paint.setTextSize(textSize);
                    paint.setColor(getContext().getResources().getColor(android.R.color.white));
                    Rect rect = new Rect(key.x, key.y, key.x + key.width, key.y + key.height);
                    Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
                    int baseline = (rect.bottom + rect.top - fontMetrics.bottom - fontMetrics.top) / 2;
                    // 下面这行是实现水平居中
                    paint.setTextAlign(Paint.Align.CENTER);
                    canvas.drawText("确认", rect.centerX(), baseline, paint);
                }
            }
        }
    }
}

3. 简单实现其他效果,封装为工具类,拖动改变光标位置,防止长按、双击弹出系统键盘

(1) 简单封装,任意界面中弹出,把自定义键盘当做View,添加到当前的DecorView中,仅针对一个EditText使用。

    //为了能够隐藏Keyboard,所以创建静态变量private static IdKeyboardView idKeyboardView;
    //设置目标EditText,但是设置后不会弹出自定义键盘 
    public static void setCustomIDKeyboard(final Activity activity, final EditText target, final IdKeyboardView.KeyboardActionCallback callback){
        target.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
            int action = event.getActionMasked();
            if(action == MotionEvent.ACTION_UP){
                KeyboardUtils.showCustomIDKeyboard(activity, target, callback);
            }
            return true;
            }
        });
    }
    //设置目标EditText,并且设置后弹出自定义键盘
    public static void showCustomIDKeyboard(final Activity activity, EditText target, final IdKeyboardView.KeyboardActionCallback callback){
        if(System.currentTimeMillis() - lastClickTime <= 500){
            return;
        }
        lastClickTime = System.currentTimeMillis();
        //获取当前DecorView
        final ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
        if(idKeyboardView == null) {
            //这里用IdKeyboardView(继承LinearLayout)展示自定义键盘,方便后面加些其他控件
            idKeyboardView = new IdKeyboardView(activity);
            //设置目标Editext,setTarget会修改目标的Touch事件等,后续有部分代码
            idKeyboardView.setTarget(activity, target);
            //IdKeyboardView创建接口,用来响应“确认”按钮
            //如,在KeyboardView.OnKeyboardActionListener的onKey中调用
            idKeyboardView.setActionCallback(callback);
            decorView.addView(idKeyboardView);
            //addView之后设置LayoutParams才会生效
            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);
            lp.gravity = Gravity.BOTTOM;
            idKeyboardView.setLayoutParams(lp);
        }
    }
    //隐藏弹出的自定义键盘
    public static void hideCustomIDKeyboard(Activity activity){
        if(idKeyboardView != null){    
            final ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
            decorView.removeView(idKeyboardView);
            idKeyboardView = null;
        }
    }

(2) 为了提供更好的使用效果,对目标EditText设置setOnTouchListener,就防止了长按、双击等弹出系统键盘,但是如果需要这些功能就要自己实现。这里仅支持一个目标EditText

public void setTarget(final Context context, final EditText editText){
        //这里就有了局限性,仅支持一个目标EditText
        if(editText == this.editText){
            return;
        }
        this.editText = editText;       
        //获得EditText的画笔
        Paint paint = editText.getPaint();
        //计算文字宽度,取平均值作为每个字符的宽度,注意不是准确的值
        textWidth = (int) (paint.measureText("123456789X") / 10);        
        this.editText.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getActionMasked();

                if(action == MotionEvent.ACTION_UP){
                    //自己封装的工具类,展示键盘
                    KeyboardUtils.showCustomIDKeyboard((Activity) context, editText, callback);
                } else if(action == MotionEvent.ACTION_MOVE){
                    //改变光标位置
                    int index = (int) ((event.getX() - editText.getPaddingLeft()) / textWidth);
                    if(index <= 0){
                        editText.setSelection(0);
                    } else if(index >= editText.getText().length()){
                        editText.setSelection(editText.getText().length());
                    } else {
                        editText.setSelection(index);
                    }
                }
                return true;
            }
        });
}

(3)改变光标样式,设置EditText的textCursorDrawable=“@drawable/xxx”

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 光标宽 -->
    <size android:width="1dp"/>
    <!-- 光标颜色 -->
    <solid android:color="@color/color_you_want"/>
    <!-- 光标长度,top = 1dp,表示向上延伸1dp,top = -2dp,表示光标上侧向中心缩短2dp-->
    <!-- 同理,bottom = 1dp,表示向下延伸1dp,bottom = -2dp,表示光标下侧向中心缩短2dp-->
    <padding android:top="-2dp"
        android:bottom="-2dp"/>
</shape>

参考了  https://blog.csdn.net/qq_29983773/article/details/79501658

猜你喜欢

转载自blog.csdn.net/FeiLaughing/article/details/82899792