自定义软键盘(屏蔽系统输入法,随机键盘)

自定义软键盘-屏蔽系统输入法,随机键盘

    • 核心代码
        • RandomKeyboard
        • RandomKeyboardViewGroup
    • 自定义RandomKeyboard
    • 自定义keyboardViewGroup
    • 其他资源
        • OnCustomKeyDealListener
        • OnKeyboardToggleListener
        • KeyModel.class
        • KEYBOARD_TYPE
        • @KeyboardType
        • random_keyboard_viewgroup.xml
        • styles.xml
        • attrs.xml
        • keyboard_preview.xml
        • bg_key_preview.xml
        • bg_keyboard.xml
        • letter_keyboard_layout.xml
        • letter_num_keyboard_layout.xml
        • num_symbol_keyboard_layout.xml
        • number_keyboard_layout.xml
        • symbol_keyboard_layout.xml
    • 附(KeyboardView,keyboard,Key,Row属性解释)
    • 相关


实现思路:
1.自定义RandomKeyboard,继承系统keyboard
2.自定义keyboardViewGroup(线性布局,含一级子控件NestedScrollView和自定义RandomKeyboard),重新addview方法使添加的子控件添加到NestedScrollView里。可在这个控件初始化时屏蔽系统输入法,监听事件分发通过scrollview中findFocus获取当前有焦点的edittext控件并绑定RandomKeyboard

核心代码

RandomKeyboard

  • 键盘操作
 public void onKey(int primaryCode, int[] keyCodes) {
        Editable editable = null;
        int selectionStart = 0;
        if (editText != null) {
            editable = editText.getText();
            selectionStart = editText.getSelectionStart();
        }
        if (keyboard != null) {
            switch (primaryCode) {
                case Keyboard.KEYCODE_SHIFT://shift处理 大小写字母切换
                    keyboard.setShifted(!keyboard.isShifted());
                    invalidateAllKeys();
                    break;
                case Keyboard.KEYCODE_DELETE://删除处理
                    if (editable != null && editable.length() > 0 && selectionStart > 0) {
                        editable.delete(selectionStart - 1, selectionStart);
                    }
                    break;
                   
              ....
              自定义键盘操作
              ....
              
                case ENTER_KEYBOARD://enter处理
                    if (editText != null && editText.getImeOptions() == EditorInfo.IME_ACTION_NEXT) {
                        @SuppressLint("WrongConstant") View view = editText.focusSearch(FOCUS_FORWARD);
                        editText.onEditorAction(EditorInfo.IME_ACTION_NEXT);
                        if (view instanceof EditText) {
                            bindEditText((EditText) view);
                        }
                    } else {
                        if (editText != null && editText.hasFocus()) {
                            editText.clearFocus();
                        }
                        hide();
                    }
                    break;
                default:
                   if (customKeyDealListener == null || !customKeyDealListener.onKey(editText, primaryCode, keyCodes)) {
                        if (primaryCode >= 97 && primaryCode <= 97 + 26) {
                            if (editText != null && editable != null) {
                                editable.insert(selectionStart,
                                        keyboard.isShifted() ? Character.toString((char) (primaryCode - 32))
                                                : Character.toString((char) primaryCode));
                            }
                        } else {
                            if (editable != null) {
                                editable.insert(
                                        selectionStart,
                                        Character.toString((char) primaryCode));
                            }
                        }
                    }
                    break;
            }
        }
    }

  • 随机打乱key
    private RandomKeyboard setRandom() {
        if (keyType == TYPE_SYMBOL || keyType == TYPE_LETTER_AND_NUM) {
            return this;
        }
        if (keyboard != null) {
            List newKeys = keyboard.getKeys();
            if (checkList(newKeys)) {
                containerKeys.clear();
                resultKeys.clear();
                //筛选并缓存可随机的key
                for (Keyboard.Key key : newKeys) {
                    if (key != null && isNeedRandom(key.label)) {
                        containerKeys.add(new KeyModel(key.codes, key.label));
                    }
                }
                //随机打乱筛选出的key
                Random rand = new SecureRandom();
                int size = containerKeys.size();
                for (int i = 0; i < size; i++) {
                    int num = rand.nextInt(size - i);
                    resultKeys.add(containerKeys.get(num));
                    containerKeys.remove(num);
                }
                //将打乱的key赋值给键盘空位(第一步中取值的位置)
                int count = 0;
                for (Keyboard.Key key : newKeys) {
                    if (key != null && isNeedRandom(key.label) && checkListItem(resultKeys, count)) {
                        KeyModel randomKey = resultKeys.get(count);
                        key.label = randomKey.getLable();
                        key.codes = randomKey.getCodes();
                        count++;
                    }
                }
                invalidateAllKeys();
                isRandom = true;
            }
        }
        return this;
    }
  • 禁用系统输入法
 //禁用系统输入法
       if (context instanceof Activity) {
            ((Activity) context).getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
        } else {
            Activity currentActivity = AppManager.getAppManager().getCurrentActivity();
            if (currentActivity != null) {
                currentActivity.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
            }
        }

RandomKeyboardViewGroup

  • 自动查找绑定EditText
   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean touchEvent = super.dispatchTouchEvent(ev);
         View editFocus = findEditFocus();

        if (editFocus != null && editFocus.hasFocus()) {
            keyboard.bindEditText((EditText) editFocus);
            keyboard.show();
        } else {
            keyboard.hide();
        }

        if (ViewUtils.isOutsideTheView(editFocus, ev) && ViewUtils.isOutsideTheView(keyboard, ev)) {
            editFocus.clearFocus();
        }
        return touchEvent;
    }

    public View findEditFocus() {
        View currentFocus = slContent != null ? slContent.findFocus() : null;
        return currentFocus instanceof EditText ? currentFocus : null;
    }

自定义RandomKeyboard

继承自KeyboardView
实现OnKeyboardActionListener监听
重写 public void onKey(int primaryCode, int[] keyCodes) 方法控制软键盘操作

public class RandomKeyboard extends KeyboardView implements KeyboardView.OnKeyboardActionListener, LifecycleObserver {

    private static final int LETTER_KEYBOARD = -10;
    private static final int NUMBER_KEYBOARD = -11;
    private static final int SYMBOL_KEYBOARD = -12;
    private static final int ENTER_KEYBOARD = -13;

    private Context context;
    private Keyboard keyboard;
    private EditText editText;

    private boolean isRandom = false;
    private boolean isNeedRandom;
    private int keyType;
    private int defLetterType = TYPE_LETTER;
    private int defNumberType = TYPE_NUMBER;

    private List containerKeys = new ArrayList<>();
    private List resultKeys = new ArrayList<>();
    private OnKeyboardToggleListener toggleListener;
    private OnCustomKeyDealListener customKeyDealListener;

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    public void onDestroy() {
        if (containerKeys instanceof ArrayList) {
            containerKeys.clear();
            containerKeys = null;
        }
        if (resultKeys instanceof ArrayList) {
            resultKeys.clear();
            resultKeys = null;
        }
        containerKeys = null;
        resultKeys = null;
        if (context instanceof LifecycleOwner) {
            ((LifecycleOwner) context).getLifecycle().removeObserver(this);
        }
        context = null;
        editText = null;
    }

    public RandomKeyboard(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RandomKeyboard(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        if (context instanceof LifecycleOwner) {
            ((LifecycleOwner) context).getLifecycle().addObserver(this);
        }
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RandomKeyboard);
        isNeedRandom = typedArray.getBoolean(R.styleable.RandomKeyboard_isRandom, false);
        keyType = typedArray.getInt(R.styleable.RandomKeyboard_keyboardType, TYPE_LETTER);
        int resourceId = typedArray.getResourceId(R.styleable.RandomKeyboard_android_keyboardLayout, 0);
        typedArray.recycle();
        setCustomKeyboard(resourceId);
        setOnKeyboardActionListener(this);
        hide();
        //禁用系统输入法
        if (context instanceof Activity) {
            ((Activity) context).getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
        } else {
            Activity currentActivity = AppManager.getAppManager().getCurrentActivity();
            if (currentActivity != null) {
                currentActivity.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
            }
        }
    }

    /**
     * 绑定EditText
     */
    public RandomKeyboard bindEditText(EditText editText) {
        this.editText = editText;
        if (editText != null) {
            int imeOptions = editText.getImeOptions();
            List keys = CheckValueUtils.checkNotNull(keyboard).getKeys();
            for (int i = 0; i < checkList(keys, "").size(); i++) {
                Keyboard.Key key = keys.get(i);
                if (key.codes[0] == -13) {
                    key.label = imeOptions == EditorInfo.IME_ACTION_NEXT ? "下一步" : "完成";
                    invalidateKey(i);
                    return this;
                }
            }
        }
        return this;
    }

    public RandomKeyboard setCustomKeyboard(@XmlRes int xml) {
        try {
            context.getResources().getXml(xml);
            keyboard = new Keyboard(context, xml);
            setKeyboard(keyboard);
            keyType = TYPE_CUSTOM;
            isRandom(isNeedRandom);
        } catch (Resources.NotFoundException e) {
            setKeyboard(keyType);
        }
        return this;
    }

    /**
     * @see com.quanyou.libraryview.common.Constant.KEYBOARD_TYPE
     */
    public RandomKeyboard setKeyboard(@KeyboardType int type) {
        keyType = type;
        switch (type) {
            case TYPE_LETTER:
                defLetterType = TYPE_LETTER;
                keyboard = new Keyboard(context, R.xml.letter_keyboard_layout);
                break;
            case TYPE_NUMBER:
                defNumberType = TYPE_NUMBER;
                keyboard = new Keyboard(context, R.xml.number_keyboard_layout);
                break;
            case TYPE_SYMBOL:
                keyboard = new Keyboard(context, R.xml.symbol_keyboard_layout);
                break;
            case TYPE_LETTER_AND_NUM:
                defLetterType = TYPE_LETTER_AND_NUM;
                keyboard = new Keyboard(context, R.xml.letter_num_keyboard_layout);
                break;
            case TYPE_NUM_AND_SYMBOL:
                defNumberType = TYPE_NUM_AND_SYMBOL;
                keyboard = new Keyboard(context, R.xml.num_symbol_keyboard_layout);
                break;
            default:
                keyboard = new Keyboard(context, R.xml.letter_keyboard_layout);
                break;
        }
        setKeyboard(keyboard);
        isRandom(isNeedRandom);
        return this;
    }

    public Keyboard getKeyboard() {
        return keyboard;
    }

    @Override
    public void onKey(int primaryCode, int[] keyCodes) {
        Editable editable = null;
        int selectionStart = 0;
        if (editText != null) {
            editable = editText.getText();
            selectionStart = editText.getSelectionStart();
        }
        if (keyboard != null) {
            switch (primaryCode) {
                case Keyboard.KEYCODE_SHIFT://shift处理 大小写字母切换
                    keyboard.setShifted(!keyboard.isShifted());
                    invalidateAllKeys();
                    break;
                case Keyboard.KEYCODE_DELETE://删除处理
                    if (editable != null && editable.length() > 0 && selectionStart > 0) {
                        editable.delete(selectionStart - 1, selectionStart);
                    }
                    break;
                case LETTER_KEYBOARD:
                    setKeyboard(defLetterType);
                    break;
                case NUMBER_KEYBOARD:
                    setKeyboard(defNumberType);
                    break;
                case SYMBOL_KEYBOARD:
                    setKeyboard(TYPE_SYMBOL);
                    break;
                case ENTER_KEYBOARD://enter处理
                    if (editText != null && editText.getImeOptions() == EditorInfo.IME_ACTION_NEXT) {
                        @SuppressLint("WrongConstant") View view = editText.focusSearch(FOCUS_FORWARD);
                        editText.onEditorAction(EditorInfo.IME_ACTION_NEXT);
                        if (view instanceof EditText) {
                            bindEditText((EditText) view);
                        }
                    } else {
                        if (editText != null && editText.hasFocus()) {
                            editText.clearFocus();
                        }
                        hide();
                    }
                    break;
                default:
                    if (customKeyDealListener == null || !customKeyDealListener.onKey(editText, primaryCode, keyCodes)) {
                        if (primaryCode >= 97 && primaryCode <= 97 + 26) {
                            if (editText != null && editable != null) {
                                editable.insert(selectionStart,
                                        keyboard.isShifted() ? Character.toString((char) (primaryCode - 32))
                                                : Character.toString((char) primaryCode));
                            }
                        } else {
                            if (editable != null) {
                                editable.insert(
                                        selectionStart,
                                        Character.toString((char) primaryCode));
                            }
                        }
                    }
                    break;
            }
        }
    }

    @Override
    public void onPress(int primaryCode) {

    }

    @Override
    public void onRelease(int primaryCode) {

    }

    @Override
    public void onText(CharSequence text) {

    }

    @Override
    public void swipeLeft() {

    }

    @Override
    public void swipeRight() {

    }

    @Override
    public void swipeDown() {

    }

    @Override
    public void swipeUp() {

    }

    /**
     * @see #setKeyboard(int)
     */
    @Override
    @Deprecated
    public final void setKeyboard(Keyboard keyboard) {
        isRandom = false;
        super.setKeyboard(keyboard);
    }

    /**
     * 随机打乱key键
     *
     * @see Constant.KEYBOARD_TYPE#TYPE_SYMBOL 字符模式不可用
     * @see Constant.KEYBOARD_TYPE#TYPE_LETTER_AND_NUM 字母数字模式不可用
     */
    public RandomKeyboard isRandom(boolean isRandom) {
        isNeedRandom = isRandom;
        if (this.isRandom == isRandom) {
            return this;
        }
        if (isRandom) {
            setRandom();
        } else {
            setKeyboard(keyType);
        }
        return this;
    }

    public boolean isRandom() {
        return isRandom;
    }

    /**
     * 随机打乱key键
     *
     * @see Constant.KEYBOARD_TYPE#TYPE_SYMBOL 字符模式不可用
     * @see Constant.KEYBOARD_TYPE#TYPE_LETTER_AND_NUM 字母数字模式不可用
     */
    private RandomKeyboard setRandom() {
        if (keyType == TYPE_SYMBOL || keyType == TYPE_LETTER_AND_NUM) {
            return this;
        }
        if (keyboard != null) {
            List newKeys = keyboard.getKeys();
            if (checkList(newKeys)) {
                containerKeys.clear();
                resultKeys.clear();
                //筛选并缓存可随机的key
                for (Keyboard.Key key : newKeys) {
                    if (key != null && isNeedRandom(key.label)) {
                        containerKeys.add(new KeyModel(key.codes, key.label));
                    }
                }
                //随机打乱筛选出的key
                Random rand = new SecureRandom();
                int size = containerKeys.size();
                for (int i = 0; i < size; i++) {
                    int num = rand.nextInt(size - i);
                    resultKeys.add(containerKeys.get(num));
                    containerKeys.remove(num);
                }
                //将打乱的key赋值给键盘空位(第一步中取值的位置)
                int count = 0;
                for (Keyboard.Key key : newKeys) {
                    if (key != null && isNeedRandom(key.label) && checkListItem(resultKeys, count)) {
                        KeyModel randomKey = resultKeys.get(count);
                        key.label = randomKey.getLable();
                        key.codes = randomKey.getCodes();
                        count++;
                    }
                }
                invalidateAllKeys();
                isRandom = true;
            }
        }
        return this;
    }

    private boolean isNeedRandom(CharSequence s) {
        return !TextUtils.isEmpty(s) && s.length() == 1 && (CheckValueUtils.isNumber(s.toString()) || CheckValueUtils.isLetter(s.toString()));
    }

    public RandomKeyboard show() {
        if (getVisibility() != VISIBLE) {
            setVisibility(VISIBLE);
            if (toggleListener != null) {
                toggleListener.onToggle(true);
            }
        }
        return this;
    }

    public RandomKeyboard hide() {
        if (getVisibility() != GONE || getVisibility() != INVISIBLE) {
            setVisibility(GONE);
            if (toggleListener != null) {
                toggleListener.onToggle(false);
            }
        }
        return this;
    }

    public void setOnKeyboardToggleListener(OnKeyboardToggleListener toggleListener) {
        this.toggleListener = toggleListener;
    }

    public RandomKeyboard setOnCustomKeyDealListener(OnCustomKeyDealListener customKeyDealListener) {
        this.customKeyDealListener = customKeyDealListener;
        return this;
    }
}

自定义keyboardViewGroup

使用方法:在activity布局文件中使用,位置:可做根布局、或置于toolbar之下到根布局底部,可假定为NestedScrollView使用。
注意:
1、keyboardViewGroup中有且只有一个子布局(因这里的布局会加载到NestedScrollView 中)。
2、无需手动绑定EditText,RandomKeyboardViewGroup 自动查找有焦点的EditText并绑定。

public class RandomKeyboardViewGroup extends FrameLayout implements LifecycleObserver {
    private Context context;
    private View inflate;
    private NestedScrollView slContent;
    private RandomKeyboard keyboard;

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    public void onDestroy() {
        if (slContent != null) {
            slContent.removeAllViews();
            slContent = null;
        }
        inflate = null;
        if (context instanceof LifecycleOwner) {
            ((LifecycleOwner) context).getLifecycle().removeObserver(this);
        }
        context = null;
        if (keyboard != null) {
            keyboard.onDestroy();
            keyboard = null;
        }
    }

    public RandomKeyboardViewGroup(Context context) {
        this(context, null);
    }

    public RandomKeyboardViewGroup(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RandomKeyboardViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        if (context instanceof LifecycleOwner) {
            ((LifecycleOwner) context).getLifecycle().addObserver(this);
        }
        inflate = View.inflate(context, R.layout.random_keyboard_viewgroup, null);
        slContent = ViewUtils.findViewById(inflate, R.id.sl_content);
        keyboard = ViewUtils.findViewById(inflate, R.id.random_keyboard);
        addView(inflate);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RandomKeyboardViewGroup);
        boolean aBoolean = typedArray.getBoolean(R.styleable.RandomKeyboardViewGroup_isRandom, false);
        int anInt = typedArray.getInt(R.styleable.RandomKeyboardViewGroup_keyboardType, TYPE_LETTER);
        int resourceId = typedArray.getResourceId(R.styleable.RandomKeyboard_android_keyboardLayout, 0);
        typedArray.recycle();
        setFocusable(true);
        setFocusableInTouchMode(true);
        try {
            context.getResources().getXml(resourceId);
            keyboard.setCustomKeyboard(resourceId);
        } catch (Resources.NotFoundException e) {
            setKeyboard(anInt);
        }
        isRandom(aBoolean);
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        View editFocus = findEditFocus();
        if (keyboard != null) {
            if (editFocus != null) {
                keyboard.bindEditText((EditText) editFocus);
                keyboard.show();
            } else {
                keyboard.hide();
            }
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean touchEvent = super.dispatchTouchEvent(ev);
        View editFocus = findEditFocus();

        if (editFocus != null && editFocus.hasFocus()) {
            keyboard.bindEditText((EditText) editFocus);
            keyboard.show();
        } else {
            keyboard.hide();
        }

        if (ViewUtils.isOutsideTheView(editFocus, ev) && ViewUtils.isOutsideTheView(keyboard, ev)) {
            editFocus.clearFocus();
        }
        return touchEvent;
    }


    public View findEditFocus() {
        View currentFocus = slContent != null ? slContent.findFocus() : null;
        return currentFocus instanceof EditText ? currentFocus : null;
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (child.getId() == inflate.getId()) {
            super.addView(child, index, params);
        } else {
            if (slContent != null) {
                slContent.addView(child, index, params);
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ViewGroup.LayoutParams layoutParams = getLayoutParams();
        layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
        layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (inflate != null) {
            ViewGroup.LayoutParams layoutParams1 = inflate.getLayoutParams();
            layoutParams1.width = getMeasuredWidth();
            layoutParams1.height = getMeasuredHeight();
            inflate.setLayoutParams(layoutParams1);
        }
        if (slContent != null && keyboard != null) {
            slContent.setMinimumWidth(getMeasuredHeight() - keyboard.getMeasuredHeight());
        }
    }

    /**
     * 打开关闭软键盘
     */
    public void KeyboardToggle(boolean isShow) {
        if (keyboard != null) {
            if (isShow) {
                keyboard.show();
            } else {
                keyboard.hide();
            }
        }
    }

    /**
     * 打开关闭软键盘
     */
    public void KeyboardToggle(EditText editText) {
        if (keyboard != null && editText != null) {
            keyboard.bindEditText(editText);
            if (editText.hasFocus()) {
                keyboard.show();
            } else {
                keyboard.hide();
            }
        }
    }

    /**
     * @see Constant.KEYBOARD_TYPE
     * @see RandomKeyboard#setKeyboard(int)
     */
    public void setKeyboard(@KeyboardType int type) {
        if (keyboard != null) {
            keyboard.setKeyboard(type);
        }
    }

    /**
     * 随机打乱key-value
     *
     * @see RandomKeyboard#isRandom(boolean)
     */
    public void isRandom(boolean isRandom) {
        if (keyboard != null) {
            keyboard.isRandom(isRandom);
        }
    }

    public void setCustomKeyboard(@XmlRes int xml) {
        if (keyboard != null) {
            keyboard.setCustomKeyboard(xml);
        }
    }

    public void setOnCustomKeyDealListener(OnCustomKeyDealListener customKeyDealListener) {
        if (keyboard != null) {
            keyboard.setOnCustomKeyDealListener(customKeyDealListener);
        }
    }

    public boolean isRandom() {
        return keyboard != null && keyboard.isRandom();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (null != keyboard && keyboard.getVisibility() == VISIBLE) {
                keyboard.hide();
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    public void setOnKeyboardToggleListener(OnKeyboardToggleListener toggleListener) {
        if (keyboard != null) {
            keyboard.setOnKeyboardToggleListener(toggleListener);
        }
    }
}

其他资源

OnCustomKeyDealListener

public interface OnCustomKeyDealListener {
    boolean onKey(EditText editText, int primaryCode, int[] keyCodes);
}

OnKeyboardToggleListener

public interface OnKeyboardToggleListener {
    void onToggle(boolean isOpen);
}

KeyModel.class

data class KeyModel(var codes: IntArray, var lable: CharSequence)

KEYBOARD_TYPE

public interface Constant {
    interface KEYBOARD_TYPE {
        int TYPE_LETTER = 1;
        int TYPE_NUMBER = 2;
        int TYPE_SYMBOL = 3;
        int TYPE_LETTER_AND_NUM = 4;
        int TYPE_NUM_AND_SYMBOL = 5;
    }
}

@KeyboardType

@IntDef(
    value = [KEYBOARD_TYPE.TYPE_LETTER,
        KEYBOARD_TYPE.TYPE_NUMBER,
        KEYBOARD_TYPE.TYPE_SYMBOL,
        KEYBOARD_TYPE.TYPE_LETTER_AND_NUM,
        KEYBOARD_TYPE.TYPE_NUM_AND_SYMBOL]
)
@Retention(AnnotationRetention.SOURCE)
annotation class KeyboardType

random_keyboard_viewgroup.xml




    

    

styles.xml