注意: 寫該文章主要幫助自己記憶,貼出來希望可以給有同樣問題的人解惑,不喜勿噴,可以提意見哦。
一,鍵盤更改內(nèi)容
1、更改系統(tǒng)鍵盤背景色
2、更改系統(tǒng)鍵盤的大小
3、更改系統(tǒng)鍵盤的位置
4、更改系統(tǒng)鍵盤提示字符的大小
5、從手指點擊鍵盤到鍵盤發(fā)出聲音的流程。
6、遇到的坑
二、詳細(xì)實現(xiàn)。
1、更改系統(tǒng)鍵盤背景圖片.
(1)找到packages/inputmethods/PinyinIME/res/layout/skb_container.xml,skb_container.xml文件,將背景圖去掉,增加左右padding值。

(2)在frameworks\base\core\java\android\inputmethodservice路徑下,找到InputMethodService.java,首先在initView方法中為根布局設(shè)置理想中的背景圖

這樣設(shè)置了之后,不會得到自己想要的效果,背景圖仍然沒有切換,最重要的是接下來的一步,在InputMethodService中找到方法updateFullscreenMode(),注釋掉圖中的兩行,這下就達(dá)到了想要的效果。

2、更改系統(tǒng)鍵盤編輯框的風(fēng)格
將編輯框的文字顏色改為白色,更換編輯框的背景顏色,離各邊距2dp,在InputMethodService.java中找到setExtraView(View view)方法,增加紅框中的內(nèi)容。

3、更改系統(tǒng)鍵盤的位置
在InputMethodService中找到方法showWindowInner(),將鍵盤的長設(shè)為原來的0.56,寬設(shè)為原來的一半,改變鍵盤的位置為右下角,增加下圖紅框中的內(nèi)容,雖然鍵盤大小已經(jīng)更改,但是內(nèi)部只顯示了一半的鍵盤字?jǐn)?shù)。


4、顯示完整的鍵盤字?jǐn)?shù)
在packages\inputmethods\PinyinIME\src\com\android\inputmethod\pinyin路徑下找到SkbContainer.java
(1)找到onMeasure方法,更改測量的寬。

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

經(jīng)過3,4系統(tǒng)鍵盤的風(fēng)格已經(jīng)更改完成。
4、更改系統(tǒng)鍵盤提示字符的大小
將提示字符大小改成原來的兩倍

5、從手指點擊鍵盤到鍵盤發(fā)出聲音的流程。
了解Android系統(tǒng)事件分發(fā)機(jī)制,應(yīng)該不難理解,當(dāng)手指點擊屏幕的時候會經(jīng)過一番處理最后會調(diào)用OnTouchEvent方法,鍵盤也不例外,當(dāng)點擊鍵盤某個按鍵的時候,會調(diào)用packages/inputmethods/PinyinIME/com/android/inputmethod/pinyin/SkbContainer.java類中的onTouchEvent(...)方法,在該方法中會調(diào)用關(guān)鍵方法 mSkv.onKeyPress(...),其中mSkv是packages/inputmethods/PinyinIME/com/android/inputmethod/pinyin/SoftKeyboardView.java類的一個對象。onTouchEvent(...)方法所對應(yīng)的代碼如下所示:
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
...
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
...
//收到按下事件,經(jīng)過一番處理會調(diào)用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;
//收到移動事件,經(jīng)過一番處理會調(diào)用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(...)方法,該方法主要是根據(jù)用戶觸摸的坐標(biāo)計算出當(dāng)前點擊的是鍵盤的哪個按鍵,判斷是否需要發(fā)出按鍵聲音。
public SoftKey onKeyPress(int x, int y,
SkbContainer.LongPressTimer longPressTimer, boolean movePress) {
//mSoftKeyDown 用于記錄當(dāng)前按下的按鍵Softkey
mKeyPressed = false;
boolean moveWithinPreviousKey = false;//判斷是否在上一個SoftKey上移動
if (movePress) {
//獲取當(dāng)前點擊的SoftKey
SoftKey newKey = mSoftKeyboard.mapToKey(x, y);
//如果直接的SoftKey和新的SoftKey相同則直接設(shè)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類中,該方法調(diào)用了聲音管理器SoundManager中的playKeyDown方法。
private void tryPlayKeyDown() {
//判斷系統(tǒng)鍵盤音是否已經(jīng)開啟,只有開啟了才有聲音
if (Settings.getKeySound()) {
//調(diào)用播放接口
mSoundManager.playKeyDown();
}
}
SoundManager.java位于目錄packages/inputmethods/PinyinIME/com/android/inputmethod/pinyin/下,SoundManager.java所對應(yīng)的playKeyDown()方法如下:
public void playKeyDown() {
//該方法主要初始化AudioManager,獲取mSilentMode的狀態(tài)。
updateRingerMode();
//當(dāng)沒有靜音的情況下可以播放聲音
if (!mSilentMode) {
//在這里可以指定按鍵的聲音
int sound = AudioManager.FX_KEY_CLICK;
//回調(diào)AudioManager的playSoundEffect方法。
mAudioManager.playSoundEffect(sound, FX_VOLUME);
}
}
AudioManager.java位于\frameworks\base\media\java\android\media目錄下,該類中幾個比較中要的常量用于標(biāo)識不同的聲音,如下所示:
* {@link #FX_KEY_CLICK}, 系統(tǒng)按鍵音
* {@link #FX_FOCUS_NAVIGATION_UP},
* {@link #FX_FOCUS_NAVIGATION_DOWN},
* {@link #FX_FOCUS_NAVIGATION_LEFT},
* {@link #FX_FOCUS_NAVIGATION_RIGHT},
* {@link #FX_KEYPRESS_STANDARD}, 標(biāo)準(zhǔn)鍵盤音
* {@link #FX_KEYPRESS_SPACEBAR}, 鍵盤點擊空格音
* {@link #FX_KEYPRESS_DELETE},鍵盤點擊刪除音
* {@link #FX_KEYPRESS_RETURN},鍵盤點擊返回音
playSoundEffect(...)方法主要調(diào)用的AudioService的playSoundEffectVolume(...)方法。effectType主要標(biāo)識哪一種聲音,如果不存在以上的聲音,直接返回,不在播放聲音。
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是一個接口,其主要實現(xiàn)類是AudioService.java,其中playSoundEffectVolume(...)方法進(jìn)行了正在的音頻調(diào)用與播放,如下所示:
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 {
到這里大家應(yīng)用就很熟悉了
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();
//設(shè)置播放音頻的音量
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、不停的點擊按鍵值,快速按完成,出現(xiàn)鍵盤彈出,界面混亂的現(xiàn)象,正常情況應(yīng)該鍵盤隱藏。
(1)原因:當(dāng)收到完成的指令時,鍵盤應(yīng)該隱藏,由于鍵盤隱藏的過程中有一個200ms的動畫,如果在200ms之內(nèi)沒有完成隱藏指令,又收到了按鍵值,再隱藏的過程中就會把鍵盤再一次顯示,所以出現(xiàn)了難以預(yù)料的問題。
(2)解決方式:
設(shè)置一個標(biāo)簽,用于判斷鍵盤是否正在執(zhí)行隱藏操作。在frameworks\base\core\java\android\inputmethodservice\InputMethodService.java類hideWindow()方法中,實現(xiàn)如下代碼。

由于packages\inputmethods\PinyinIME類繼承InputMethodService,所以可以直接調(diào)用InputMethodService的公共方法,在PinyinIME.java類的onKeyDown和onKeyDownUp方法中判斷,當(dāng)aimationStarting()等于true的時候,不要往下執(zhí)行,直接返回。解決如下:
