android studio 運行LatinIME工程
網上找了篇 https://www.jianshu.com/p/5278addbf99c
從android源碼裏copy下這個app需要的部分代碼
路徑:源碼目錄/frameworks/opt/inputmethodcommon/java/com/android/inputmethodcommon
copy下上邊opt目錄下的inputmethodcommon文件夾,複製到LatinIME目錄下即可LatinIME目錄下新建build.gradle文件,如下
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.1"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName = "1.0"
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
}
}
sourceSets {
main {
manifest.srcFile 'java/AndroidManifest.xml'
java.srcDirs = ['java/src', 'java-overridable/src', 'common/src', 'inputmethodcommon/java']
res.srcDirs = ['java/res']
}
}
externalNativeBuild {
cmake {
path 'native/CMakeLists.txt'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
htmlReport false
abortOnError false
disable 'MissingTranslation'
disable 'ExtraTranslation'
}
}
dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation "com.google.code.findbugs:jsr305:3.0.1"
}
gradle的版本可以改成自己本地有的,就不用下載了,如果提示gradle版本不匹配啥的,可以自己copy下對應的gradle文件夾到工程目錄下,如下圖紅框
- 在源碼的native目錄下新建 CMakeLists.txt文件,加入cmake的配置
cmake_minimum_required(VERSION 3.4.1)
# 二進制碼剝除
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s")
add_library(jni_latinime SHARED
jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
jni/com_android_inputmethod_latin_BinaryDictionary.cpp
jni/com_android_inputmethod_latin_BinaryDictionaryUtils.cpp
jni/com_android_inputmethod_latin_DicTraverseSession.cpp
jni/jni_common.cpp
jni/src/dictionary/header/header_policy.cpp
jni/src/dictionary/header/header_read_write_utils.cpp
jni/src/dictionary/property/ngram_context.cpp
jni/src/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
jni/src/dictionary/structure/backward/v402/ver4_dict_buffers.cpp
jni/src/dictionary/structure/backward/v402/ver4_dict_constants.cpp
jni/src/dictionary/structure/backward/v402/ver4_patricia_trie_node_reader.cpp
jni/src/dictionary/structure/backward/v402/ver4_patricia_trie_node_writer.cpp
jni/src/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
jni/src/dictionary/structure/backward/v402/ver4_patricia_trie_reading_utils.cpp
jni/src/dictionary/structure/backward/v402/ver4_patricia_trie_writing_helper.cpp
jni/src/dictionary/structure/backward/v402/ver4_pt_node_array_reader.cpp
jni/src/dictionary/structure/backward/v402/bigram/ver4_bigram_list_policy.cpp
jni/src/dictionary/structure/backward/v402/content/bigram_dict_content.cpp
jni/src/dictionary/structure/backward/v402/content/probability_dict_content.cpp
jni/src/dictionary/structure/backward/v402/content/shortcut_dict_content.cpp
jni/src/dictionary/structure/backward/v402/content/sparse_table_dict_content.cpp
jni/src/dictionary/structure/backward/v402/content/terminal_position_lookup_table.cpp
jni/src/dictionary/structure/pt_common/dynamic_pt_gc_event_listeners.cpp
jni/src/dictionary/structure/pt_common/dynamic_pt_reading_helper.cpp
jni/src/dictionary/structure/pt_common/dynamic_pt_reading_utils.cpp
jni/src/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
jni/src/dictionary/structure/pt_common/dynamic_pt_writing_utils.cpp
jni/src/dictionary/structure/pt_common/patricia_trie_reading_utils.cpp
jni/src/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.cpp
jni/src/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.cpp
jni/src/dictionary/structure/v2/patricia_trie_policy.cpp
jni/src/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp
jni/src/dictionary/structure/v2/ver2_pt_node_array_reader.cpp
jni/src/dictionary/structure/v4/ver4_dict_buffers.cpp
jni/src/dictionary/structure/v4/ver4_dict_constants.cpp
jni/src/dictionary/structure/v4/ver4_patricia_trie_node_reader.cpp
jni/src/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
jni/src/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
jni/src/dictionary/structure/v4/ver4_patricia_trie_reading_utils.cpp
jni/src/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp
jni/src/dictionary/structure/v4/ver4_pt_node_array_reader.cpp
jni/src/dictionary/structure/v4/content/dynamic_language_model_probability_utils.cpp
jni/src/dictionary/structure/v4/content/language_model_dict_content.cpp
jni/src/dictionary/structure/v4/content/language_model_dict_content_global_counters.cpp
jni/src/dictionary/structure/v4/content/shortcut_dict_content.cpp
jni/src/dictionary/structure/v4/content/sparse_table_dict_content.cpp
jni/src/dictionary/structure/v4/content/terminal_position_lookup_table.cpp
jni/src/dictionary/utils/buffer_with_extendable_buffer.cpp
jni/src/dictionary/utils/byte_array_utils.cpp
jni/src/dictionary/utils/dict_file_writing_utils.cpp
jni/src/dictionary/utils/file_utils.cpp
jni/src/dictionary/utils/forgetting_curve_utils.cpp
jni/src/dictionary/utils/format_utils.cpp
jni/src/dictionary/utils/mmapped_buffer.cpp
jni/src/dictionary/utils/multi_bigram_map.cpp
jni/src/dictionary/utils/probability_utils.cpp
jni/src/dictionary/utils/sparse_table.cpp
jni/src/dictionary/utils/trie_map.cpp
jni/src/suggest/core/suggest.cpp
jni/src/suggest/core/dicnode/dic_node.cpp
jni/src/suggest/core/dicnode/dic_node_utils.cpp
jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
jni/src/suggest/core/dictionary/dictionary.cpp
jni/src/suggest/core/dictionary/dictionary_utils.cpp
jni/src/suggest/core/dictionary/digraph_utils.cpp
jni/src/suggest/core/dictionary/error_type_utils.cpp
jni/src/suggest/core/layout/additional_proximity_chars.cpp
jni/src/suggest/core/layout/proximity_info.cpp
jni/src/suggest/core/layout/proximity_info_params.cpp
jni/src/suggest/core/layout/proximity_info_state.cpp
jni/src/suggest/core/layout/proximity_info_state_utils.cpp
jni/src/suggest/core/policy/weighting.cpp
jni/src/suggest/core/result/suggestion_results.cpp
jni/src/suggest/core/result/suggestions_output_utils.cpp
jni/src/suggest/core/session/dic_traverse_session.cpp
jni/src/suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp
jni/src/suggest/policyimpl/typing/scoring_params.cpp
jni/src/suggest/policyimpl/typing/typing_scoring.cpp
jni/src/suggest/policyimpl/typing/typing_suggest_policy.cpp
jni/src/suggest/policyimpl/typing/typing_traversal.cpp
jni/src/suggest/policyimpl/typing/typing_weighting.cpp
jni/src/utils/autocorrection_threshold_utils.cpp
jni/src/utils/char_utils.cpp
jni/src/utils/jni_data_utils.cpp
jni/src/utils/log_utils.cpp
jni/src/utils/time_keeper.cpp)
include_directories(jni/src/)
target_link_libraries(jni_latinime
android
log
z)
ok,這時候工程應該可以正常運行了。對了,就像帖子裏說的,你的studio需要下載配置好ndk
補充說明
上邊改完debug模式沒啥問題了,release build的時候會出問題
清單文件裏會提示下邊的有問題,因爲gradle配置裏已經設置了最小版本,目標版本了
<!-- <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="23" />-->
簽名完apk無法安裝,網上搜到的解決辦法是在application標籤下添加
android:extractNativeLibs="true"
這些可能都是gradle編譯的時候會出現的問題,如果你是放在系統源碼裏一起編譯到image裏的,估計不用改這些了吧。因爲我也沒有image,只有別人從 image裏提取出來的這個LatinIME工程。。
我這裏改的是8.0的輸入法.
ui修改
目前只考慮英文的,我這裏測試的是平板,所以只修改了xml-sw600dp 這個文件夾下相關的資源文件,如果是給手機用的,那修改默認的xml下的文件即可
- 默認的英文字母界面 rows_kwerty.xml
總共有4個Row也就是4行,最後一行通過include引用了另外一個通用的文件 row_kwerty4.xml
比如想刪除一個key,添加一個key都可以,不過需要注意按鍵的總寬度不能大於100,否則有的按鈕就不顯示了
我這裏是把刪除按鍵和action按鍵放到最後兩行了
默認界面左下角點擊那個123的按鍵,會跳到 數字符號界面
- 數字符號界面 rows_symbols.xml
在這個界面點擊第三行第一個按鈕,切換到更多符號界面
- 對應的就是rows_symbols_shift.xml
如下最後兩行的代碼,
<Key
latin:keySpec="¿" />
<Key //替換原來和左側一樣的鍵爲刪除鍵
latin:keyStyle="deleteKeyStyle"
latin:keyWidth="fillRight" />
</Row>
<Row
latin:keyWidth="9.0%p"
latin:backgroundType="functional"
>
<Key
latin:keyStyle="toAlphaKeyStyle"
latin:keyWidth="10%p" />
<include
latin:keyboardLayout="@xml/row_symbols_shift4" />
<!-- <include-->
<!-- latin:keyboardLayout="@xml/key_emoji" />-->
<Key //新加的
latin:keyStyle="enterKeyStyle"
latin:keyWidth="fillRight" />
</Row>
佈局修改
這裏只測試英文的,rows_qwerty.xml
table上測試,用的是default屬性
修改keyWidth爲10%p ,這裏的百分比是除去key之間的間隔的,10個key平分,所以就是10%
<Row>
<switch>
<!-- Split keyboard layout for the first row -->
<case
latin:isSplitLayout="true"
>
//...
<default>
<include
latin:keyboardLayout="@xml/rowkeys_qwerty1"
latin:keyWidth="10%p" />
<!-- <Key-->
<!-- latin:keyStyle="deleteKeyStyle"-->
<!-- latin:keyWidth="fillRight" />-->
</default>
</switch>
</Row>
第二行,這個和我想的效果差距太大,我這百分比是加上間隔算的,現在發現不對
<include
latin:keyboardLayout="@xml/rowkeys_qwerty2"
latin:keyXPos="6.275%p"
latin:keyWidth="8.57%p" />
測試結果發現這裏的百分比是除去間隔的,所以上邊第二行修改如下
9個字母佔90%,每個10%,剩下兩邊各5%,也就是默認起始x偏移5%,效果如下
<include
latin:keyboardLayout="@xml/rowkeys_qwerty2"
latin:keyXPos="5%p"
latin:keyWidth="10%p" />
rows_qwerty.xml 修改字母界面沒啥問題,
修改數字界面,應該是rows_symbols.xml結果發現改完沒變化,那就是改錯地方了。
我測試的是橫屏板子輸入法,所以最先找的是 xml-sw600dp-land 沒找到,我就去xml下去找了,
現在想起來還有個xml-sw600dp文件夾,land下沒有應該先找這個的。這次修改完就生效了,如下
數字界面切換符號界面
rows_symboles_shift.xml
簡單修改完成,目前只是把key的位置修改完了。
鍵盤高度修改
我們的需求是鍵盤高度增大了,所以根據下邊的邏輯,我就修改了下最小高度,最後使用的就是這個值了。
在config.xml 下看到這個名字,需要注意下,你要適配的大小,找對應的,這個文件有很多個,找對應的修改
<dimen name="config_default_keyboard_height">365.4dp</dimen>
<fraction name="config_min_keyboard_height">55%p</fraction>
搜下哪裏用到了上邊的配置,ResourceUtils.java
public static int getDefaultKeyboardHeight(final Resources res) {
final DisplayMetrics dm = res.getDisplayMetrics();
final String keyboardHeightInDp = getDeviceOverrideValue(
res, R.array.keyboard_heights, null /* defaultValue */);
final float keyboardHeight;
if (TextUtils.isEmpty(keyboardHeightInDp)) {//目前數組是空的,所以會走這裏了
keyboardHeight = res.getDimension(R.dimen.config_default_keyboard_height);
} else {
keyboardHeight = Float.parseFloat(keyboardHeightInDp) * dm.density;
}
final float maxKeyboardHeight = res.getFraction(//最大高度
R.fraction.config_max_keyboard_height, dm.heightPixels, dm.heightPixels);
float minKeyboardHeight = res.getFraction(//最小高度
R.fraction.config_min_keyboard_height, dm.heightPixels, dm.heightPixels);
if (minKeyboardHeight < 0.0f) {//如果是負的,弄成正的,而且負的標誌百分比是按照寬來算的
// Specified fraction was negative, so it should be calculated against display
// width.
minKeyboardHeight = -res.getFraction(
R.fraction.config_min_keyboard_height, dm.widthPixels, dm.widthPixels);
}
// Keyboard height will not exceed maxKeyboardHeight and will not be less than
// minKeyboardHeight. 首先固定高度和最大高度取最小值,然後再和最小高度比較取最大值,所以我們修改下最小高度就行了.
return (int)Math.max(Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
}
具體這個高度的百分比弄多少,看需求了,我們的需求是按鍵key的寬高是一樣的,所以這裏需要根據屏幕的寬高計算下。
方法如下:
因爲我們要求普通按鍵的寬高是一樣的,所以鍵盤的高度需要計算出來。
首先需要定義好按鍵之間的間隔,因爲用到的都是百分比。而且p指的是鍵盤的寬高,不是屏幕的寬高,因爲水平方向一般是鋪滿屏幕的,所以寬和屏幕寬一樣,但屏幕高和鍵盤高差距很大,別理解錯了。
以下邊設置爲例
<fraction name="config_keyboard_top_padding_holo">2.0%p</fraction>
<fraction name="config_keyboard_bottom_padding_holo">2.0%p</fraction>
<fraction name="config_key_vertical_gap_holo">2.0%p</fraction>
<fraction name="config_key_horizontal_gap_holo">1%p</fraction>
水平方向的間隔總共是10%p,那麼key的寬度就是 90%p除10 ,每個key的寬度就是 9%p 也就是====== 寬度*9%
垂直方向,間隔總共是10%p【這裏的p指的是鍵盤的高度,不是屏幕的高度】, 假設 鍵盤高度是x%p,那麼
假設屏幕高度爲h,寬爲w
x%*h=10%*(x%*h)+ w*9%*4;
x=w*4/h
知道設備的寬高,那麼這個鍵盤高度百分比x也就算出來了。
實際測試發現上邊的算法有問題,因爲間隔獲取的時候直接強轉爲int了,比如結果是7.88強轉以後就是7了,所以上邊算出來有誤差
比如,寬度440,間隔2%p,我們會認爲10個間隔是20%p,也就是88,實際結果不是,單個2%p是8.8取整就是8,
那麼10個間隔實際是80,和上邊的公式就差了8,
如果沒有這要求,其實高度設置多少都行,反正key的高度最後是平分鍵盤高度的.
需要注意下,我只修改英文鍵盤,所以是按照4行算的,如果是泰文,那是5行的,所以上邊只是說明了下計算的邏輯,具體的還得看實際需求,如果不想修改配置文件,你也可以直接在上邊的getDefaultKeyboardHeight方法裏,根據寬度來算下高度
主題
我用的8.0的源碼,這個有提供4種主題,如下
修改的時候要找到對應的主題修改,別修改錯了地方,源碼裏主題都在這了
看到light和dark,那麼
themes-lxx-dark 對應的 Material Dark
themes-lxx-light 對應的 Material Light
themes-lxx 是上邊兩個通用的部分
themes-klp 是 Holo White
themes-ics 是Holo Blue(區分很簡單,這裏的顏色有藍色的)
themes-holo 是上邊兩個通用的
themes-common 是所有主題通用的部分
目前我以holo white 爲默認的做修改,其他幾種先不管
<style
name="KeyboardView.KLP"
parent="KeyboardView.Holo"
>
<item name="android:background">#F2F2F2</item>//鍵盤背景,不包括上邊提示那部分
<item name="keyBackground">@drawable/btn_keyboard_key_klp_test</item>//默認按鍵的背景,就是那些字母數字
<item name="functionalKeyBackground">@drawable/btn_keyboard_key_klp_test</item>//功能性的按鍵,xml裏的Key有聲明的
<item name="spacebarBackground">@drawable/btn_keyboard_key_klp_test</item>//空格鍵的背景
<item name="keyTextColor">@color/key_text_color_holo</item>// key上字母的顏色
<item name="keyTextInactivatedColor">@color/key_text_inactivated_color_holo</item>
<item name="functionalTextColor">@color/key_text_color_holo</item>//功能性按鍵上的文字顏色
<item name="keyHintLetterColor">@color/key_hint_letter_color_holo</item>//第一行字母右上角那數字的顏色
<item name="keyHintLabelColor">@color/key_hint_label_color_holo</item>//最後一行按鈕右下角三個點的顏色
<item name="keyShiftedLetterHintInactivatedColor">@color/key_shifted_letter_hint_inactivated_color_holo</item>
<item name="keyShiftedLetterHintActivatedColor">@color/key_shifted_letter_hint_activated_color_holo</item>
<item name="keyPreviewTextColor">@color/key_text_color_holo</item>
</style>
修改按鍵背景
按鍵的背景系統默認的有3種, 普通按鈕,功能性的按鍵,空格鍵
根據需求我們把這三種顏色修改成我們要的.然後還得增加一種深色的,如下圖。
默認最底下一行除了空格都是functional背景的,如果不需要,可以修改下latin:backgroundType爲normal即可,我們的需求那個 , -, = 都不需要顏色,那麼改成normal就行,背景就是普通背景了.
<Key
latin:backgroundType="normal"
latin:keySpec="/" />
查看attrs.xml 下定義的 latin:backgroundType="functional"
<declare-styleable name="Keyboard_Key">
<attr name="backgroundType" format="enum">
<!-- This should be aligned with
{@link com.android.inputmethod.keyboard.Key#BACKGROUND_TYPE_NORMAL} etc. -->
<enum name="empty" value="0" />
<enum name="normal" value="1" />
<enum name="functional" value="2" />
<enum name="stickyOff" value="3" />
<enum name="stickyOn" value="4" />
<enum name="action" value="5" />
<enum name="spacebar" value="6" />
</attr>
// 系統默認就定義了三種background,我們在下邊加一種新的
<declare-styleable name="KeyboardView">
<!-- Background image for the key. This image needs to be a
{@link android.graphics.drawable.StateListDrawable}, with the following possible states:
normal, pressed, checkable, checkable+pressed, checkable+checked,
checkable+checked+pressed. -->
<attr name="keyBackground" format="reference" />
<!-- Background image for the functional key. This image needs to be a
{@link android.graphics.drawable.StateListDrawable}, with the following possible
states: normal, pressed. -->
<attr name="functionalKeyBackground" format="reference" />
<!-- Background image for the spacebar. This image needs to be a
{@link android.graphics.drawable.StateListDrawable}, with the following possible
states: normal, pressed. -->
<attr name="spacebarBackground" format="reference" />
先看下背景哪裏用到了,Key.java
可以看到,就分了3種情況,
public final Drawable selectBackgroundDrawable(@Nonnull final Drawable keyBackground,
@Nonnull final Drawable functionalKeyBackground,
@Nonnull final Drawable spacebarBackground) {
final Drawable background;
if (mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL) {
background = functionalKeyBackground;
} else if (mBackgroundType == BACKGROUND_TYPE_SPACEBAR) {
background = spacebarBackground;
} else {
background = keyBackground;
}
final int[] state = KeyBackgroundState.STATES[mBackgroundType].getState(mPressed);
background.setState(state);
return background;
}
圖片是在KeyboardView.java 里加載的,所以需要去主題裏設置
public KeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs,
R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground);
mKeyBackground.getPadding(mKeyBackgroundPadding);
final Drawable functionalKeyBackground = keyboardViewAttr.getDrawable(
R.styleable.KeyboardView_functionalKeyBackground);
mFunctionalKeyBackground = (functionalKeyBackground != null) ? functionalKeyBackground
: mKeyBackground;
final Drawable spacebarBackground = keyboardViewAttr.getDrawable(
R.styleable.KeyboardView_spacebarBackground);
mSpacebarBackground = (spacebarBackground != null) ? spacebarBackground : mKeyBackground;
現在假設增加了一種attrs.xml下添加
<declare-styleable name="KeyboardView">
<attr name="functionalKeyBackground2" format="reference" /> //for dark blue action key
theme裏添加
如果四個主題你都要改的話,那就都加,或者加載默認的主題下邊,我這裏就只處理一種主題
<item name="functionalKeyBackground">@drawable/btn_keyboard_key_functional_klp</item>
<item name="functionalKeyBackground2">@drawable/btn_keyboard_key_functional_klp2</item>
KeyboardView裏獲取圖片
// newly added
final Drawable functionalKeyBackground2 = keyboardViewAttr.getDrawable(
R.styleable.KeyboardView_functionalKeyBackground2);
mFunctionalKeyBackground2 = (functionalKeyBackground2 != null) ? functionalKeyBackground2
: mKeyBackground;
那麼哪裏用這圖片了?先給Key增加一種類型
<attr name="backgroundType" format="enum">
<!-- This should be aligned with
{@link com.android.inputmethod.keyboard.Key#BACKGROUND_TYPE_NORMAL} etc. -->
<enum name="empty" value="0" />
<enum name="normal" value="1" />
<enum name="functional" value="2" />
<enum name="stickyOff" value="3" />
<enum name="stickyOn" value="4" />
<enum name="action" value="5" />
<enum name="spacebar" value="6" />
<enum name="functional2" value="7" /> //new added
</attr>
然後修改下Key的latin:backgroundType屬性
<Key
latin:backgroundType="functional2"
latin:keyStyle="enterKeyStyle"
latin:keyWidth="fillRight" />
好了,在key.java裏處理下
如下,下個方法增加一個參數,從KeyboardView裏傳過來的, 多加個if條件
//
public static final int BACKGROUND_TYPE_FUNCTIONAL2 = 7;
//
case BACKGROUND_TYPE_SPACEBAR: return "spacebar";
case BACKGROUND_TYPE_FUNCTIONAL2:return "functional2";
public final Drawable selectBackgroundDrawable(@Nonnull final Drawable keyBackground,
@Nonnull final Drawable functionalKeyBackground,
@Nonnull final Drawable spacebarBackground,@Nonnull final Drawable functionalKeyBackground2) {
final Drawable background;
if (mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL) {
background = functionalKeyBackground;
}else if(mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL2){
background = functionalKeyBackground2;
}else if (mBackgroundType == BACKGROUND_TYPE_SPACEBAR) {
background = spacebarBackground;
} else {
background = keyBackground;
}
final int[] state = KeyBackgroundState.STATES[mBackgroundType].getState(mPressed);
background.setState(state);
return background;
}
想着ok了,運行了下掛了,下邊這行掛了,數組越界,
java.lang.ArrayIndexOutOfBoundsException: length=7; index=7
final int[] state = KeyBackgroundState.STATES[mBackgroundType].getState(mPressed);
點進去一看,果然,這個忘了處理了,如下,最後位置添加一個新的
public static final KeyBackgroundState[] STATES = {
// 0: BACKGROUND_TYPE_EMPTY
new KeyBackgroundState(android.R.attr.state_empty),
// 1: BACKGROUND_TYPE_NORMAL
new KeyBackgroundState(),
// 2: BACKGROUND_TYPE_FUNCTIONAL
new KeyBackgroundState(),
// 3: BACKGROUND_TYPE_STICKY_OFF
new KeyBackgroundState(android.R.attr.state_checkable),
// 4: BACKGROUND_TYPE_STICKY_ON
new KeyBackgroundState(android.R.attr.state_checkable, android.R.attr.state_checked),
// 5: BACKGROUND_TYPE_ACTION
new KeyBackgroundState(android.R.attr.state_active),
// 6: BACKGROUND_TYPE_SPACEBAR
new KeyBackgroundState(),
// 7: BACKGROUND_TYPE_FUNCTIONAL2
new KeyBackgroundState(),
};
這次就ok了
刪除頂部的提示view
@layout/main_keyboard_frame
在這個佈局裏,我以爲把提示的view設置爲gone就完事了,結果沒效果。然後搜了下這個控件用到的地方,發現java代碼裏有控制它的可見性
<com.android.inputmethod.latin.suggestions.SuggestionStripView
android:id="@+id/suggestion_strip_view"
android:layoutDirection="ltr"
android:layout_width="match_parent"
android:layout_height="@dimen/config_suggestions_strip_height"
android:visibility="gone"
android:gravity="center_vertical"
style="?attr/suggestionStripViewStyle" />
LatinIME.java裏有
final boolean shouldShowImportantNotice =
ImportantNoticeUtils.shouldShowImportantNotice(this, currentSettingsValues);
final boolean shouldShowSuggestionCandidates =
currentSettingsValues.mInputAttributes.mShouldShowSuggestions
&& currentSettingsValues.isSuggestionsEnabledPerUserSettings();
final boolean shouldShowSuggestionsStripUnlessPassword = shouldShowImportantNotice
|| currentSettingsValues.mShowsVoiceInputKey
|| shouldShowSuggestionCandidates
|| currentSettingsValues.isApplicationSpecifiedCompletionsOn();
final boolean shouldShowSuggestionsStrip = shouldShowSuggestionsStripUnlessPassword
&& !currentSettingsValues.mInputAttributes.mIsPasswordField;
mSuggestionStripView.updateVisibility(shouldShowSuggestionsStrip, isFullscreenMode());
//go on
public void updateVisibility(final boolean shouldBeVisible, final boolean isFullscreenMode) {
final int visibility = shouldBeVisible ? VISIBLE : (isFullscreenMode ? GONE : INVISIBLE);
setVisibility(visibility);
final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent();
//mVoiceKey.setVisibility(currentSettingsValues.mShowsVoiceInputKey ? VISIBLE : INVISIBLE);
mVoiceKey.setVisibility(View.GONE);
}
主要就是setting裏有個設置是否提示【第一個參數】,以及當前是否全屏【第二個參數】
我們不需要顯示這個佈局,那麼上邊代碼直接改成如下即可
mSuggestionStripView.updateVisibility(false,true);
如果需要修改顏色啥的,那去主題裏修改下,應該是以suggestion開頭的一些屬性
<style
name="SuggestionWord.KLP"
parent="SuggestionWord"
>
<item name="android:background">@drawable/btn_suggestion_klp</item>
<item name="android:textColor">@color/highlight_color_klp</item>
</style>
長按有彈框的,處理下彈框顏色
<style
name="MoreKeysKeyboardView.KLP"
parent="KeyboardView.KLP"
>
<item name="android:background">#F2F2F2</item>//彈框整體背景
<item name="keyBackground">@drawable/btn_keyboard_key_popup_klp</item>//每個key的背景
<item name="divider">@drawable/more_keys_divider</item>//
<item name="keyTypeface">normal</item>
<item name="verticalCorrection">@dimen/config_more_keys_keyboard_vertical_correction_holo</item>
</style>
字體style修改
默認是bold,改下
<item name="keyTypeface">normal</item>
折騰到現在長這樣,現在問題就是圖標看起來有點小
圖標大小如何修改
先來回顧下之前研究的,這些Key的內容咋定義的
- 普通的字母
第一種是第一行那種長按可以選擇數字的,第二種是第二行那普通的字母
<Key
latin:keySpec="!text/keyspec_q"
latin:keyHintLabel="1"
latin:additionalMoreKeys="1"
latin:moreKeys="!text/morekeys_q" />
<Key
latin:keySpec="k" />
- 功能按鍵
這些一般都是簡單寫個KeyStyle,具體的屬性在 <include latin:keyboardLayout="@xml/key_styles_common" />
<Key
latin:keyStyle="deleteKeyStyle"
latin:keyWidth="fillRight" />
//key_styles_common 裏找到styleName一樣的
<key-style
latin:styleName="deleteKeyStyle"
latin:keySpec="!icon/delete_key|!code/key_delete"
latin:keyActionFlags="isRepeatable|noKeyPreview"
latin:backgroundType="functional" />
有的還帶有parent屬性的
<key-style
latin:styleName="shiftKeyStyle"
latin:keySpec="!icon/shift_key|!code/key_shift"
latin:backgroundType="stickyOff"
latin:parentStyle="baseForShiftKeyStyle" />
<key-style
latin:styleName="baseForShiftKeyStyle"
latin:keyActionFlags="noKeyPreview"
latin:keyLabelFlags="preserveCase"
latin:moreKeys="!noPanelAutoMoreKey!, |!code/key_capslock" />
其實可以看到key的內容是定義在keySpec裏的
目前有以下幾種
!icon/shift_key|!code/key_shift
!text/keyspec_q
去Key.java裏看下咋解析這些值的
final String keySpec = keyStyle.getString(keyAttr, R.styleable.Keyboard_Key_keySpec);
public Key(@Nullable final String keySpec, @Nonnull final TypedArray keyAttr,
@Nonnull final KeyStyle style, @Nonnull final KeyboardParams params,
@Nonnull final KeyboardRow row) {
mIconId = KeySpecParser.getIconId(keySpec);
final int code = KeySpecParser.getCode(keySpec);
final String label = KeySpecParser.getLabel(keySpec);
String outputText = KeySpecParser.getOutputText(keySpec);
看下這些方法,可以得出一些結論
1.1. keySpec可以有多個,以豎槓分開
1.2. 如果有icon的話,icon必須放在首位
1.3. 文字圖片都是根據前綴來區分的
1.4. 解析出對應的名字,比如shift_key,這個是icon,那麼會去KeyboardIconsSet這個類裏找,這裏有初始化所有的icon
1.5 定義了icon的話,label就無效了
public static final String PREFIX_ICON = "!icon/";
private static final char BACKSLASH = Constants.CODE_BACKSLASH; // 反斜槓 \
private static final char VERTICAL_BAR = Constants.CODE_VERTICAL_BAR; //豎槓 |
private static final String PREFIX_HEX = "0x";
private static boolean hasIcon(@Nonnull final String keySpec) {
return keySpec.startsWith(KeyboardIconsSet.PREFIX_ICON);
}
public static int getIconId(@Nullable final String keySpec) {
if (keySpec == null) {
// TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
return KeyboardIconsSet.ICON_UNDEFINED;
}
if (!hasIcon(keySpec)) {
return KeyboardIconsSet.ICON_UNDEFINED;
}
final int labelEnd = indexOfLabelEnd(keySpec);
final String iconName = getBeforeLabelEnd(keySpec, labelEnd)
.substring(KeyboardIconsSet.PREFIX_ICON.length());
//iconName取的就是
return KeyboardIconsSet.getIconId(iconName);
}
//code
if (text.startsWith(KeyboardCodesSet.PREFIX_CODE)) {
return KeyboardCodesSet.getCode(text.substring(KeyboardCodesSet.PREFIX_CODE.length()));
}
// 有icon的話,lable直接返回null了
public static String getLabel(@Nullable final String keySpec) {
if (keySpec == null) {
// TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
return null;
}
if (hasIcon(keySpec)) {
return null;
}
//有code的話 text就返回null了
public static String getOutputText(@Nullable final String keySpec) {
if (keySpec == null) {
// TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
return null;
}
final int labelEnd = indexOfLabelEnd(keySpec);
if (hasCode(keySpec, labelEnd)) {
return null;
}
看下已定義的icon有哪些 KeyboardIconsSet.java
看名字大概就知道是哪些按鍵了,每個key都對應一個style的,我們去查下style
public static final String PREFIX_ICON = "!icon/";
private static final String NAME_UNDEFINED = "undefined";
public static final String NAME_SHIFT_KEY = "shift_key";//
public static final String NAME_SHIFT_KEY_SHIFTED = "shift_key_shifted";//
public static final String NAME_DELETE_KEY = "delete_key";//
public static final String NAME_SETTINGS_KEY = "settings_key";
public static final String NAME_SPACE_KEY = "space_key";
public static final String NAME_SPACE_KEY_FOR_NUMBER_LAYOUT = "space_key_for_number_layout";
public static final String NAME_ENTER_KEY = "enter_key";
public static final String NAME_GO_KEY = "go_key";
public static final String NAME_SEARCH_KEY = "search_key";
public static final String NAME_SEND_KEY = "send_key";
public static final String NAME_NEXT_KEY = "next_key";
public static final String NAME_DONE_KEY = "done_key";
public static final String NAME_PREVIOUS_KEY = "previous_key";
public static final String NAME_TAB_KEY = "tab_key";
public static final String NAME_SHORTCUT_KEY = "shortcut_key";
public static final String NAME_SHORTCUT_KEY_DISABLED = "shortcut_key_disabled";
public static final String NAME_LANGUAGE_SWITCH_KEY = "language_switch_key";
public static final String NAME_ZWNJ_KEY = "zwnj_key";
public static final String NAME_ZWJ_KEY = "zwj_key";
public static final String NAME_EMOJI_ACTION_KEY = "emoji_action_key";
public static final String NAME_EMOJI_NORMAL_KEY = "emoji_normal_key";
private static final Object[] NAMES_AND_ATTR_IDS = {
NAME_UNDEFINED, ATTR_UNDEFINED,
NAME_SHIFT_KEY, R.styleable.Keyboard_iconShiftKey,
NAME_DELETE_KEY, R.styleable.Keyboard_iconDeleteKey,
NAME_SETTINGS_KEY, R.styleable.Keyboard_iconSettingsKey,
NAME_SPACE_KEY, R.styleable.Keyboard_iconSpaceKey,
NAME_ENTER_KEY, R.styleable.Keyboard_iconEnterKey,
NAME_GO_KEY, R.styleable.Keyboard_iconGoKey,
NAME_SEARCH_KEY, R.styleable.Keyboard_iconSearchKey,
NAME_SEND_KEY, R.styleable.Keyboard_iconSendKey,
NAME_NEXT_KEY, R.styleable.Keyboard_iconNextKey,
NAME_DONE_KEY, R.styleable.Keyboard_iconDoneKey,
NAME_PREVIOUS_KEY, R.styleable.Keyboard_iconPreviousKey,
NAME_TAB_KEY, R.styleable.Keyboard_iconTabKey,
NAME_SHORTCUT_KEY, R.styleable.Keyboard_iconShortcutKey,
NAME_SPACE_KEY_FOR_NUMBER_LAYOUT, R.styleable.Keyboard_iconSpaceKeyForNumberLayout,
NAME_SHIFT_KEY_SHIFTED, R.styleable.Keyboard_iconShiftKeyShifted,
NAME_SHORTCUT_KEY_DISABLED, R.styleable.Keyboard_iconShortcutKeyDisabled,
NAME_LANGUAGE_SWITCH_KEY, R.styleable.Keyboard_iconLanguageSwitchKey,
NAME_ZWNJ_KEY, R.styleable.Keyboard_iconZwnjKey,
NAME_ZWJ_KEY, R.styleable.Keyboard_iconZwjKey,
NAME_EMOJI_ACTION_KEY, R.styleable.Keyboard_iconEmojiActionKey,
NAME_EMOJI_NORMAL_KEY, R.styleable.Keyboard_iconEmojiNormalKey,
};
//加載主題裏的圖片
public void loadIcons(final TypedArray keyboardAttrs) {
final int size = ATTR_ID_TO_ICON_ID.size();
for (int index = 0; index < size; index++) {
final int attrId = ATTR_ID_TO_ICON_ID.keyAt(index);
try {
final Drawable icon = keyboardAttrs.getDrawable(attrId);
setDefaultBounds(icon);
final Integer iconId = ATTR_ID_TO_ICON_ID.get(attrId);
mIcons[iconId] = icon;
mIconResourceIds[iconId] = keyboardAttrs.getResourceId(attrId, 0);
} catch (Resources.NotFoundException e) {
Log.w(TAG, "Drawable resource for icon #"
+ keyboardAttrs.getResources().getResourceEntryName(attrId)
+ " not found");
}
}
}
現在分析下主題
有這麼一個KeyboardTheme類的,這裏就包含了4種主題了,自己用的哪種,去對應下邊找就行了
static final KeyboardTheme[] KEYBOARD_THEMES = {
new KeyboardTheme(THEME_ID_ICS, "ICS", R.style.KeyboardTheme_ICS,
// This has never been selected because we support ICS or later.
VERSION_CODES.BASE),
new KeyboardTheme(THEME_ID_KLP, "KLP", R.style.KeyboardTheme_KLP,
// Default theme for ICS, JB, and KLP.
VERSION_CODES.ICE_CREAM_SANDWICH),
new KeyboardTheme(THEME_ID_LXX_LIGHT, "LXXLight", R.style.KeyboardTheme_LXX_Light,
// Default theme for LXX.
Build.VERSION_CODES.LOLLIPOP),
new KeyboardTheme(THEME_ID_LXX_DARK, "LXXDark", R.style.KeyboardTheme_LXX_Dark,
// This has never been selected as default theme.
VERSION_CODES.BASE),
};
如下圖片定義在這裏
<style name="KeyboardIcons.Holo">
<!-- Keyboard icons -->
<item name="iconShiftKey">@drawable/sym_keyboard_shift_holo_dark</item>
<item name="iconDeleteKey">@drawable/sym_keyboard_delete_holo_dark</item>
<item name="iconSettingsKey">@drawable/sym_keyboard_settings_holo_dark</item>
<item name="iconSpaceKey">@null</item>
<item name="iconEnterKey">@drawable/sym_keyboard_return_holo_dark</item>
<item name="iconSearchKey">@drawable/sym_keyboard_search_holo_dark</item>
<item name="iconTabKey">@drawable/sym_keyboard_tab_holo_dark</item>
<item name="iconShortcutKey">@drawable/sym_keyboard_voice_holo_dark</item>
<item name="iconSpaceKeyForNumberLayout">@drawable/sym_keyboard_space_holo_dark</item>
<item name="iconShiftKeyShifted">@drawable/sym_keyboard_shift_locked_holo_dark</item>
<item name="iconShortcutKeyDisabled">@drawable/sym_keyboard_voice_off_holo_dark</item>
<item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch_dark</item>
<item name="iconZwnjKey">@drawable/sym_keyboard_zwnj_holo_dark</item>
<item name="iconZwjKey">@drawable/sym_keyboard_zwj_holo_dark</item>
<item name="iconEmojiActionKey">@drawable/sym_keyboard_smiley_holo_dark</item>
<item name="iconEmojiNormalKey">@drawable/sym_keyboard_smiley_holo_dark</item>
</style>
結果發現ics和klp主題下的圖片不全,少5個,lxx主題下的圖片是全的,這樣的話klp主題下那5個缺失的圖片咋獲取?少的那5個是動態變化的那個enter key
<Key
latin:backgroundType="functional2"
latin:keyStyle="enterKeyStyle"
latin:keyWidth="fillRight" />
然後去key_styles_common裏找了下,沒有styleName叫enterKeyStyle的,不可能啊,沒有的話會掛的,完事全局搜了下,發現這東西定義在另外一個文件裏,然後在key_styles_common裏include進來
<include
latin:keyboardLayout="@xml/key_styles_enter" />
如下,簡單複製了幾個action,根據imeAction區分,parentStyle裏定義了其他信息,也是include的其他文件
<case
latin:imeAction="actionGo"
>
<key-style
latin:styleName="enterKeyStyle"
latin:parentStyle="goActionKeyStyle" />
</case>
<case
latin:imeAction="actionNext"
>
<key-style
latin:styleName="enterKeyStyle"
latin:parentStyle="nextActionKeyStyle" />
</case>
注意一下,這個幾個action的code名字都是key_enter
可以看到,兩種case ,第一個先判斷icon有沒有定義 latin:isIconDefined,沒有的話就走下邊那種,那種裏邊定義了text了,
<!-- Go key -->
<switch>
<case latin:isIconDefined="go_key">
<key-style
latin:styleName="goActionKeyStyle"
latin:keySpec="!icon/go_key|!code/key_enter"
latin:parentStyle="defaultEnterKeyStyle" />
</case>
<default>
<key-style
latin:styleName="goActionKeyStyle"
latin:keySpec="!text/label_go_key|!code/key_enter"
latin:parentStyle="defaultEnterKeyStyle" />
</default>
</switch>
<!-- Next key -->
<switch>
<case latin:isIconDefined="next_key">
<key-style
latin:styleName="nextActionKeyStyle"
latin:keySpec="!icon/next_key|!code/key_enter"
latin:parentStyle="defaultEnterKeyStyle" />
</case>
<default>
<key-style
latin:styleName="nextActionKeyStyle"
latin:keySpec="!text/label_next_key|!code/key_enter"
latin:parentStyle="defaultEnterKeyStyle" />
</default>
</switch>
最終測試了,因爲沒定義這5個圖片,所以在這種主題下,輸入法的action確實不顯示圖片,顯示的是文字
go ,send ,next,prev,done 這種 ,資源文件裏有定義
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="label_go_key" msgid="4033615332628671065">"Go"</string>
<string name="label_next_key" msgid="5586407279258592635">"Next"</string>
<string name="label_previous_key" msgid="1421141755779895275">"Prev"</string>
<string name="label_done_key" msgid="7564866296502630852">"Done"</string>
<string name="label_send_key" msgid="482252074224462163">"Send"</string>
<string name="label_search_key" msgid="7965186050435796642">"Search"</string>
<string name="label_pause_key" msgid="2225922926459730642">"Pause"</string>
<string name="label_wait_key" msgid="5891247853595466039">"Wait"</string>
</resources>
和KeyboardIconsSet一樣也有一個KeyboardTextsSet 的類來解析文字
public final class KeyboardTextsSet {
public static final String PREFIX_TEXT = "!text/";
private static final String PREFIX_RESOURCE = "!string/";
text獲取還得另外一個類KeyboardTextsTable.java
說明下 text 定義的字段是在KeyboardTextsTable裏存着的,string是資源文件string.xml裏的
相關部分代碼
final String name = text.substring(pos + prefixLength, end);
if (prefix.equals(PREFIX_TEXT)) {//text文本
sb.append(getText(name));
} else { // PREFIX_RESOURCE
final String resourcePackageName = mResourcePackageName;
final RunInLocale<String> getTextJob = new RunInLocale<String>() {
@Override
protected String job(final Resources res) {
final int resId = res.getIdentifier(name, "string", resourcePackageName);//獲取string
return res.getString(resId);
}
};
sb.append(getTextJob.runInLocale(mResources, mResourceLocale));
}
//text開頭的文本是通過table查找的
public String getText(final String name) {
return KeyboardTextsTable.getText(name, mTextsTable);
}
下邊簡單截取一部分table數據
private static final String[] NAMES = {
// /* index:histogram */ "name",
/* 0:33 */ "morekeys_a",
/* 1:33 */ "morekeys_o",
/* 2:32 */ "morekeys_e",
/* 3:31 */ "morekeys_u",
/* 4:31 */ "keylabel_to_alpha",
/* 5:30 */ "morekeys_i",
//
/* Default texts */
private static final String[] TEXTS_DEFAULT = {
/* morekeys_a ~ */
EMPTY, EMPTY, EMPTY, EMPTY,
/* ~ morekeys_u */
// Label for "switch to alphabetic" key.
/* keylabel_to_alpha */ "ABC",
/* morekeys_i ~ */
EMPTY, EMPTY, EMPTY,
繼續看下如何修改按鍵上圖片大小
- 直接去Key裏找用到icon的地方
public int getIconId() {
return mIconId;
}
@Nullable
public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha)
KeyboardView.java 裏有用到getIcon方法
下邊的方法裏,自己處理下iconWidth和iconHeight的計算邏輯,看你要放大還是縮小。
// Draw key top visuals.
protected void onDrawKeyTopVisuals(@Nonnull final Key key, @Nonnull final Canvas canvas,
@Nonnull final Paint paint, @Nonnull final KeyDrawParams params) {
//
final Drawable icon = (keyboard == null) ? null
: key.getIcon(keyboard.mIconsSet, params.mAnimAlpha);
//
if (label != null) {
//這裏是畫label的,如果要修改label大小顏色啥可以這裏處理下
}
// Draw key icon.
if (label == null && icon != null) {
final int iconWidth;
if (key.getCode() == Constants.CODE_SPACE && icon instanceof NinePatchDrawable) {
iconWidth = (int)(keyWidth * mSpacebarIconWidthRatio);
} else {
iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth);
}
final int iconHeight = icon.getIntrinsicHeight();
final int iconY;
if (key.isAlignIconToBottom()) {
iconY = keyHeight - iconHeight;
} else {
iconY = (keyHeight - iconHeight) / 2; // Align vertically center.
}
final int iconX = (keyWidth - iconWidth) / 2; // Align horizontally center.
drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
}
如何去掉三個點
想了兩種情況:
第一種長按功能去掉,這個比較簡單,
第二種,長按功能還在,只是三個點不可見,找下三個點哪裏設置的,隱藏掉或者顏色修改爲透明
如下,把//delete 註釋的那行代碼刪了就看不到三個點了.
//空格
<Key
latin:keyStyle="spaceKeyStyle"
latin:keyWidth="50.0%p" />
<key-style
latin:styleName="spaceKeyStyle"
latin:keySpec="!icon/space_key|!code/key_space"
latin:backgroundType="spacebar"
latin:keyActionFlags="noKeyPreview|enableLongPress" />//delete
//逗號
<Key
latin:keySpec="!text/keyspec_comma"
latin:keyLabelFlags="hasPopupHint"
latin:keyStyle="settingsMoreKeysStyle" />
<key-style
latin:styleName="settingsMoreKeysStyle"
latin:keyLabelFlags="hasPopupHint"//delete
latin:additionalMoreKeys="!text/keyspec_settings"
latin:backgroundType="functional" />
//句號
<Key
latin:keySpec="!text/keyspec_period"
latin:keyHintLabel="!text/keyhintlabel_period"
latin:keyLabelFlags="hasPopupHint|hasShiftedLetterHint"//delete
latin:moreKeys="!text/morekeys_period"
latin:backgroundType="functional" />
//還有那個enter鍵也有三個點
最後發現三個點是在thems-holo.xml裏定義的,可以把這裏的字符串改成空,自然看不見了
<!-- U+2026: "…" HORIZONTAL ELLIPSIS -->
<item name="keyPopupHintLetter">…</item>
也可以代碼裏不畫了,還是KeyboardView.java裏,這裏兩個條件都滿足才畫,這樣我們就知道xml裏的key屬性咋修改了。刪除 latin:keyLabelFlags="hasPopupHint"即可
if (key.hasPopupHint() && key.getMoreKeys() != null) {
drawKeyPopupHint(key, canvas, paint, params);
}
//
public final boolean hasPopupHint() {
return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0; // 0x200
}
逗號和問號沒啥問題了,不過空格好像不太一樣,空格好像不滿足上邊的if條件,那爲啥也畫了三個點?
看space的屬性有個enableLongPress ,把這個刪了發現那三個點也沒了,搜了下關聯的代碼
//Key.java
public final boolean isLongPressEnabled() {
// We need not start long press timer on the key which has activated shifted letter.
return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0
&& (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
}
//MainKeyboardView.java
//可以看到,對space單獨做了處理,下邊有調用drawKeyPopupHint
protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
final KeyDrawParams params) {
if (key.altCodeWhileTyping() && key.isEnabled()) {
params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
}
super.onDrawKeyTopVisuals(key, canvas, paint, params);
final int code = key.getCode();
if (code == Constants.CODE_SPACE) {
// If input language are explicitly selected.
if (mLanguageOnSpacebarFormatType != LanguageOnSpacebarUtils.FORMAT_TYPE_NONE) {
drawLanguageOnSpacebar(key, canvas, paint);
}
// Whether space key needs to show the "..." popup hint for special purposes
if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) {
drawKeyPopupHint(key, canvas, paint, params);//space不想畫三個點把這裏註釋也行
}
} else if (code == Constants.CODE_LANGUAGE_SWITCH) {
drawKeyPopupHint(key, canvas, paint, params);
}
}
key間隙大小修改
- 水平方向的間隔
<fraction name="config_key_horizontal_gap_holo">1.030%p</fraction>
上邊的值改成5%p,效果如下,可以看到,兩個key之間的間隙大小就是這個值,那麼對於左右兩端的,就是這個值的一半了
那麼左右兩邊的間隔不可控嗎?不是的,只是默認的值是0而已
<fraction name="config_keyboard_left_padding">0%p</fraction>
<fraction name="config_keyboard_right_padding">0%p</fraction>
如果要水平方向所有間隔都一樣,咋辦?簡單
gap如果是10,那麼最左邊的key默認也有個5的間隔,這樣的話left padding也弄成5就ok了。
<fraction name="config_key_horizontal_gap_holo">10%p</fraction>
<fraction name="config_keyboard_left_padding">5%p</fraction>
<fraction name="config_keyboard_right_padding">5%p</fraction>
- 垂直方向的間隔
<fraction name="config_min_keyboard_height">83%p</fraction>
<fraction name="config_key_vertical_gap_holo">13.0%p</fraction>
如上,我把鍵盤整體高度弄的很大83%p,vertical gap 也不小,效果如下,可以看出,這個不影響頂部和底部的間隔
那麼頂部和底部的間隔咋控制,下邊兩個值
<fraction name="config_keyboard_top_padding_holo">2.0%p</fraction>
<fraction name="config_keyboard_bottom_padding_holo">2.0%p</fraction>
這些都是主題裏配置的,不同的分辨率可能有不同的值而已,自己修改下自己需要的即可
<style name="Keyboard">
<item name="rowHeight">25%p</item>
<item name="horizontalGap">@fraction/config_key_horizontal_gap_holo</item>
<item name="verticalGap">@fraction/config_key_vertical_gap_holo</item>
<item name="touchPositionCorrectionData">@array/touch_position_correction_data_holo</item>
<item name="keyboardTopPadding">@fraction/config_keyboard_top_padding_holo</item>
<item name="keyboardBottomPadding">@fraction/config_keyboard_bottom_padding_holo</item>
<item name="keyboardLeftPadding">@fraction/config_keyboard_left_padding</item>
<item name="keyboardRightPadding">@fraction/config_keyboard_right_padding</item>
<item name="moreKeysTemplate">@xml/kbd_more_keys_keyboard_template</item>
<item name="maxMoreKeysColumn">@integer/config_max_more_keys_column</item>
</style>
那麼這裏的2%p的p指的是誰?就是對應的鍵盤的寬和高,
那麼如何讓水平和垂直的間隔一樣?拿到鍵盤的寬高自己算下就ok了。
mKeyVerticalGap = (int) res.getFraction(R.fraction.config_key_vertical_gap_holo,
defaultKeyboardHeight, defaultKeyboardHeight);
mBottomPadding = (int) res.getFraction(R.fraction.config_keyboard_bottom_padding_holo,
defaultKeyboardHeight, defaultKeyboardHeight);
mTopPadding = (int) res.getFraction(R.fraction.config_keyboard_top_padding_holo,
defaultKeyboardHeight, defaultKeyboardHeight);
mKeyHorizontalGap = (int) (res.getFraction(R.fraction.config_key_horizontal_gap_holo,
defaultKeyboardWidth, defaultKeyboardWidth));
問題記錄
- 輸入法主題裏有4種,可測試發現只有第三種可以選,選其他的沒效果【我拿到的源碼是被修改過的】
看下源碼,查找問題
進入ThemeSettingsFragment類,查看主題存儲查找的邏輯
public static KeyboardTheme getKeyboardTheme(final Context context) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final KeyboardTheme[] availableThemeArray = getAvailableThemeArray(context);
return getKeyboardTheme(prefs, BuildCompatUtils.EFFECTIVE_SDK_INT, availableThemeArray);
}
//
static KeyboardTheme[] getAvailableThemeArray(final Context context) {
if (AVAILABLE_KEYBOARD_THEMES == null) {
final int[] availableThemeIdStringArray = context.getResources().getIntArray(
R.array.keyboard_theme_ids);
final ArrayList<KeyboardTheme> availableThemeList = new ArrayList<>();
for (final int id : availableThemeIdStringArray) {
final KeyboardTheme theme = searchKeyboardThemeById(id, KEYBOARD_THEMES);
if (theme != null) {
availableThemeList.add(theme);
}
}
AVAILABLE_KEYBOARD_THEMES = availableThemeList.toArray(
new KeyboardTheme[availableThemeList.size()]);
Arrays.sort(AVAILABLE_KEYBOARD_THEMES);
}
return AVAILABLE_KEYBOARD_THEMES;
}
//可以發現,只有themeid是2的返回了數據,其他都是null,所以最終AVAILABLE_KEYBOARD_THEMES只有一條數據.
static KeyboardTheme searchKeyboardThemeById(final int themeId,
final KeyboardTheme[] availableThemeIds) {
// TODO: This search algorithm isn't optimal if there are many themes.
for (final KeyboardTheme theme : availableThemeIds) {
if (theme.mThemeId == 2) {
return theme;
}
}
return null;
}
more keys 彈框相關內容修改記錄
MoreKeysKeyboardView 這個類就是用來顯示更多keys彈框的View,用到的地方如下
<com.android.inputmethod.keyboard.MoreKeysKeyboardView
android:id="@+id/more_keys_keyboard_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/moreKeysKeyboardViewStyle" />
搜下上邊用到的style,對應主題下有這個屬性
<item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.ICS</item>
//
<style
name="MoreKeysKeyboardView.ICS"
parent="KeyboardView.ICS"
>
<item name="android:background">@drawable/keyboard_popup_panel_background_ics</item>
<item name="keyBackground">@drawable/btn_keyboard_key_popup_ics</item>
<item name="divider">@drawable/more_keys_divider</item>
<item name="keyTypeface">normal</item>
<item name="verticalCorrection">@dimen/config_more_keys_keyboard_vertical_correction_holo</item>
</style>
可以看到,有morekeysboard的背景,key的背景,divider,
那麼這個彈框的大小位置咋確定的?
看MoreKeysKeyboard.java 裏有個Builder
public Builder(final Context context, final Key key, final Keyboard keyboard,
final boolean isSingleMoreKeyWithPreview, final int keyPreviewVisibleWidth,
final int keyPreviewVisibleHeight, final Paint paintToMeasure) {
super(context, new MoreKeysKeyboardParams());
load(keyboard.mMoreKeysTemplate, keyboard.mId);
// TODO: More keys keyboard's vertical gap is currently calculated heuristically.
// Should revise the algorithm.
mParams.mVerticalGap = keyboard.mVerticalGap / 2;
// This {@link MoreKeysKeyboard} is invoked from the <code>key</code>.
mParentKey = key;
final int keyWidth, rowHeight;//morekeys裏的key的寬度這裏可以自己處理
if (isSingleMoreKeyWithPreview) {
// Use pre-computed width and height if this more keys keyboard has only one key to
// mitigate visual flicker between key preview and more keys keyboard.
// Caveats for the visual assets: To achieve this effect, both the key preview
// backgrounds and the more keys keyboard panel background have the exact same
// left/right/top paddings. The bottom paddings of both backgrounds don't need to
// be considered because the vertical positions of both backgrounds were already
// adjusted with their bottom paddings deducted.
keyWidth = keyPreviewVisibleWidth;
rowHeight = keyPreviewVisibleHeight + mParams.mVerticalGap;
} else {
final float padding = context.getResources().getDimension(
R.dimen.config_more_keys_keyboard_key_horizontal_padding)
+ (key.hasLabelsInMoreKeys()
? mParams.mDefaultKeyWidth * LABEL_PADDING_RATIO : 0.0f);
keyWidth = getMaxKeyWidth(key, mParams.mDefaultKeyWidth, padding, paintToMeasure);
rowHeight = keyboard.mMostCommonKeyHeight;
}
final int dividerWidth;
if (key.needsDividersInMoreKeys()) {
dividerWidth = (int)(keyWidth /9f);
} else {
dividerWidth = 0;//key之間的分割線寬度,
}
final MoreKeySpec[] moreKeys = key.getMoreKeys();
mParams.setParameters(moreKeys.length, key.getMoreKeysColumnNumber(), keyWidth,
rowHeight, key.getX() + key.getWidth() / 2, keyboard.mId.mWidth,
key.isMoreKeysFixedColumn(), key.isMoreKeysFixedOrder(), dividerWidth);
}
下邊就是計算morekeys彈框的其他屬性了,比如寬高位置,有需要修改的可以自行修改
public void setParameters(final int numKeys, final int numColumn, final int keyWidth,
final int rowHeight, final int coordXInParent, final int parentKeyboardWidth,
final boolean isMoreKeysFixedColumn, final boolean isMoreKeysFixedOrder,
final int dividerWidth) {
mIsMoreKeysFixedOrder = isMoreKeysFixedOrder;
if (parentKeyboardWidth / keyWidth < Math.min(numKeys, numColumn)) {
throw new IllegalArgumentException("Keyboard is too small to hold more keys: "
+ parentKeyboardWidth + " " + keyWidth + " " + numKeys + " " + numColumn);
}
mDefaultKeyWidth = keyWidth;
mDefaultRowHeight = rowHeight;
final int numRows = (numKeys + numColumn - 1) / numColumn;
mNumRows = numRows;
final int numColumns = isMoreKeysFixedColumn ? Math.min(numKeys, numColumn)
: getOptimizedColumns(numKeys, numColumn);
mNumColumns = numColumns;
final int topKeys = numKeys % numColumns;
mTopKeys = topKeys == 0 ? numColumns : topKeys;
final int numLeftKeys = (numColumns - 1) / 2;
final int numRightKeys = numColumns - numLeftKeys; // including default key.
// Maximum number of keys we can layout both side of the parent key
final int maxLeftKeys = coordXInParent / keyWidth;
final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
int leftKeys, rightKeys;
if (numLeftKeys > maxLeftKeys) {
leftKeys = maxLeftKeys;
rightKeys = numColumns - leftKeys;
} else if (numRightKeys > maxRightKeys + 1) {
rightKeys = maxRightKeys + 1; // include default key
leftKeys = numColumns - rightKeys;
} else {
leftKeys = numLeftKeys;
rightKeys = numRightKeys;
}
// If the left keys fill the left side of the parent key, entire more keys keyboard
// should be shifted to the right unless the parent key is on the left edge.
if (maxLeftKeys == leftKeys && leftKeys > 0) {
leftKeys--;
rightKeys++;
}
// If the right keys fill the right side of the parent key, entire more keys
// should be shifted to the left unless the parent key is on the right edge.
if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
leftKeys++;
rightKeys--;
}
mLeftKeys = leftKeys;
mRightKeys = rightKeys;
// Adjustment of the top row.
mTopRowAdjustment = isMoreKeysFixedOrder ? getFixedOrderTopRowAdjustment()
: getAutoOrderTopRowAdjustment();
mDividerWidth = dividerWidth;
mColumnWidth = mDefaultKeyWidth + mDividerWidth;
mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth ;
// Need to subtract the bottom row's gutter only.
mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
+ mTopPadding + mBottomPadding;
}
臨時記錄
- 我們的需求隱藏了上邊的suggestion部分,只顯示key的部分,現在我想在keyboard右上角加個按鈕,點擊可以隱藏輸入法,如下,加個imageView,問題很多
首先發現imageView如果不是match的話,那麼下邊的MainKeyboardView的寬度和imageView一樣寬。
其次,給imageView設置的點擊事件無效.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical" >
<!-- To ensure that key preview popup is correctly placed when the current system locale is
one of RTL locales, layoutDirection="ltr" is needed in the SDK version 17+. -->
<com.android.inputmethod.latin.suggestions.SuggestionStripView
android:id="@+id/suggestion_strip_view"
android:layoutDirection="ltr"
android:layout_width="match_parent"
android:layout_height="@dimen/config_suggestions_strip_height"
android:gravity="center_vertical"
style="?attr/suggestionStripViewStyle" />
<ImageView
android:id="@+id/iv_hidden"
android:src="@drawable/ic_shift_on"
android:layout_width="match_parent"
android:layout_height="80dp"/>
<!-- To ensure that key preview popup is correctly placed when the current system locale is
one of RTL locales, layoutDirection="ltr" is needed in the SDK version 17+. -->
<com.android.inputmethod.keyboard.MainKeyboardView
android:id="@+id/keyboard_view"
android:layoutDirection="ltr"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
點擊無效,那麼猜測touch事件被攔截了,應該是父容器處理過?
查下代碼,很明顯,要去看下InputView幹啥了。
<com.android.inputmethod.latin.InputView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/inputViewStyle">
<include
android:id="@+id/main_keyboard_frame"
layout="@layout/main_keyboard_frame" />
<include
android:id="@+id/emoji_palettes_view"
layout="@layout/emoji_palettes_view" />
</com.android.inputmethod.latin.InputView>
測試結果,我在InputView的幾個相關的touch方法裏都添加了日誌,可是點擊那個按鈕,這裏邊一個方法都沒走,這就奇怪了,容器touch方法一個沒走是幾個意思。
然後我想着難道是InputView的父容器處理了?就是那個dialog,去dialog裏查了下也沒處理。
最後在粗略瀏覽InputMethodService的時候看到了一個Insets的東西,看到了touchableRegion,setTouchableInsets,看名字就是設置觸摸區域,觸摸範圍的,感覺有點意思,就打印了下相關的代碼。結果證實就是這玩意整的。
info.contentInsets.top = mTmpInsets.contentTopInsets;
info.visibleInsets.top = mTmpInsets.visibleTopInsets;
info.touchableRegion.set(mTmpInsets.touchableRegion);
info.setTouchableInsets(mTmpInsets.touchableInsets);
整理下相關的代碼
- InputMethodService裏rootView[就是dialog的根佈局]註冊了一個回調
mRootView = mInflater.inflate(
com.android.internal.R.layout.input_method, null);
mWindow.setContentView(mRootView);//window就是輸入法對應的dialog
mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsComputer);
mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
回調處理
final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
if (isExtractViewShown()) {
// In true fullscreen mode, we just say the window isn't covering
// any content so we don't impact whatever is behind.
View decor = getWindow().getWindow().getDecorView();
info.contentInsets.top = info.visibleInsets.top = decor.getHeight();
info.touchableRegion.setEmpty();
info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
} else {//我們用到的走這裏
onComputeInsets(mTmpInsets);
info.contentInsets.top = mTmpInsets.contentTopInsets;
info.visibleInsets.top = mTmpInsets.visibleTopInsets;
info.touchableRegion.set(mTmpInsets.touchableRegion);
info.setTouchableInsets(mTmpInsets.touchableInsets);
}
};
用到回調的地方
ViewRootIml.java
final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
insets.reset();
// Compute new insets in place.
mAttachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
然後ViewTreeObserver裏,下邊調用了回調
final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
mOnComputeInternalInsetsListeners;
if (listeners != null && listeners.size() > 0) {
CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start();
try {
int count = access.size();
for (int i = 0; i < count; i++) {
access.get(i).onComputeInternalInsets(inoutInfo);
}
} finally {
listeners.end();
}
}
}
onComputeInsets(mTmpInsets); 這個方法InputMethodService裏有,子類LatinIME也有,我們只看下touchableRegion的設置
LatinIME.java
final int visibleTopY = inputHeight - visibleKeyboardView.getHeight() - suggestionsHeight;
mSuggestionStripView.setMoreSuggestionsHeight(visibleTopY);
// Need to set expanded touchable region only if a keyboard view is being shown.
if (visibleKeyboardView.isShown()) {
final int touchLeft = 0;
final int touchTop = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY;
final int touchRight = visibleKeyboardView.getWidth();
final int touchBottom = inputHeight
// Extend touchable region below the keyboard.
+ EXTENDED_TOUCHABLE_REGION_HEIGHT;
outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
outInsets.touchableRegion.set(touchLeft, touchTop, touchRight, touchBottom);
}
由此可見,我們只需要修改下visibleTopY即可。因爲我們的需求是把suggestions佈局隱藏了,所以上邊的suggestionsHeight就是0了,最終topY就是mainKeyboard的頂部位置了,現在要加個按鈕在mainKeyboard上,那麼需要再減去箭頭的高度,以確保箭頭也在可觸摸範圍。
/**
* Option for {@link #setTouchableInsets(int)}: the entire window frame
* can be touched.
*/
public static final int TOUCHABLE_INSETS_FRAME = 0;
/**
* Option for {@link #setTouchableInsets(int)}: the area inside of
* the content insets can be touched.
*/
public static final int TOUCHABLE_INSETS_CONTENT = 1;
/**
* Option for {@link #setTouchableInsets(int)}: the area inside of
* the visible insets can be touched.
*/
public static final int TOUCHABLE_INSETS_VISIBLE = 2;
/**
* Option for {@link #setTouchableInsets(int)}: the area inside of
* the provided touchable region in {@link #touchableRegion} can be touched.
*/
public static final int TOUCHABLE_INSETS_REGION = 3;
這種效果
箭頭所在的背景是透明的,需要注意的地方,還是LatinIME裏的onComputeInsets方法
2行加註釋的地方,outinsets只修改touch的top,確保點擊圖標有效,content的top位置不修改,確保上邊的icon沒有背景,是透明的。
if (visibleKeyboardView.isShown()) {
final int touchLeft = 0;
final int touchTop = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY;
final int touchRight = visibleKeyboardView.getWidth();
final int touchBottom = inputHeight
// Extend touchable region below the keyboard.
+ EXTENDED_TOUCHABLE_REGION_HEIGHT;
outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
outInsets.touchableRegion.set(touchLeft, touchTop-60, touchRight, touchBottom);//觸摸範圍top增加icon的高度,60是箭頭的高度
}
outInsets.contentTopInsets = visibleTopY;//content的top不修改,還是mainkeyboard的頂部
outInsets.visibleTopInsets = visibleTopY;
雜記
- 自己寫一個輸入法的話,要做啥?
首先要成爲輸入法,需要一個服務,這個服務在你切換當前輸入法爲默認輸入法的時候就啓動了。
meta-data是必須的,name是固定的,有這個纔會被識別爲輸入法。
<service android:name=".july.MyTestService"
android:label="test input"//系統設置裏顯示的輸入法的名字就是這個
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data android:name="android.view.im" android:resource="@xml/method" />
</service>
method.xml
<?xml version="1.0" encoding="utf-8"?>
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity"//系統設置裏輸入法選項會跳轉這個類
android:isDefault="true"
android:supportsSwitchingToNextInputMethod="true">
<subtype android:icon="@drawable/abc_vector_test"
android:label="subtype label"
android:name="name"
android:isAsciiCapable="true"
/>