自定义系统键盘

   注意: 写该文章主要帮助自己记忆,贴出来希望可以给有同样问题的人解惑,不喜勿喷,可以提意见哦。

一,键盘更改内容

1、更改系统键盘背景色
2、更改系统键盘的大小
3、更改系统键盘的位置
4、更改系统键盘提示字符的大小
5、从手指点击键盘到键盘发出声音的流程。
6、遇到的坑

二、详细实现。

1、更改系统键盘背景图片.
(1)找到packages/inputmethods/PinyinIME/res/layout/skb_container.xml,skb_container.xml文件,将背景图去掉,增加左右padding值。


1510716521(1).png

(2)在frameworks\base\core\java\android\inputmethodservice路径下,找到InputMethodService.java,首先在initView方法中为根布局设置理想中的背景图

1510714059(1).png

这样设置了之后,不会得到自己想要的效果,背景图仍然没有切换,最重要的是接下来的一步,在InputMethodService中找到方法updateFullscreenMode(),注释掉图中的两行,这下就达到了想要的效果。


1510713991(1).png

2、更改系统键盘编辑框的风格
将编辑框的文字颜色改为白色,更换编辑框的背景颜色,离各边距2dp,在InputMethodService.java中找到setExtraView(View view)方法,增加红框中的内容。


1510714799(1).png

3、更改系统键盘的位置
在InputMethodService中找到方法showWindowInner(),将键盘的长设为原来的0.56,宽设为原来的一半,改变键盘的位置为右下角,增加下图红框中的内容,虽然键盘大小已经更改,但是内部只显示了一半的键盘字数。


1510715166(1).png

1510715137(1).png

4、显示完整的键盘字数
在packages\inputmethods\PinyinIME\src\com\android\inputmethod\pinyin路径下找到SkbContainer.java
(1)找到onMeasure方法,更改测量的宽。


1510716009(1).png

(2)找到方法updateSkbLayout,在红框的地方更改布局的宽高。


1510715797(1).png

经过3,4系统键盘的风格已经更改完成。

4、更改系统键盘提示字符的大小
将提示字符大小改成原来的两倍


1510717395(1).png

5、从手指点击键盘到键盘发出声音的流程。

了解Android系统事件分发机制,应该不难理解,当手指点击屏幕的时候会经过一番处理最后会调用OnTouchEvent方法,键盘也不例外,当点击键盘某个按键的时候,会调用packages/inputmethods/PinyinIME/com/android/inputmethod/pinyin/SkbContainer.java类中的onTouchEvent(...)方法,在该方法中会调用关键方法 mSkv.onKeyPress(...),其中mSkv是packages/inputmethods/PinyinIME/com/android/inputmethod/pinyin/SoftKeyboardView.java类的一个对象。onTouchEvent(...)方法所对应的代码如下所示:

 @Override
public boolean onTouchEvent(MotionEvent event) {
    super.onTouchEvent(event);
    ...
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
        ...
       //收到按下事件,经过一番处理会调用mSkv.onKeyPress(...)方法。
        if (null != mSkv) {
            mSoftKeyDown = mSkv.onKeyPress(x - mSkvPosInContainer[0], y
                    - mSkvPosInContainer[1], mLongPressTimer, false);
        }
        break;

    case MotionEvent.ACTION_MOVE:
        if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
            break;
        }
        if (mDiscardEvent) {
            resetKeyPress(0);
            break;
        }

        if (mPopupSkbShow && mPopupSkbNoResponse) {
            break;
        }

        SoftKeyboardView skv = inKeyboardView(x, y, mSkvPosInContainer);
        if (null != skv) {
            if (skv != mSkv) {
                mSkv = skv;
               //收到移动事件,经过一番处理会调用mSkv.onKeyPress(...)方法。
                mSoftKeyDown = mSkv.onKeyPress(x - mSkvPosInContainer[0], y
                        - mSkvPosInContainer[1], mLongPressTimer, true);
            } else if (null != skv) {
                if (null != mSkv) {
                    mSoftKeyDown = mSkv.onKeyMove(
                            x - mSkvPosInContainer[0], y
                                    - mSkvPosInContainer[1]);
                    if (null == mSoftKeyDown) {
                        mDiscardEvent = true;
                    }
                }
            }
        }
        break;
       ...
     }

追踪到packages/inputmethods/PinyinIME/com/android/inputmethod/pinyin/SoftKeyboardView.java类中的onKeyPress(...)方法,该方法主要是根据用户触摸的坐标计算出当前点击的是键盘的哪个按键,判断是否需要发出按键声音。

 public SoftKey onKeyPress(int x, int y,
        SkbContainer.LongPressTimer longPressTimer, boolean movePress) {
   //mSoftKeyDown 用于记录当前按下的按键Softkey
    mKeyPressed = false;
    boolean moveWithinPreviousKey = false;//判断是否在上一个SoftKey上移动
     
    if (movePress) {
        //获取当前点击的SoftKey
        SoftKey newKey = mSoftKeyboard.mapToKey(x, y);
         //如果直接的SoftKey和新的SoftKey相同则直接设moveWithinPreviousKey =true,并返回
        if (newKey == mSoftKeyDown) moveWithinPreviousKey = true;
        mSoftKeyDown = newKey;
    } else {
        mSoftKeyDown = mSoftKeyboard.mapToKey(x, y);
    }
    //如果用户在键盘上移动,按键声音只响一下,之后的不会再想,直接返回。
    if (moveWithinPreviousKey || null == mSoftKeyDown) return mSoftKeyDown;
    mKeyPressed = true;

    if (!movePress) {
        //播放按键声音的接口
        tryPlayKeyDown();
       //表示键盘可以震动的接口
        tryVibrate();
    }

    mLongPressTimer = longPressTimer;
    ...
  }

tryPlayKeyDown()方法位于packages/inputmethods/PinyinIME/com/android/inputmethod/pinyin/SoftKeyboardView.java类中,该方法调用了声音管理器SoundManager中的playKeyDown方法。

  private void tryPlayKeyDown() {
    //判断系统键盘音是否已经开启,只有开启了才有声音
    if (Settings.getKeySound()) {
        //调用播放接口
        mSoundManager.playKeyDown();
   }
}

SoundManager.java位于目录packages/inputmethods/PinyinIME/com/android/inputmethod/pinyin/下,SoundManager.java所对应的playKeyDown()方法如下:

  public void playKeyDown() {
    //该方法主要初始化AudioManager,获取mSilentMode的状态。
    updateRingerMode();
    //当没有静音的情况下可以播放声音
    if (!mSilentMode) {
       //在这里可以指定按键的声音
        int sound = AudioManager.FX_KEY_CLICK;
        //回调AudioManager的playSoundEffect方法。
        mAudioManager.playSoundEffect(sound, FX_VOLUME);
    }
}

AudioManager.java位于\frameworks\base\media\java\android\media目录下,该类中几个比较中要的常量用于标识不同的声音,如下所示:
* {@link #FX_KEY_CLICK}, 系统按键音
* {@link #FX_FOCUS_NAVIGATION_UP},
* {@link #FX_FOCUS_NAVIGATION_DOWN},
* {@link #FX_FOCUS_NAVIGATION_LEFT},
* {@link #FX_FOCUS_NAVIGATION_RIGHT},
* {@link #FX_KEYPRESS_STANDARD}, 标准键盘音
* {@link #FX_KEYPRESS_SPACEBAR}, 键盘点击空格音
* {@link #FX_KEYPRESS_DELETE},键盘点击删除音
* {@link #FX_KEYPRESS_RETURN},键盘点击返回音
playSoundEffect(...)方法主要调用的AudioService的playSoundEffectVolume(...)方法。effectType主要标识哪一种声音,如果不存在以上的声音,直接返回,不在播放声音。

  public void  playSoundEffect(int effectType, float volume) {
    if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
        return;
    }

    IAudioService service = getService();
    try {
        service.playSoundEffectVolume(effectType, volume);
    } catch (RemoteException e) {
        Log.e(TAG, "Dead object in playSoundEffect"+e);
    }
}

IAudioService是一个接口,其主要实现类是AudioService.java,其中playSoundEffectVolume(...)方法进行了正在的音频调用与播放,如下所示:

   private void playSoundEffect(int effectType, int volume) {
        synchronized (mSoundEffectsLock) {
            if (mSoundPool == null) {
                return;
            }
            float volFloat;
            // use default if volume is not specified by caller
            if (volume < 0) {
                volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20);
            } else {
                volFloat = (float) volume / 1000.0f;
            }

            if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
                mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], volFloat, volFloat, 0, 0, 1.0f);
            } else {
               到这里大家应用就很熟悉了
                MediaPlayer mediaPlayer = new MediaPlayer();
                try {
                    //音频格式的路径
                    String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effectType][0]];
                    mediaPlayer.setDataSource(filePath);
                   mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
                    mediaPlayer.prepare();
                    //设置播放音频的音量
                    mediaPlayer.setVolume(volFloat, volFloat);
                    mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
                        public void onCompletion(MediaPlayer mp) {
                           //播放完成,情况缓存
                            cleanupPlayer(mp);
                        }
                    });
                    mediaPlayer.setOnErrorListener(new OnErrorListener() {
                        public boolean onError(MediaPlayer mp, int what, int extra) {
                            cleanupPlayer(mp);
                            return true;
                        }
                    });
                   //开始播放
                    mediaPlayer.start();
                } catch (IOException ex) {
                    Log.w(TAG, "MediaPlayer IOException: "+ex);
                } catch (IllegalArgumentException ex) {
                    Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);
                } catch (IllegalStateException ex) {
                    Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
                }
            }
        }
    }

6、不停的点击按键值,快速按完成,出现键盘弹出,界面混乱的现象,正常情况应该键盘隐藏。
(1)原因:当收到完成的指令时,键盘应该隐藏,由于键盘隐藏的过程中有一个200ms的动画,如果在200ms之内没有完成隐藏指令,又收到了按键值,再隐藏的过程中就会把键盘再一次显示,所以出现了难以预料的问题。
(2)解决方式:
设置一个标签,用于判断键盘是否正在执行隐藏操作。在frameworks\base\core\java\android\inputmethodservice\InputMethodService.java类hideWindow()方法中,实现如下代码。


image.png

由于packages\inputmethods\PinyinIME类继承InputMethodService,所以可以直接调用InputMethodService的公共方法,在PinyinIME.java类的onKeyDown和onKeyDownUp方法中判断,当aimationStarting()等于true的时候,不要往下执行,直接返回。解决如下:


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,107评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,654评论 18 139
  • 【小太阳《零极限》 D2】 跟你身体对话,跟它说:“我爱你现在的样子。谢谢你一直与我同在。如果我曾经对你有任何怠...
    小太阳小姐阅读 88评论 0 0
  • 何其冷冰冰 也不见月色 细雨霏霏潜在幽幽夜里 朦胧里那一丝烛光摇曳 有家的人怎么还不回家 辗转...
    瞒青兮阅读 169评论 4 7
  • 尘封是一念铭记,只是释放蕴藏的惊喜需要尘缘的温融还以本色。尘封是愿力的蛰伏,时机未到无法彰显洪荒的无畏。尘封的人和...
    鼎森书院胡老师阅读 236评论 0 1