Android InputMethodService|KeyboardView 自定义输入法和键盘 01

如何自定义 安卓输入法 和 键盘

1.首先有几个关键类

1.InputMethodService 2.Keyboard 3.KeyboardView

1.1 InputMethodService

看下这个类的介绍


Android InputMethodService|KeyboardView 自定义输入法和键盘 01_第1张图片
图片.png

InputMethodService provides a standard implementation of an InputMethod
作用 提供一个标准键盘实现 balabala.....

Android InputMethodService|KeyboardView 自定义输入法和键盘 01_第2张图片
输入法生命周期.png

请参考 http://blog.csdn.net/weijinqian0/article/details/76906317

1.2.Keyboard 源码分析

Android InputMethodService|KeyboardView 自定义输入法和键盘 01_第3张图片
图片.png

xmL 定义键盘的属性 : 键位宽/高/水平间距/垂直间距/按键文字图标/键值.....
键盘的UI样式就在这个类里面定义,准确的说 是在keyboard加载的xml文件中定义

构造方法
传入键盘布局文件id

    /**
     * Creates a keyboard from the given xml key layout file.
     * @param context the application or service context
     * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
     */
    public Keyboard(Context context, int xmlLayoutResId) {
    this(context, xmlLayoutResId, 0);
   }


     /**
 * Creates a keyboard from the given xml key layout file. Weeds out rows
 * that have a keyboard mode defined but don't match the specified mode.
 * @param context the application or service context
 * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
 * @param modeId keyboard mode identifier
 * @param width sets width of keyboard
 * @param height sets height of keyboard
 */
public Keyboard(Context context, @XmlRes int xmlLayoutResId, int modeId, int width,
        int height) {
    mDisplayWidth = width;
    mDisplayHeight = height;

    mDefaultHorizontalGap = 0;
    mDefaultWidth = mDisplayWidth / 10;
    mDefaultVerticalGap = 0;
    mDefaultHeight = mDefaultWidth;
    mKeys = new ArrayList();
    mModifierKeys = new ArrayList();
    mKeyboardMode = modeId;
//加载键盘
    loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
}

xml 解析键盘布局

private void loadKeyboard(Context context, XmlResourceParser parser) {
    boolean inKey = false;
    boolean inRow = false;
    boolean leftMostKey = false;
    int row = 0;
    int x = 0;
    int y = 0;
    Key key = null;
    Row currentRow = null;
    Resources res = context.getResources();
    boolean skipRow = false;

    try {
        int event;
        while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
            if (event == XmlResourceParser.START_TAG) {
                String tag = parser.getName();
                if (TAG_ROW.equals(tag)) {
                    inRow = true;
                    x = 0;
                    currentRow = createRowFromXml(res, parser);
                    rows.add(currentRow);
                    skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode;
                    if (skipRow) {
                        skipToEndOfRow(parser);
                        inRow = false;
                    }
               } else if (TAG_KEY.equals(tag)) {
                    inKey = true;
                    key = createKeyFromXml(res, currentRow, x, y, parser);
                    mKeys.add(key);
                    if (key.codes[0] == KEYCODE_SHIFT) {
                        // Find available shift key slot and put this shift key in it
                        for (int i = 0; i < mShiftKeys.length; i++) {
                            if (mShiftKeys[i] == null) {
                                mShiftKeys[i] = key;
                                mShiftKeyIndices[i] = mKeys.size()-1;
                                break;
                            }
                        }
                        mModifierKeys.add(key);
                    } else if (key.codes[0] == KEYCODE_ALT) {
                        mModifierKeys.add(key);
                    }
                    currentRow.mKeys.add(key);
                } else if (TAG_KEYBOARD.equals(tag)) {
                    parseKeyboardAttributes(res, parser);
                }
            } else if (event == XmlResourceParser.END_TAG) {
                if (inKey) {
                    inKey = false;
                    x += key.gap + key.width;
                    if (x > mTotalWidth) {
                        mTotalWidth = x;
                    }
                } else if (inRow) {
                    inRow = false;
                    y += currentRow.verticalGap;
                    y += currentRow.defaultHeight;
                    row++;
                } else {
                    // TODO: error or extend?
                }
            }
        }
    } catch (Exception e) {
        Log.e(TAG, "Parse error:" + e);
        e.printStackTrace();
    }
    mTotalHeight = y - mDefaultVerticalGap;
}

1.3.KeyboardView :自定义键盘一般继承keyboardView 重写其方法

Android InputMethodService|KeyboardView 自定义输入法和键盘 01_第4张图片
图片.png

作用:渲染按键 侦测按压


2. 具体实现

2.1 自定义键盘继承KeyboardView 当然也可以不定义 直接使用keyboardView

2.2 创建布局文件

 


   


 



2.3 新建服务继承InputMethodService

 public class MyInputMethodService extends InputMethodService {
 @Override
 public View onCreateInputView() {

    View view = getLayoutInflater().
            //键盘的布局文件
            inflate(R.layout.keyboard_global, null);

    return view;
  }
 }

清单文件

    
        
            
        
        
    

2.4 xml 定义键盘的resource 文件 和 不同键盘布局文件


Android InputMethodService|KeyboardView 自定义输入法和键盘 01_第5张图片
图片.png

字母键盘的布局






    
    
    
    
    
    
    
    
    
    


    
    
    
    
    
    
    
    
    




    
    
    
    
    
    
    
    

    






    
    

    
    

    




xml 中定义布局的 属性

keyLabel 按键显示的内容
keyIcon 按键显示的图标内容
keyWidth 按键的宽度
keyHeight 按键的高度
horizontalGap 代表按键前的间隙水平方向上的
isSticky 按键是否是sticky的,就像shift 键 具有两种状态
isModifier 按键是不是功能键
keyOutputText 指定按键输出的内容是字符串
isRepeatable 按键是可重复的,如果长按键可以触发重复按键事件则为true,else为false
keyEdgeFlags 指定按键的对齐指令,取值为left或者right

设置和选择输入法
跳转输入法设置界面:

Intent intent = new Intent();
intent.setAction( Settings.ACTION_INPUT_METHOD_SETTINGS);
SoftDemoActivity.this.startActivity(intent);

Android InputMethodService|KeyboardView 自定义输入法和键盘 01_第6张图片
设置输入法.png

弹出输入法选择框

((InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE)).showInputMethodPicker();


Android InputMethodService|KeyboardView 自定义输入法和键盘 01_第7张图片
选择输入法.png
Android InputMethodService|KeyboardView 自定义输入法和键盘 01_第8张图片
Kapture 2018-02-02 at 15.08.06.gif

3问题处理

3.1 第一排按键预览位置错误

Android InputMethodService|KeyboardView 自定义输入法和键盘 01_第9张图片
图片.png

对比下讯飞输入法


Android InputMethodService|KeyboardView 自定义输入法和键盘 01_第10张图片
图片.png

可以看出讯飞输入法上面还有一个透明布局

按键的预览本质是一个popupwindow


Android InputMethodService|KeyboardView 自定义输入法和键盘 01_第11张图片
图片.png

在keyboardViwe 源码中可以看到按键预览的实现方式,按键预览的pop的位置不会超过给 InputMethodService 的布局的范围
更证: 其实onCreateCandidatesView 中设置candidatesView也可以
override fun onCreateCandidatesView(): View {
// return super.onCreateCandidatesView()
return 自定义的view
}

问题解决: 只要给InputMethodService 设置的布局的高度大于布局中的keyboardView的高度,那么就可以显示正常


Android InputMethodService|KeyboardView 自定义输入法和键盘 01_第12张图片
图片.png

3.2 给不同按键设置不同背景

keyboardView 可以给按键设置背景 但是是给所有的按键设置

这个地方不是在xml 资源文件中设置按键的keyIcon 而是重写keyboardView的Ondraw 根据不同keycode重新绘制

3.3 禁止部分按键的按键预览

1.在 keyboardView的监听 OnKeyboardActionListener 回调的onPress()中处理

  @Override
    public void onPress(int primaryCode) {

        //设置某些按键不显示预览的view
        LogUtil.d(TAG, "-->Keyboard: onPress at >>" + primaryCode);
        KeyboardUtils.vibrate(20);
        if (primaryCode == Keyboard.KEYCODE_SHIFT || primaryCode == Keyboard.KEYCODE_DELETE  //
                || primaryCode == Keyboard.KEYCODE_DONE || primaryCode == VipKeyboardCode.CODE_SPACE //
                || primaryCode == VipKeyboardCode.CODE_TYPE_QWERTY || primaryCode == VipKeyboardCode.CODE_TYPE_NUM //
                || primaryCode == VipKeyboardCode.CODE_TYPE_SYMBOL || primaryCode == VipKeyboardCode.CODE_OPEN_VIP //
                || primaryCode == VipKeyboardCode.CODE_TYPE_SWITCH_INPUT || primaryCode == Keyboard.KEYCODE_MODE_CHANGE) {
            setPreviewEnabled(false);
        } else {
            setPreviewEnabled(true);
        }
    }

2.上面处理了点击是没问题了 但是在键盘上滑动时,滑动到每个按键还是会显示preView

所以可以重写KeyboardView的OnTouchEvent方法处理

/**
 * 处理滑动时 回车等键位的 按键预览 出现问题
 */
@Override
public boolean onTouchEvent(MotionEvent me) {


    float x = me.getX();
    float y = me.getY();
    switch (me.getAction()) {

        case MotionEvent.ACTION_DOWN:
            mDownX = x;
            mDownY = y;
            break;

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            //取消预览
            setPreviewEnabled(false);
            setPopupOffset(0, ScreenUtil.dp2px(0));
            break;

        case MotionEvent.ACTION_MOVE:
            setPreviewEnabled(false);
            //滑动距离小于10dp时不隐藏键盘预览 大于10dp时隐藏键盘按键预览
            if (Math.abs(x - mDownX) > ScreenUtil.dp2px(10) || Math.abs(y - mDownY) > ScreenUtil
                    .dp2px(10)) {
                //取消预览
                setPopupOffset(0, ScreenUtil.dp2px(0));
            }
            break;
    }


    return super.onTouchEvent(me);

}

demo地址 https://github.com/qingyc/AndroidKeyboardViewDemo

你可能感兴趣的:(Android InputMethodService|KeyboardView 自定义输入法和键盘 01)