Android4.3 Google Pinyin输入法UI定制

Android4.3 Google Pinyin输入法UI定制

先来看原版输入法的效果如下:

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7lpm1gMy-1643352211535)(D:\文档\AndroidInput系统\InputMethod\image\原版竖屏.jpg)]

定制后的效果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8xjBRf70-1643352211536)(D:\文档\AndroidInput系统\InputMethod\image\定制版.png)]

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软键盘如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c4gzgCG1-1643352211536)(D:\文档\AndroidInput系统\InputMethod\image\区域说明.png)]

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);

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值