Android4.3 Google Pinyin输入法UI定制
先来看原版输入法的效果如下:
定制后的效果如下:
SystemServer会启动InputMethodManagerService,在InputMethodManagerService中会启动action为"android.view.InputMethod"的Service,同时会设置组件名称来决定启动哪个输入法。
/frameworks/base/services/java/com/android/server/InputMethodManagerService.java
1194 InputBindResult startInputInnerLocked() {
1195 if (mCurMethodId == null) {
1196 return mNoBinding;
1197 }
1198
1199 if (!mSystemReady) {
1200 // If the system is not yet ready, we shouldn't be running third
1201 // party code.
1202 return new InputBindResult(null, null, mCurMethodId, mCurSeq);
1203 }
1204
1205 InputMethodInfo info = mMethodMap.get(mCurMethodId);
1206 if (info == null) {
1207 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1208 }
1209
1210 unbindCurrentMethodLocked(false, true);
1211 //InputMethod.SERVICE_INTERFACE ="android.view.InputMethod"
1212 mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
1213 mCurIntent.setComponent(info.getComponent());
1214 mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
1215 com.android.internal.R.string.input_method_binding_label);
1216 mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
1217 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
1218 if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
1219 | Context.BIND_NOT_VISIBLE | Context.BIND_SHOWING_UI)) {
1220 mLastBindTime = SystemClock.uptimeMillis();
1221 mHaveConnection = true;
1222 mCurId = info.getId();
1223 mCurToken = new Binder();
1224 try {
1225 if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
1226 mIWindowManager.addWindowToken(mCurToken,
1227 WindowManager.LayoutParams.TYPE_INPUT_METHOD);
1228 } catch (RemoteException e) {
1229 }
1230 return new InputBindResult(null, null, mCurId, mCurSeq);
1231 } else {
1232 mCurIntent = null;
1233 Slog.w(TAG, "Failure connecting to input method service: "
1234 + mCurIntent);
1235 }
1236 return null;
1237 }
我们需要关注的是PinyinIME,在启动Service时如果进程没有创建,那么会首先创建进程,这里就是创建PinyinIME这个apk的进程,同时启动com.android.inputmethod.pinyin.PinyinIME这个service。在这个service的onCreate中调用了startPinyinDecoderService启动了AndroidManifest.xml中声明的另一个Sevcie采用显示启动的方式。
177 public void onCreate() {
178 mEnvironment = Environment.getInstance();
179 if (mEnvironment.needDebug()) {
180 Log.d(TAG, "onCreate.");
181 }
182 super.onCreate();
183
184 startPinyinDecoderService();
185 mImEn = new EnglishInputProcessor();
186 Settings.getInstance(PreferenceManager
187 .getDefaultSharedPreferences(getApplicationContext()));
188
189 mInputModeSwitcher = new InputModeSwitcher(this);
190 mChoiceNotifier = new ChoiceNotifier(this);
191 mGestureListenerSkb = new OnGestureListener(false);
192 mGestureListenerCandidates = new OnGestureListener(true);
193 mGestureDetectorSkb = new GestureDetector(this, mGestureListenerSkb);
194 mGestureDetectorCandidates = new GestureDetector(this,
195 mGestureListenerCandidates);
196
197 mEnvironment.onConfigurationChanged(getResources().getConfiguration(),
198 this);
199 }
在PinyinIME中这个包的layout中有三个layout分别包含以下区域:
floating_container中文区域上面显示你所按下按键的字符的区域(红框部分)
candidates_container- > 显示中文的区域(黄框部分)
skb_container-> 软键盘UI(蓝框部分)
开启apk的log后有以下发现:
1.点击editText的组件会调用到 onStartInputView去开启软键盘Android 4.3软键盘如下图所示:
Candidates window是上面中文字符(红框部分)
键盘定制:
1.各种键的大小,间隔,颜色,背景
键的大小和间隔这次是通过键的背景图通过预留一些透明边框来实现
键盘的背景是在/packages/inputmethods/PinyinIME/res/layout/skb_container.xml中的SkbContainer的背景定义的要切换键盘背景直接替换掉相应的图片即可
键盘的UI的xml对应的是在/packages/inputmethods/PinyinIME/res/xml/中定义的,如qwerty键盘对应的是:
/packages/inputmethods/PinyinIME/res/xml/skb_qwerty.xml
16<keyboard
//skb_template1是一个模板其中定义了普通按键,功能按键,enter按键等等的样式可以直接引用
17 skb_template="@xml/skb_template1"
18 skb_cache_flag="true"
19 qwerty="true"
20 qwerty_uppercase="true"
21 width="10%p"
22 height="25%p"
23 key_type="0"
24 repeat="false"
25 balloon="true">
26 <!--第一行 qwerty 由于没有指定键的宽高则是使用keyboard的宽(10%)高(25%) 10个键*10% =100% 键盘的高度是有普通按键的高度*4决定的所以这边是25%也是由于qwerty的键盘只有4行-->
27 <row>
28 <keys splitter="|" labels="Q|W|E|R|T|Y|U|I|O|P"
29 codes="45|51|33|46|48|53|49|37|43|44"/>
30 </row>
31 <!--第二行指定键的宽度是10.205%p 从4.078%p开始,所以前面预留了一块空间并且由于4.078%p+10.205*9 小于100% 所以后面也空下来了一部分空间-->
32 <row start_pos_x="4.078%p" width="10.205%p">
33 <keys splitter="|" labels="A|S|D|F|G|H|J|K|L"
34 codes="29|47|32|34|35|36|38|39|40"/>
35 </row>
36 <!--第三行包含 ,Z X C V B N M 同上-->:
37 <row width="10.205%p">
<!--下面这一行指定了 ,这个键的宽度是14.286%p 需要使用图片来作为字符 icon_popup 则是指定在按下相应的键盘后应该显示什么字符或者图片-->
38 <key label="," width="14.286%p" icon="@drawable/comma_full_icon"
39 icon_popup="@drawable/comma_full_popup_icon">
<!--toggle_state 则是在不同输入模式下的不同效果 ,比如中文模式和英文模式的逗号的样式是不同的 keyType则是在模板中定义的键的类型:
如keyType = 2 则对应skb_template1.xml中如下:意思是带小点的key
39 <key_type
40 id="2"
41 bg="@drawable/light_key_bg"
42 hlbg="@drawable/light_key_hl_bg"/>
接着在下面 <key id="3"/>也是模板中定义的一种按键:下面这种定义的是删除键
97 <key id="3" start_pos_x="85.715%p" start_pos_y="50%p"
98 width="14.286%p" height="25%p" code="67" key_type="1"
99 repeat="true"/>
-->
40 <toggle_state state_id="@string/toggle_smiley" code="-6"
41 icon="@drawable/smiley_icon" icon_popup="@drawable/smiley_popup_icon"
42 key_type="2"/>
43 <toggle_state state_id="@string/toggle_en_lower" code="-1"
44 icon="@drawable/shift_off_icon"
45 icon_popup="@drawable/shift_off_popup_icon" key_type="2"/>
46 <toggle_state state_id="@string/toggle_en_upper" code="-1"
47 icon="@drawable/shift_on_icon"
48 icon_popup="@drawable/shift_on_popup_icon" key_type="3"/>
49 <toggle_state state_id="@string/toggle_cn_cand" label="'"
50 key_type="1"/>
51 </key>
52 <keys splitter="|" labels="Z|X|C|V|B|N|M"
53 codes="54|52|31|50|30|42|41"/>
54 <key id="3"/>
55 </row>
56
57 <row width="20%p" row_id="@string/toggle_row_cn">
58 <key id="6"/>
59 <key id="4"/>
60 <key code="62" key_type="5" width="30.608%p"/>
61 <key label="。" width="14.696%p" icon="@drawable/period_full_icon"
62 icon_popup="@drawable/period_full_popup_icon"/>
63 <key id="1"/>
64 </row>
65
66 <row width="20%p" row_id="@string/toggle_row_en" start_pos_y="75%p">
67 <key id="6"/>
68 <key id="4"/>
69 <key code="62" key_type="5" width="30.608%p"/>
70 <key id="7"/>
71 <key id="1"/>
72 </row>
73
74 <row width="20%p" row_id="@string/toggle_row_uri" start_pos_y="75%p">
75 <key id="6"/>
76 <key id="4"/>
77 <key label="/" width="15.304%p"/>
78 <key code="62" key_type="5" width="15.304%p"/>
79 <key id="7"/>
80 <key id="1"/>
81 </row>
82
83 <row width="20%p" row_id="@string/toggle_row_emailaddress" start_pos_y="75%p">
84 <key id="6"/>
85 <key id="4"/>
86 <key label="\@" width="15.304%p"/>
87 <key code="62" key_type="5" width="15.304%p"/>
88 <key id="7"/>
89 <key id="1"/>
90 </row>
91</keyboard>
app通过XmlKeyboardLoader来解析xml并创建成softKey对象加入到softKeyboard中
/packages/inputmethods/PinyinIME/src/com/android/inputmethod/pinyin/XmlKeyboardLoader.java
public SoftKeyboard loadKeyboard(int resourceId, int skbWidth, int skbHeight) {
...
508 } else if (XMLTAG_ROW.compareTo(attr) == 0) {
509 if (!attrRow.getAttributes(attrSkb)) {
510 return null;
511 }
512 // Get the starting positions for the row.
//解析row的tag 设置第一个键的坐标
513 mKeyXPos = getFloat(xrp, XMLATTR_START_POS_X, 0);
514 mKeyYPos = getFloat(xrp, XMLATTR_START_POS_Y, mKeyYPos);
515 int rowId = getInteger(xrp, XMLATTR_ROW_ID,
516 KeyRow.ALWAYS_SHOW_ROW_ID);
517 softKeyboard.beginNewRow(rowId, mKeyYPos);
518 } else if (XMLTAG_KEYS.compareTo(attr) == 0) {
519 if (null == softKeyboard) return null;
520 if (!attrKeys.getAttributes(attrRow)) {
521 return null;
522 }
523
524 String splitter = xrp.getAttributeValue(null,
525 XMLATTR_KEY_SPLITTER);
526 splitter = Pattern.quote(splitter);
527 String labels = xrp.getAttributeValue(null,
528 XMLATTR_KEY_LABELS);
529 String codes = xrp.getAttributeValue(null,
530 XMLATTR_KEY_CODES);
531 if (null == splitter || null == labels) {
532 return null;
533 }
534 String labelArr[] = labels.split(splitter);
535 String codeArr[] = null;
536 if (null != codes) {
537 codeArr = codes.split(splitter);
538 if (labelArr.length != codeArr.length) {
539 return null;
540 }
541 }
542
543 for (int i = 0; i < labelArr.length; i++) {
544 softKey = new SoftKey();
545 int keyCode = 0;
546 if (null != codeArr) {
547 keyCode = Integer.valueOf(codeArr[i]);
548 }
549 softKey.setKeyAttribute(keyCode, labelArr[i],
550 attrKeys.repeat, attrKeys.balloon);
551
552 softKey.setKeyType(mSkbTemplate
553 .getKeyType(attrKeys.keyType), null, null);
554 //定义key的上下左右 可以在这里设置普通键的大小和间隔 以比例的方式设置
555 float left, right, top, bottom;
556 left = mKeyXPos;
557
558 right = left + attrKeys.keyWidth;
559 top = mKeyYPos;
560 bottom = top + attrKeys.keyHeight;
561
562 if (right - left < 2 * mKeyXMargin) return null;
563 if (bottom - top < 2 * mKeyYMargin) return null;
564
565 softKey.setKeyDimensions(left, top, right, bottom);
566 softKeyboard.addSoftKey(softKey);
567 mKeyXPos = right;
568 if ((int) mKeyXPos * mSkbWidth > mSkbWidth) {
569 return null;
570 }
571 }
572 } else if (XMLTAG_KEY.compareTo(attr) == 0) {
573 if (null == softKeyboard) {
574 return null;
575 }
576 if (!attrKey.getAttributes(attrRow)) {
577 return null;
578 }
579
580 int keyId = this.getInteger(xrp, XMLATTR_ID, -1);
//获取功能键
581 if (keyId >= 0) {
582 softKey = mSkbTemplate.getDefaultKey(keyId);
583 } else {
584 softKey = getSoftKey(xrp, attrKey);
585 }
586 if (null == softKey) return null;
587
588 // Update the position for next key.
589 mKeyXPos = softKey.mRightF;
590 if ((int) mKeyXPos * mSkbWidth > mSkbWidth) {
591 return null;
592 }
593 // If the current xml event type becomes a starting tag,
594 // it indicates that we have parsed too much to get
595 // toggling states, and we started a new row. In this
596 // case, the row starting position information should
597 // be updated.
598 if (mXmlEventType == XmlResourceParser.START_TAG) {
599 attr = xrp.getName();
600 if (XMLTAG_ROW.compareTo(attr) == 0) {
601 mKeyYPos += attrRow.keyHeight;
602 if ((int) mKeyYPos * mSkbHeight > mSkbHeight) {
603 return null;
604 }
605 }
606 }
607 softKeyboard.addSoftKey(softKey);
608 }
}
634 private SoftKey getSoftKey(XmlResourceParser xrp,
635 KeyCommonAttributes attrKey) throws XmlPullParserException,
636 IOException {
637 int keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);
638 String keyLabel = getString(xrp, XMLATTR_KEY_LABEL, null);
639 Drawable keyIcon = getDrawable(xrp, XMLATTR_KEY_ICON, null);
640 Drawable keyIconPopup = getDrawable(xrp, XMLATTR_KEY_ICON_POPUP, null);
641 int popupSkbId = xrp.getAttributeResourceValue(null,
642 XMLATTR_KEY_POPUP_SKBID, 0);
643
644 if (null == keyLabel && null == keyIcon) {
645 keyIcon = mSkbTemplate.getDefaultKeyIcon(keyCode);
646 keyIconPopup = mSkbTemplate.getDefaultKeyIconPopup(keyCode);
647 if (null == keyIcon || null == keyIconPopup) return null;
648 }
649
650 // Dimension information must been initialized before
651 // getting toggle state, because mKeyYPos may be changed
652 // to next row when trying to get toggle state.
//功能键的上下左右可以在这边设置
653 float left, right, top, bottom;
654 left = mKeyXPos;
655 right = left + attrKey.keyWidth;
656 top = mKeyYPos;
657 bottom = top + attrKey.keyHeight;
658
659 if (right - left < 2 * mKeyXMargin) return null;
660 if (bottom - top < 2 * mKeyYMargin) return null;
661
662 // Try to find if the next tag is
663 // {@link #XMLTAG_TOGGLE_STATE_OF_KEY}, if yes, try to
664 // create a toggle key.
665 boolean toggleKey = false;
666 mXmlEventType = xrp.next();
667 mNextEventFetched = true;
668
669 SoftKey softKey;
670 if (mXmlEventType == XmlResourceParser.START_TAG) {
671 mAttrTmp = xrp.getName();
672 if (mAttrTmp.compareTo(XMLTAG_TOGGLE_STATE) == 0) {
673 toggleKey = true;
674 }
675 }
676 if (toggleKey) {
677 softKey = new SoftKeyToggle();
678 if (!((SoftKeyToggle) softKey).setToggleStates(getToggleStates(
679 attrKey, (SoftKeyToggle) softKey, keyCode))) {
680 return null;
681 }
682 } else {
683 softKey = new SoftKey();
684 }
685
686 // Set the normal state
687 softKey.setKeyAttribute(keyCode, keyLabel, attrKey.repeat,
688 attrKey.balloon);
689 softKey.setPopupSkbId(popupSkbId);
690 softKey.setKeyType(mSkbTemplate.getKeyType(attrKey.keyType), keyIcon,
691 keyIconPopup);
692
693 softKey.setKeyDimensions(left, top, right, bottom);
694 return softKey;
695 }
2.键上显示的字符的大小位置
/packages/inputmethods/PinyinIME/src/com/android/inputmethod/pinyin/Environment.java
65 /**
66 * The text size for normal keys. It is relative to the smaller one of
67 * screen width and height.普通按键的字符的大小比例
68 */
69 private static final float NORMAL_KEY_TEXT_SIZE_RATIO = 0.075f;
70
71 /**
72 * The text size for function keys. It is relative to the smaller one of
73 * screen width and height.功能按键的字符的大小比例
74 */
75 private static final float FUNCTION_KEY_TEXT_SIZE_RATIO = 0.055f;
76
117 public void onConfigurationChanged(Configuration newConfig, Context context) {
118 if (mConfig.orientation != newConfig.orientation) {
119 WindowManager wm = (WindowManager) context
120 .getSystemService(Context.WINDOW_SERVICE);
121 Display d = wm.getDefaultDisplay();
122 mScreenWidth = d.getWidth();
123 mScreenHeight = d.getHeight();
124
125 int scale;
126 if (mScreenHeight > mScreenWidth) {
//竖屏模式下
127 mKeyHeight = (int) (mScreenHeight * KEY_HEIGHT_RATIO_PORTRAIT);
128 mCandidatesAreaHeight = (int) (mScreenHeight * CANDIDATES_AREA_HEIGHT_RATIO_PORTRAIT);
129 scale = mScreenWidth;
130 } else {
//横屏模式下
131 mKeyHeight = (int) (mScreenHeight * KEY_HEIGHT_RATIO_LANDSCAPE);
132 mCandidatesAreaHeight = (int) (mScreenHeight * CANDIDATES_AREA_HEIGHT_RATIO_LANDSCAPE);
133 scale = mScreenHeight;
134 }
135 mNormalKeyTextSize = (int) (scale * NORMAL_KEY_TEXT_SIZE_RATIO);
136 mFunctionKeyTextSize = (int) (scale * FUNCTION_KEY_TEXT_SIZE_RATIO);
137 mNormalBalloonTextSize = (int) (scale * NORMAL_BALLOON_TEXT_SIZE_RATIO);
138 mFunctionBalloonTextSize = (int) (scale * FUNCTION_BALLOON_TEXT_SIZE_RATIO);
139 mKeyBalloonWidthPlus = (int) (scale * KEY_BALLOON_WIDTH_PLUS_RATIO);
140 mKeyBalloonHeightPlus = (int) (scale * KEY_BALLOON_HEIGHT_PLUS_RATIO);
141 }
142
143 mConfig.updateFrom(newConfig);
144 }
194 public int getKeyTextSize(boolean isFunctionKey) {
195 if (isFunctionKey) {
196 return mFunctionKeyTextSize;
197 } else {
198 return mNormalKeyTextSize;
199 }
200 }
键盘的绘制时在SoftKeyboardView的onDraw中胡绘制的:
/packages/inputmethods/PinyinIME/src/com/android/inputmethod/pinyin/SoftKeyboardView.java
400 protected void onDraw(Canvas canvas) {
401 if (null == mSoftKeyboard) return;
402
403 canvas.translate(mPaddingLeft, mPaddingTop);
404
405 Environment env = Environment.getInstance();
//键盘中普通键盘中字符的大小和功能按键的大小都是通过Environment得到的
406 mNormalKeyTextSize = env.getKeyTextSize(false);
407 mFunctionKeyTextSize = env.getKeyTextSize(true);
408 // Draw the last soft keyboard
409 int rowNum = mSoftKeyboard.getRowNum();
410 int keyXMargin = mSoftKeyboard.getKeyXMargin();
411 int keyYMargin = mSoftKeyboard.getKeyYMargin();
412 for (int row = 0; row < rowNum; row++) {
413 KeyRow keyRow = mSoftKeyboard.getKeyRowForDisplay(row);
414 if (null == keyRow) continue;
415 List<SoftKey> softKeys = keyRow.mSoftKeys;
416 int keyNum = softKeys.size();
417 for (int i = 0; i < keyNum; i++) {
418 SoftKey softKey = softKeys.get(i);
//根据键的类型来设置字符的大小
419 if (SoftKeyType.KEYTYPE_ID_NORMAL_KEY == softKey.mKeyType.mKeyTypeId) {
420 mPaint.setTextSize(mNormalKeyTextSize);
421 } else {
422 mPaint.setTextSize(mFunctionKeyTextSize);
423 }
//绘制键
424 drawSoftKey(canvas, softKey, keyXMargin, keyYMargin);
425 }
426 }
427
428 if (mDimSkb) {
429 mPaint.setColor(0xa0000000);
430 canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
431 }
432
433 mDirtyRect.setEmpty();
434 }
436 private void drawSoftKey(Canvas canvas, SoftKey softKey, int keyXMargin,
437 int keyYMargin) {
438 Drawable bg;
439 int textColor;
440 if (mKeyPressed && softKey == mSoftKeyDown) {
441 bg = softKey.getKeyHlBg();
442 textColor = softKey.getColorHl();
443 } else {
444 bg = softKey.getKeyBg();
445 textColor = softKey.getColor();
446 }
447
448 if (null != bg) {
449 bg.setBounds(softKey.mLeft + keyXMargin, softKey.mTop + keyYMargin,
450 softKey.mRight - keyXMargin, softKey.mBottom - keyYMargin);
451 bg.draw(canvas);
452 }
453
454 String keyLabel = softKey.getKeyLabel();
455 Drawable keyIcon = softKey.getKeyIcon();
456 if (null != keyIcon) {
457 Drawable icon = keyIcon;
458 int marginLeft = (softKey.width() - icon.getIntrinsicWidth()) / 2;
459 int marginRight = softKey.width() - icon.getIntrinsicWidth()
460 - marginLeft;
461 int marginTop = (softKey.height() - icon.getIntrinsicHeight()) / 2;
462 int marginBottom = softKey.height() - icon.getIntrinsicHeight()
463 - marginTop;
//设置字符为图片的键的字符的宽高
464 icon.setBounds(softKey.mLeft + marginLeft,
465 softKey.mTop + marginTop, softKey.mRight - marginRight,
466 softKey.mBottom - marginBottom);
467 icon.draw(canvas);
468 } else if (null != keyLabel) {
469 mPaint.setColor(textColor);
470 float x = softKey.mLeft
471 + (softKey.width() - mPaint.measureText(keyLabel)) / 2.0f;
472 int fontHeight = mFmi.bottom - mFmi.top;
473 float marginY = (softKey.height() - fontHeight) / 2.0f;
474 float y = softKey.mTop + marginY - mFmi.top + mFmi.bottom / 1.5f;
//设置普通字符的键的字符绘制的起始位置如果键盘上的字符靠键盘的上面可以将下面的y+1改成y+5
475 canvas.drawText(keyLabel, x, y + 1, mPaint);
476 }
477 }
3.禁用键盘的横屏全屏模式
这个功能可以直接重写如下方法即可:
/packages/inputmethods/PinyinIME/src/com/android/inputmethod/pinyin/PinyinIME.java
@Override
967 public boolean onEvaluateFullscreenMode() {
976 return false;
977 }
4.修改CandidatesView的字体大小和颜色背景等
CandidatesView区域中总共分为三部分,左右箭头和中间显示中文的区域
中间的部分的绘制如下:
/packages/inputmethods/PinyinIME/src/com/android/inputmethod/pinyin/CandidateView.java
479 @Override
480 protected void onDraw(Canvas canvas) {
481 super.onDraw(canvas);
482 // The invisible candidate view(the one which is not in foreground) can
483 // also be called to drawn, but its decoding result and candidate list
484 // may be empty.
485 if (null == mDecInfo || mDecInfo.isCandidatesListEmpty()) return;
486
487 // Calculate page. If the paging information is ready, the function will
488 // return at once.
489 calculatePage(mPageNo);
490
491 int pStart = mDecInfo.mPageStart.get(mPageNo);
492 int pSize = mDecInfo.mPageStart.get(mPageNo + 1) - pStart;
493 float candMargin = mCandidateMargin + mCandidateMarginExtra;
494 if (mActiveCandInPage > pSize - 1) {
495 mActiveCandInPage = pSize - 1;
496 }
497
498 mCandRects.removeAllElements();
499
500 float xPos = mPaddingLeft;
501 int yPos = (getMeasuredHeight() -
502 (mFmiCandidates.bottom - mFmiCandidates.top)) / 2
503 - mFmiCandidates.top;
//绘制每页之间的分隔符
504 xPos += drawVerticalSeparator(canvas, xPos);
//循环获取每一页信息 一个字或者一个词为一页
505 for (int i = 0; i < pSize; i++) {
506 float footnoteSize = 0;
507 String footnote = null;
508 if (mShowFootnote) {
509 footnote = Integer.toString(i + 1);
510 footnoteSize = mFootnotePaint.measureText(footnote);
511 assert (footnoteSize < candMargin);
512 }
513 String cand = mDecInfo.mCandidatesList.get(pStart + i);
514 float candidateWidth = mCandidatesPaint.measureText(cand);
515 float centerOffset = 0;
516 if (candidateWidth < MIN_ITEM_WIDTH) {
517 centerOffset = (MIN_ITEM_WIDTH - candidateWidth) / 2;
518 candidateWidth = MIN_ITEM_WIDTH;
519 }
520
521 float itemTotalWidth = candidateWidth + 2 * candMargin;
522 //绘制选中的页的背景
523 if (mActiveCandInPage == i && mEnableActiveHighlight) {
524 mActiveCellRect.set(xPos, mPaddingTop + 1, xPos
525 + itemTotalWidth, getHeight() - mPaddingBottom - 1);
526 mActiveCellDrawable.setBounds((int) mActiveCellRect.left,
527 (int) mActiveCellRect.top, (int) mActiveCellRect.right,
528 (int) mActiveCellRect.bottom);
529 mActiveCellDrawable.draw(canvas);
530 }
531
532 if (mCandRects.size() < pSize) mCandRects.add(new RectF());
533 mCandRects.elementAt(i).set(xPos - 1, yPos + mFmiCandidates.top,
534 xPos + itemTotalWidth + 1, yPos + mFmiCandidates.bottom);
535 //绘制脚注,实际中footnote为null
536 // Draw footnote
537 if (mShowFootnote) {
538 canvas.drawText(footnote, xPos + (candMargin - footnoteSize)
539 / 2, yPos, mFootnotePaint);
540 }
541
542 // Left margin
543 xPos += candMargin;
544 if (candidateWidth > mContentWidth - xPos - centerOffset) {
545 cand = getLimitedCandidateForDrawing(cand,
546 mContentWidth - xPos - centerOffset);
547 }
//设置选中的词的颜色和普通情况下的颜色
548 if (mActiveCandInPage == i && mEnableActiveHighlight) {
549 mCandidatesPaint.setColor(mActiveCandidateColor);
550 } else {
551 mCandidatesPaint.setColor(mNormalCandidateColor);
552 }
//绘制中文区域的字符位置在xPos + centerOffset和 ypos
553 canvas.drawText(cand, xPos + centerOffset, yPos,
554 mCandidatesPaint);
555
556 // Candidate and right margin
557 xPos += candidateWidth + candMargin;
558
559 // Draw the separator between candidates.
//绘制分隔符
560 xPos += drawVerticalSeparator(canvas, xPos);
561 }
562
563 // Update the arrow status of the container.
564 if (null != mArrowUpdater && mUpdateArrowStatusWhenDraw) {
565 mArrowUpdater.updateArrowStatus();
566 mUpdateArrowStatusWhenDraw = false;
567 }
568 }
从上面的绘制的代码可以知道如下元素:
中文区域的字体的颜色分为mActiveCandidateColor和mNormalCandidateColor一个代表选中的字体的颜色一个代表普通字体的颜色
//选中的字符页的背景
238 mActiveCellDrawable = r.getDrawable(R.drawable.candidate_hl_bg);
//字符页之间的分隔符
239 mSeparatorDrawable = r.getDrawable(R.drawable.candidates_vertical_line);
//CandidatesView的左边距
240 mCandidateMargin = r.getDimension(R.dimen.candidate_margin_left_right);
241 //普通字符的颜色
242 mImeCandidateColor = r.getColor(R.color.candidate_color);
//推荐字符颜色
243 mRecommendedCandidateColor = r.getColor(R.color.recommended_candidate_color);
244 mNormalCandidateColor = mImeCandidateColor;
//选中字符的颜色
245 mActiveCandidateColor = r.getColor(R.color.active_candidate_color);
另外字符大小的设置代码如下:
363 private void onSizeChanged() {
364 mContentWidth = getMeasuredWidth() - mPaddingLeft - mPaddingRight;
365 mContentHeight = (int) ((getMeasuredHeight() - mPaddingTop - mPaddingBottom) * 0.95f);
366 /**
367 * How to decide the font size if the height for display is given?
368 * Now it is implemented in a stupid way.
369 */
370 int textSize = 1;
371 mCandidatesPaint.setTextSize(textSize);
372 mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
//字体大小的递增计算的,基本填满了高度
373 while (mFmiCandidates.bottom - mFmiCandidates.top < mContentHeight) {
374 textSize++;
375 mCandidatesPaint.setTextSize(textSize);
376 mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
377 }
378 //apk使用的字体的大小 所以可以将下面这个textSize乘以一个系数即可缩小字体的大小
379 mImeCandidateTextSize = textSize;
//推荐使用字体的大小
380 mRecommendedCandidateTextSize = textSize * 3 / 4;
381 if (null == mDecInfo) {
382 mCandidateTextSize = mImeCandidateTextSize;
383 mCandidatesPaint.setTextSize(mCandidateTextSize);
384 mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
385 mSuspensionPointsWidth =
386 mCandidatesPaint.measureText(SUSPENSION_POINTS);
387 } else {
388 // Reset the decoding information to update members for painting.
389 setDecodingInfo(mDecInfo);
390 }
391
392 textSize = 1;
393 mFootnotePaint.setTextSize(textSize);
394 mFmiFootnote = mFootnotePaint.getFontMetricsInt();
395 while (mFmiFootnote.bottom - mFmiFootnote.top < mContentHeight / 2) {
396 textSize++;
397 mFootnotePaint.setTextSize(textSize);
398 mFmiFootnote = mFootnotePaint.getFontMetricsInt();
399 }
400 textSize--;
401 mFootnotePaint.setTextSize(textSize);
402 mFmiFootnote = mFootnotePaint.getFontMetricsInt();
403
404 // When the size is changed, the first page will be displayed.
405 mPageNo = 0;
406 mActiveCandInPage = 0;
407 }
所以要调整中文区域的字体的大小在onSizeChange中改变mCandidateTextSize的值
5.添加退出输入法的按钮
由于输入法是点击back键收起的输入法,但是我们的平台没有back键,所以需要添加一个键来做收起的动作,我这边是将这个键添加到了上面这个candidates_container中,所以这样做必须要CandidatesWindow一直显示,当CandidateView不在时会遮挡内容看起来不美观,所以可以做这样一个处理,当CandidateView和左右箭头不在时将这个layout的背景设置成透明的即可如下:
/packages/inputmethods/PinyinIME/src/com/android/inputmethod/pinyin/CandidatesContainer.java
358 private void showArrow(ImageButton arrowBtn, boolean show) {
359 if (show){
360 arrowBtn.setVisibility(View.VISIBLE);
+ this.getBackground().setAlpha(255);
} else {
arrowBtn.setVisibility(View.INVISIBLE);
+ this.getBackground().setAlpha(0);
}
362
363 }
要使CandidatesWindow一直显示可以做以下改动:
/packages/inputmethods/PinyinIME/src/com/android/inputmethod/pinyin/PinyinIME.java
1118 @Override
1119 public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
1120 if (mEnvironment.needDebug()) {
1121 Log.d(TAG, "onStartInputView " + " contentType: "
1122 + String.valueOf(editorInfo.inputType) + " Restarting:"
1123 + String.valueOf(restarting));
1124 }
1125 updateIcon(mInputModeSwitcher.requestInputWithSkb(editorInfo));
1126 resetToIdleState(false);
1127 mSkbContainer.updateInputMode();
//下面这个就是键盘开启时CandidatesView是否要开启,我们将之开启即可
1128 setCandidatesViewShown(true);
1129 }
另外就是实现收起键盘的功能,可以借用左右箭头的处理:可以直接调用PinyinIME的 requestHideSelf(0)即可将键盘收起但是CandidatesContainer没有持有PinyinIME的引用,所以还需要将PinyinIME service的引用在创建CandidatesContainer传给CandidatesContainer接着CandidatesContainer在收到按钮的点击事件后直接调用PinyinIME的 requestHideSelf(0)即可:
/packages/inputmethods/PinyinIME/src/com/android/inputmethod/pinyin/CandidatesContainer.java
365 public boolean onTouch(View v, MotionEvent event) {
366 if (event.getAction() == MotionEvent.ACTION_DOWN) {
367 if (v == mLeftArrowBtn) {
368 mCvListener.onToRightGesture();
369 } else if (v == mRightArrowBtn) {
370 mCvListener.onToLeftGesture();
371 }
372 } else if (event.getAction() == MotionEvent.ACTION_UP) {
373 CandidateView cv = (CandidateView) mFlipper.getCurrentView();
374 cv.enableActiveHighlight(true);
+ if(v==mDownArrowBtn){
+ if(mService!=null){
+ mService.requestHideSelf(0);
+ }
+ }
375 }
376
377 return false;
378 }
6.设置默认输入法
在初始化时会从setting中读取默认输入法,所以我们可以分成以下几步:
1.首先frameworks\base\packages\SettingsProvider\res\values\defaults.xml 增加下面语句
com.android.inputmethod.Pinyin/.PinyinIME
2.将其加入到数据库中可以用和其他默认值类似的方法:
在/frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java中有一个函数loadGlobalSettings可以利用这个函数为default_input_method设置初始值:
在loadGlobalSettings加入下面这句函数:
loadStringSetting(stmt, Settings.Secure.ENABLED_INPUT_METHODS, R.string.def_input_method);