最近一段时间,公司要开发安卓输入法,看了几款开源的例子,其中对 PinyinIME 整篇阅读,并进行了相应的注释,现在把注释的代码发上来,和大家分享学习。

下载改过包名并进行过注释的PinyinIME: http://download.csdn.net/detail/keanbin/6656347

下载没有改过包名的PinyinIME(也进行了代码注释): http://download.csdn.net/detail/keanbin/6656395

下载原装的PinyinIME: http://download.csdn.net/detail/keanbin/6656443

1、BalloonHint.java :

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.Gravity;
import android.view.View;
import android.view.View.MeasureSpec;
import android.widget.PopupWindow;/*** Subclass of PopupWindow used as the feedback when user presses on a soft key* or a candidate. 气泡对话框*/
public class BalloonHint extends PopupWindow {/*** Delayed time to show the balloon hint. 延时多长时间显示*/public static final int TIME_DELAY_SHOW = 0;/*** Delayed time to dismiss the balloon hint. 延时多长时间消失*/public static final int TIME_DELAY_DISMISS = 200;/*** The padding information of the balloon. Because PopupWindow's background* can not be changed unless it is dismissed and shown again, we set the* real background drawable to the content view, and make the PopupWindow's* background transparent. So actually this padding information is for the* content view.*/private Rect mPaddingRect = new Rect();/*** The context used to create this balloon hint object.*/private Context mContext;/*** Parent used to show the balloon window.*/private View mParent;/*** The content view of the balloon. 气泡View*/BalloonView mBalloonView;/*** The measuring specification used to determine its size. Key-press* balloons and candidates balloons have different measuring specifications.* 按键气泡和候选词气泡有不同的测量模式。*/private int mMeasureSpecMode;/*** Used to indicate whether the balloon needs to be dismissed forcibly.* 气泡是否需要强行销毁。*/private boolean mForceDismiss;/*** Timer used to show/dismiss the balloon window with some time delay.* 气泡显示和销毁的定时器*/private BalloonTimer mBalloonTimer;private int mParentLocationInWindow[] = new int[2];public BalloonHint(Context context, View parent, int measureSpecMode) {super(context);mParent = parent;mMeasureSpecMode = measureSpecMode;setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);setTouchable(false);setBackgroundDrawable(new ColorDrawable(0));mBalloonView = new BalloonView(context);mBalloonView.setClickable(false);setContentView(mBalloonView);mBalloonTimer = new BalloonTimer();}public Context getContext() {return mContext;}public Rect getPadding() {return mPaddingRect;}public void setBalloonBackground(Drawable drawable) {// We usually pick up a background from a soft keyboard template,// and the object may has been set to this balloon before.if (mBalloonView.getBackground() == drawable) {return;}mBalloonView.setBackgroundDrawable(drawable);if (null != drawable) {drawable.getPadding(mPaddingRect);} else {mPaddingRect.set(0, 0, 0, 0);}}/*** Set configurations to show text label in this balloon.* * @param label*            The text label to show in the balloon.* @param textSize*            The text size used to show label.* @param textBold*            Used to indicate whether the label should be bold.* @param textColor*            The text color used to show label.* @param width*            The desired width of the balloon. The real width is determined*            by the desired width and balloon's measuring specification.* @param height*            The desired width of the balloon. The real width is determined*            by the desired width and balloon's measuring specification.*/public void setBalloonConfig(String label, float textSize,boolean textBold, int textColor, int width, int height) {mBalloonView.setTextConfig(label, textSize, textBold, textColor);setBalloonSize(width, height);}/*** Set configurations to show text label in this balloon.* * @param icon*            The icon used to shown in this balloon.* @param width*            The desired width of the balloon. The real width is determined*            by the desired width and balloon's measuring specification.* @param height*            The desired width of the balloon. The real width is determined*            by the desired width and balloon's measuring specification.*/public void setBalloonConfig(Drawable icon, int width, int height) {mBalloonView.setIcon(icon);setBalloonSize(width, height);}public boolean needForceDismiss() {return mForceDismiss;}public int getPaddingLeft() {return mPaddingRect.left;}public int getPaddingTop() {return mPaddingRect.top;}public int getPaddingRight() {return mPaddingRect.right;}public int getPaddingBottom() {return mPaddingRect.bottom;}/*** 延时显示气泡* * @param delay*            延时的时间* @param locationInParent*            气泡显示的位置,相对于父视图*/public void delayedShow(long delay, int locationInParent[]) {if (mBalloonTimer.isPending()) {mBalloonTimer.removeTimer();}if (delay <= 0) {mParent.getLocationInWindow(mParentLocationInWindow);showAtLocation(mParent, Gravity.LEFT | Gravity.TOP,locationInParent[0], locationInParent[1]+ mParentLocationInWindow[1]);} else {mBalloonTimer.startTimer(delay, BalloonTimer.ACTION_SHOW,locationInParent, -1, -1);}}/*** 延时更新气泡* * @param delay*            延时的时间* @param locationInParent*            气泡显示的位置,相对于父视图*/public void delayedUpdate(long delay, int locationInParent[], int width,int height) {mBalloonView.invalidate();if (mBalloonTimer.isPending()) {mBalloonTimer.removeTimer();}if (delay <= 0) {mParent.getLocationInWindow(mParentLocationInWindow);update(locationInParent[0], locationInParent[1]+ mParentLocationInWindow[1], width, height);} else {mBalloonTimer.startTimer(delay, BalloonTimer.ACTION_UPDATE,locationInParent, width, height);}}/*** 气泡延时消失* * @param delay*/public void delayedDismiss(long delay) {if (mBalloonTimer.isPending()) {mBalloonTimer.removeTimer();int pendingAction = mBalloonTimer.getAction();if (0 != delay && BalloonTimer.ACTION_HIDE != pendingAction) {mBalloonTimer.run();}}if (delay <= 0) {dismiss();} else {mBalloonTimer.startTimer(delay, BalloonTimer.ACTION_HIDE, null, -1,-1);}}public void removeTimer() {if (mBalloonTimer.isPending()) {mBalloonTimer.removeTimer();}}private void setBalloonSize(int width, int height) {int widthMeasureSpec = MeasureSpec.makeMeasureSpec(width,mMeasureSpecMode);int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,mMeasureSpecMode);mBalloonView.measure(widthMeasureSpec, heightMeasureSpec);int oldWidth = getWidth();int oldHeight = getHeight();int newWidth = mBalloonView.getMeasuredWidth() + getPaddingLeft()+ getPaddingRight();int newHeight = mBalloonView.getMeasuredHeight() + getPaddingTop()+ getPaddingBottom();setWidth(newWidth);setHeight(newHeight);// If update() is called to update both size and position, the system// will first MOVE the PopupWindow to the new position, and then// perform a size-updating operation, so there will be a flash in// PopupWindow if user presses a key and moves finger to next one whose// size is different.// PopupWindow will handle the updating issue in one go in the future,// but before that, if we find the size is changed, a mandatory dismiss// operation is required. In our UI design, normal QWERTY keys' width// can be different in 1-pixel, and we do not dismiss the balloon when// user move between QWERTY keys.// 调用update()去更新位置和大小,系统会先移动对话框到新的位置,然后再去更新大小,所以如果需要更新大小,那么我们就需要先强制去销毁它,再去显示。mForceDismiss = false;if (isShowing()) {mForceDismiss = oldWidth - newWidth > 1 || newWidth - oldWidth > 1;}}private class BalloonTimer extends Handler implements Runnable {public static final int ACTION_SHOW = 1;public static final int ACTION_HIDE = 2;public static final int ACTION_UPDATE = 3;/*** The pending action.*/private int mAction;private int mPositionInParent[] = new int[2];private int mWidth;private int mHeight;private boolean mTimerPending = false;public void startTimer(long time, int action, int positionInParent[],int width, int height) {mAction = action;if (ACTION_HIDE != action) {mPositionInParent[0] = positionInParent[0];mPositionInParent[1] = positionInParent[1];}mWidth = width;mHeight = height;postDelayed(this, time);mTimerPending = true;}public boolean isPending() {return mTimerPending;}public boolean removeTimer() {if (mTimerPending) {mTimerPending = false;removeCallbacks(this);return true;}return false;}public int getAction() {return mAction;}public void run() {switch (mAction) {case ACTION_SHOW:mParent.getLocationInWindow(mParentLocationInWindow);showAtLocation(mParent, Gravity.LEFT | Gravity.TOP,mPositionInParent[0], mPositionInParent[1]+ mParentLocationInWindow[1]);break;case ACTION_HIDE:dismiss();break;case ACTION_UPDATE:mParent.getLocationInWindow(mParentLocationInWindow);update(mPositionInParent[0], mPositionInParent[1]+ mParentLocationInWindow[1], mWidth, mHeight);}mTimerPending = false;}}/*** 气泡View* * @author keanbin* */private class BalloonView extends View {/*** Suspension points used to display long items.*/private static final String SUSPENSION_POINTS = "...";/*** The icon to be shown. If it is not null, {@link #mLabel} will be* ignored. mIcon 如果不为空,mLabel就被忽略。*/private Drawable mIcon;/*** The label to be shown. It is enabled only if {@link #mIcon} is null.*/private String mLabel;private int mLabeColor = 0xff000000;private Paint mPaintLabel;private FontMetricsInt mFmi; // 字体整形/尺寸/*** The width to show suspension points. 省略号的宽度*/private float mSuspensionPointsWidth;public BalloonView(Context context) {super(context);mPaintLabel = new Paint();mPaintLabel.setColor(mLabeColor);mPaintLabel.setAntiAlias(true);mPaintLabel.setFakeBoldText(true);mFmi = mPaintLabel.getFontMetricsInt();}public void setIcon(Drawable icon) {mIcon = icon;}public void setTextConfig(String label, float fontSize,boolean textBold, int textColor) {// Icon should be cleared so that the label will be enabled.mIcon = null;mLabel = label;mPaintLabel.setTextSize(fontSize);mPaintLabel.setFakeBoldText(textBold);mPaintLabel.setColor(textColor);mFmi = mPaintLabel.getFontMetricsInt();mSuspensionPointsWidth = mPaintLabel.measureText(SUSPENSION_POINTS);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 取出测量的模式final int widthMode = MeasureSpec.getMode(widthMeasureSpec);final int heightMode = MeasureSpec.getMode(heightMeasureSpec);// 取出测量的宽度高度final int widthSize = MeasureSpec.getSize(widthMeasureSpec);final int heightSize = MeasureSpec.getSize(heightMeasureSpec);if (widthMode == MeasureSpec.EXACTLY) {setMeasuredDimension(widthSize, heightSize);return;}// 计算最少需要的尺寸int measuredWidth = getPaddingLeft() + getPaddingRight();int measuredHeight = getPaddingTop() + getPaddingBottom();if (null != mIcon) {measuredWidth += mIcon.getIntrinsicWidth();measuredHeight += mIcon.getIntrinsicHeight();} else if (null != mLabel) {measuredWidth += (int) (mPaintLabel.measureText(mLabel));measuredHeight += mFmi.bottom - mFmi.top;}if (widthSize > measuredWidth || widthMode == MeasureSpec.AT_MOST) {measuredWidth = widthSize;}if (heightSize > measuredHeight|| heightMode == MeasureSpec.AT_MOST) {measuredHeight = heightSize;}// TODO// measuredWidth不是包含getPaddingLeft()和getPaddingRight()吗?怎么屏幕宽度还需要再减去它们?int maxWidth = Environment.getInstance().getScreenWidth()- getPaddingLeft() - getPaddingRight();if (measuredWidth > maxWidth) {measuredWidth = maxWidth;}// 设置尺寸setMeasuredDimension(measuredWidth, measuredHeight);}@Overrideprotected void onDraw(Canvas canvas) {int width = getWidth();int height = getHeight();if (null != mIcon) {int marginLeft = (width - mIcon.getIntrinsicWidth()) / 2;int marginRight = width - mIcon.getIntrinsicWidth()- marginLeft;int marginTop = (height - mIcon.getIntrinsicHeight()) / 2;int marginBottom = height - mIcon.getIntrinsicHeight()- marginTop;mIcon.setBounds(marginLeft, marginTop, width - marginRight,height - marginBottom);mIcon.draw(canvas);} else if (null != mLabel) {float labelMeasuredWidth = mPaintLabel.measureText(mLabel);float x = getPaddingLeft();x += (width - labelMeasuredWidth - getPaddingLeft() - getPaddingRight()) / 2.0f;String labelToDraw = mLabel;if (x < getPaddingLeft()) {// 区域不够显示,显示短语+省略号x = getPaddingLeft();labelToDraw = getLimitedLabelForDrawing(mLabel, width- getPaddingLeft() - getPaddingRight());}int fontHeight = mFmi.bottom - mFmi.top;float marginY = (height - fontHeight) / 2.0f;float y = marginY - mFmi.top;canvas.drawText(labelToDraw, x, y, mPaintLabel);}}/*** 显示的文本过长,截取适合的短语+省略号* * @param rawLabel* @param widthToDraw* @return*/private String getLimitedLabelForDrawing(String rawLabel,float widthToDraw) {int subLen = rawLabel.length();if (subLen <= 1)return rawLabel;do {subLen--;float width = mPaintLabel.measureText(rawLabel, 0, subLen);if (width + mSuspensionPointsWidth <= widthToDraw|| 1 >= subLen) {return rawLabel.substring(0, subLen) + SUSPENSION_POINTS;}} while (true);}}
}

2、CandidatesContainer.java

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationSet;
import android.view.animation.TranslateAnimation;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.ViewFlipper;import com.keanbin.pinyinime.PinyinIME.DecodingInfo;/*** 集装箱中的箭头更新监听器* * @ClassName ArrowUpdater* @author keanbin*/
interface ArrowUpdater {void updateArrowStatus();
}/*** Container used to host the two candidate views. When user drags on candidate* view, animation is used to dismiss the current candidate view and show a new* one. These two candidate views and their parent are hosted by this container.* <p>* Besides the candidate views, there are two arrow views to show the page* forward/backward arrows.* </p>*/
/*** 候选词集装箱* * @ClassName CandidatesContainer* @author keanbin*/
public class CandidatesContainer extends RelativeLayout implementsOnTouchListener, AnimationListener, ArrowUpdater {/*** Alpha value to show an enabled arrow. 箭头图片显示时的透明度*/private static int ARROW_ALPHA_ENABLED = 0xff;/*** Alpha value to show an disabled arrow. 箭头图片不显示时的透明度*/private static int ARROW_ALPHA_DISABLED = 0x40;/*** Animation time to show a new candidate view and dismiss the old one.* 显示或者关闭一个候选词View的动画时间*/private static int ANIMATION_TIME = 200;/*** Listener used to notify IME that user clicks a candidate, or navigate* between them. 候选词视图监听器*/private CandidateViewListener mCvListener;/*** The left arrow button used to show previous page. 左边箭头按钮*/private ImageButton mLeftArrowBtn;/*** The right arrow button used to show next page. 右边箭头按钮*/private ImageButton mRightArrowBtn;/*** Decoding result to show. 词库解码对象*/private DecodingInfo mDecInfo;/*** The animation view used to show candidates. It contains two views.* Normally, the candidates are shown one of them. When user navigates to* another page, animation effect will be performed.* ViewFlipper页面管理,它包含两个视图,正常只显示其中一个,当切换候选词页的时候,就启动另一个视图装载接着要显示的候选词切入进来。*/private ViewFlipper mFlipper;/*** The x offset of the flipper in this container. ViewFlipper 在集装箱的偏移位置。*/private int xOffsetForFlipper;/*** Animation used by the incoming view when the user navigates to a left* page. 传入页面移动向左边的动画*/private Animation mInAnimPushLeft;/*** Animation used by the incoming view when the user navigates to a right* page. 传入页面移动向右边的动画*/private Animation mInAnimPushRight;/*** Animation used by the incoming view when the user navigates to a page* above. If the page navigation is triggered by DOWN key, this animation is* used. 传入页面移动向上的动画*/private Animation mInAnimPushUp;/*** Animation used by the incoming view when the user navigates to a page* below. If the page navigation is triggered by UP key, this animation is* used. 传入页面移动向下的动画*/private Animation mInAnimPushDown;/*** Animation used by the outgoing view when the user navigates to a left* page. 传出页面移动向左边的动画*/private Animation mOutAnimPushLeft;/*** Animation used by the outgoing view when the user navigates to a right* page.传出页面移动向右边的动画*/private Animation mOutAnimPushRight;/*** Animation used by the outgoing view when the user navigates to a page* above. If the page navigation is triggered by DOWN key, this animation is* used.传出页面移动向上边的动画*/private Animation mOutAnimPushUp;/*** Animation used by the incoming view when the user navigates to a page* below. If the page navigation is triggered by UP key, this animation is* used.传出页面移动向下边的动画*/private Animation mOutAnimPushDown;/*** Animation object which is used for the incoming view currently.* 传入页面当前使用的动画*/private Animation mInAnimInUse;/*** Animation object which is used for the outgoing view currently.* 传出页面当前使用的动画*/private Animation mOutAnimInUse;/*** Current page number in display. 当前显示的页码*/private int mCurrentPage = -1;public CandidatesContainer(Context context, AttributeSet attrs) {super(context, attrs);}public void initialize(CandidateViewListener cvListener,BalloonHint balloonHint, GestureDetector gestureDetector) {mCvListener = cvListener;mLeftArrowBtn = (ImageButton) findViewById(R.id.arrow_left_btn);mRightArrowBtn = (ImageButton) findViewById(R.id.arrow_right_btn);mLeftArrowBtn.setOnTouchListener(this);mRightArrowBtn.setOnTouchListener(this);mFlipper = (ViewFlipper) findViewById(R.id.candidate_flipper);mFlipper.setMeasureAllChildren(true);invalidate();requestLayout();for (int i = 0; i < mFlipper.getChildCount(); i++) {CandidateView cv = (CandidateView) mFlipper.getChildAt(i);cv.initialize(this, balloonHint, gestureDetector, mCvListener);}}/*** 显示候选词* * @param decInfo* @param enableActiveHighlight*/public void showCandidates(PinyinIME.DecodingInfo decInfo,boolean enableActiveHighlight) {if (null == decInfo)return;mDecInfo = decInfo;mCurrentPage = 0;if (decInfo.isCandidatesListEmpty()) {showArrow(mLeftArrowBtn, false);showArrow(mRightArrowBtn, false);} else {showArrow(mLeftArrowBtn, true);showArrow(mRightArrowBtn, true);}for (int i = 0; i < mFlipper.getChildCount(); i++) {CandidateView cv = (CandidateView) mFlipper.getChildAt(i);cv.setDecodingInfo(mDecInfo);}stopAnimation();CandidateView cv = (CandidateView) mFlipper.getCurrentView();cv.showPage(mCurrentPage, 0, enableActiveHighlight);updateArrowStatus();invalidate();}/*** 获取当前的页码* * @return*/public int getCurrentPage() {return mCurrentPage;}/*** 设置候选词是否高亮* * @param enableActiveHighlight*/public void enableActiveHighlight(boolean enableActiveHighlight) {CandidateView cv = (CandidateView) mFlipper.getCurrentView();cv.enableActiveHighlight(enableActiveHighlight);invalidate();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {Environment env = Environment.getInstance();int measuredWidth = env.getScreenWidth();int measuredHeight = getPaddingTop();measuredHeight += env.getHeightForCandidates();widthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth,MeasureSpec.EXACTLY);heightMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight,MeasureSpec.EXACTLY);super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (null != mLeftArrowBtn) {// 设置候选词所在的 ViewFlipper 在集装箱中的偏移位置xOffsetForFlipper = mLeftArrowBtn.getMeasuredWidth();}}/*** 高亮位置向上一个候选词移动或者移动到上一页的最后一个候选词的位置。* * @return*/public boolean activeCurseBackward() {if (mFlipper.isFlipping() || null == mDecInfo) {return false;}CandidateView cv = (CandidateView) mFlipper.getCurrentView();if (cv.activeCurseBackward()) {cv.invalidate();return true;} else {return pageBackward(true, true);}}/*** 高亮位置向下一个候选词移动或者移动到下一页的第一个候选词的位置。* * @return*/public boolean activeCurseForward() {if (mFlipper.isFlipping() || null == mDecInfo) {return false;}CandidateView cv = (CandidateView) mFlipper.getCurrentView();if (cv.activeCursorForward()) {cv.invalidate();return true;} else {return pageForward(true, true);}}/*** 到上一页候选词* * @param animLeftRight*            高亮位置是否到本页最后一个候选词位置* @param enableActiveHighlight* @return*/public boolean pageBackward(boolean animLeftRight,boolean enableActiveHighlight) {if (null == mDecInfo)return false;if (mFlipper.isFlipping() || 0 == mCurrentPage)return false;int child = mFlipper.getDisplayedChild();int childNext = (child + 1) % 2;CandidateView cv = (CandidateView) mFlipper.getChildAt(child);CandidateView cvNext = (CandidateView) mFlipper.getChildAt(childNext);mCurrentPage--;int activeCandInPage = cv.getActiveCandiatePosInPage();if (animLeftRight)activeCandInPage = mDecInfo.mPageStart.elementAt(mCurrentPage + 1)- mDecInfo.mPageStart.elementAt(mCurrentPage) - 1;cvNext.showPage(mCurrentPage, activeCandInPage, enableActiveHighlight);loadAnimation(animLeftRight, false);startAnimation();updateArrowStatus();return true;}/*** 到下一页候选词* * @param animLeftRight*            高亮位置是否到本页第一个候选词位置* @param enableActiveHighlight* @return*/public boolean pageForward(boolean animLeftRight,boolean enableActiveHighlight) {if (null == mDecInfo)return false;if (mFlipper.isFlipping() || !mDecInfo.preparePage(mCurrentPage + 1)) {return false;}int child = mFlipper.getDisplayedChild();int childNext = (child + 1) % 2;CandidateView cv = (CandidateView) mFlipper.getChildAt(child);int activeCandInPage = cv.getActiveCandiatePosInPage();cv.enableActiveHighlight(enableActiveHighlight);CandidateView cvNext = (CandidateView) mFlipper.getChildAt(childNext);mCurrentPage++;if (animLeftRight)activeCandInPage = 0;cvNext.showPage(mCurrentPage, activeCandInPage, enableActiveHighlight);loadAnimation(animLeftRight, true);startAnimation();updateArrowStatus();return true;}/*** 获取活动(高亮)的候选词在所有候选词中的位置* * @return*/public int getActiveCandiatePos() {if (null == mDecInfo)return -1;CandidateView cv = (CandidateView) mFlipper.getCurrentView();return cv.getActiveCandiatePosGlobal();}/*** 更新箭头显示*/public void updateArrowStatus() {if (mCurrentPage < 0)return;boolean forwardEnabled = mDecInfo.pageForwardable(mCurrentPage);boolean backwardEnabled = mDecInfo.pageBackwardable(mCurrentPage);if (backwardEnabled) {enableArrow(mLeftArrowBtn, true);} else {enableArrow(mLeftArrowBtn, false);}if (forwardEnabled) {enableArrow(mRightArrowBtn, true);} else {enableArrow(mRightArrowBtn, false);}}/*** 设置箭头图标是否有效,和图标的透明度。* * @param arrowBtn* @param enabled*/private void enableArrow(ImageButton arrowBtn, boolean enabled) {arrowBtn.setEnabled(enabled);if (enabled)arrowBtn.setAlpha(ARROW_ALPHA_ENABLED);elsearrowBtn.setAlpha(ARROW_ALPHA_DISABLED);}/*** 设置箭头图标是否显示* * @param arrowBtn* @param show*/private void showArrow(ImageButton arrowBtn, boolean show) {if (show)arrowBtn.setVisibility(View.VISIBLE);elsearrowBtn.setVisibility(View.INVISIBLE);}/*** view的触摸事件监听器* * @param v* @param event*/public boolean onTouch(View v, MotionEvent event) {if (event.getAction() == MotionEvent.ACTION_DOWN) {if (v == mLeftArrowBtn) {// 调用候选词视图监听器的向右滑动手势处理函数mCvListener.onToRightGesture();} else if (v == mRightArrowBtn) {// 调用候选词视图监听器的向左滑动手势处理函数mCvListener.onToLeftGesture();}} else if (event.getAction() == MotionEvent.ACTION_UP) {// 设置候选词视图高亮活动的候选词。CandidateView cv = (CandidateView) mFlipper.getCurrentView();cv.enableActiveHighlight(true);}return false;}// The reason why we handle candiate view's touch events here is because// that the view under the focused view may get touch events instead of the// focused one.@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO 触摸事件的坐标点是以哪里为原点?// 调整触摸事件坐标点的坐标event.offsetLocation(-xOffsetForFlipper, 0);// 调用候选词视图触摸事件处理函数CandidateView cv = (CandidateView) mFlipper.getCurrentView();cv.onTouchEventReal(event);return true;}/*** 创建动画,并设置给ViewFlipper mFlipper。* * @param animLeftRight* @param forward*/public void loadAnimation(boolean animLeftRight, boolean forward) {if (animLeftRight) {if (forward) {if (null == mInAnimPushLeft) {mInAnimPushLeft = createAnimation(1.0f, 0, 0, 0, 0, 1.0f,ANIMATION_TIME);mOutAnimPushLeft = createAnimation(0, -1.0f, 0, 0, 1.0f, 0,ANIMATION_TIME);}mInAnimInUse = mInAnimPushLeft;mOutAnimInUse = mOutAnimPushLeft;} else {if (null == mInAnimPushRight) {mInAnimPushRight = createAnimation(-1.0f, 0, 0, 0, 0, 1.0f,ANIMATION_TIME);mOutAnimPushRight = createAnimation(0, 1.0f, 0, 0, 1.0f, 0,ANIMATION_TIME);}mInAnimInUse = mInAnimPushRight;mOutAnimInUse = mOutAnimPushRight;}} else {if (forward) {if (null == mInAnimPushUp) {mInAnimPushUp = createAnimation(0, 0, 1.0f, 0, 0, 1.0f,ANIMATION_TIME);mOutAnimPushUp = createAnimation(0, 0, 0, -1.0f, 1.0f, 0,ANIMATION_TIME);}mInAnimInUse = mInAnimPushUp;mOutAnimInUse = mOutAnimPushUp;} else {if (null == mInAnimPushDown) {mInAnimPushDown = createAnimation(0, 0, -1.0f, 0, 0, 1.0f,ANIMATION_TIME);mOutAnimPushDown = createAnimation(0, 0, 0, 1.0f, 1.0f, 0,ANIMATION_TIME);}mInAnimInUse = mInAnimPushDown;mOutAnimInUse = mOutAnimPushDown;}}// 设置动画监听器,当动画结束的时候,调用onAnimationEnd()。mInAnimInUse.setAnimationListener(this);mFlipper.setInAnimation(mInAnimInUse);mFlipper.setOutAnimation(mOutAnimInUse);}/*** 创建移动动画* * @param xFrom* @param xTo* @param yFrom* @param yTo* @param alphaFrom* @param alphaTo* @param duration* @return*/private Animation createAnimation(float xFrom, float xTo, float yFrom,float yTo, float alphaFrom, float alphaTo, long duration) {AnimationSet animSet = new AnimationSet(getContext(), null);Animation trans = new TranslateAnimation(Animation.RELATIVE_TO_SELF,xFrom, Animation.RELATIVE_TO_SELF, xTo,Animation.RELATIVE_TO_SELF, yFrom, Animation.RELATIVE_TO_SELF,yTo);Animation alpha = new AlphaAnimation(alphaFrom, alphaTo);animSet.addAnimation(trans);animSet.addAnimation(alpha);animSet.setDuration(duration);return animSet;}/*** 开始动画,mFlipper显示下一个。*/private void startAnimation() {mFlipper.showNext();}/*** 停止动画,mFlipper停止切换Flipping。*/private void stopAnimation() {mFlipper.stopFlipping();}/*** 动画监听器:动画停止的时候的监听器回调。*/public void onAnimationEnd(Animation animation) {if (!mLeftArrowBtn.isPressed() && !mRightArrowBtn.isPressed()) {CandidateView cv = (CandidateView) mFlipper.getCurrentView();cv.enableActiveHighlight(true);}}/*** 动画监听器:动画重复的时候的监听器回调。*/public void onAnimationRepeat(Animation animation) {}/*** 动画监听器:动画开始的时候的监听器回调。*/public void onAnimationStart(Animation animation) {}
}

3、CandidateView.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import java.util.Vector;import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;import com.keanbin.pinyinime.PinyinIME.DecodingInfo;/*** View to show candidate list. There two candidate view instances which are* used to show animation when user navigates between pages.*/
/*** 候选词视图* * @ClassName CandidateView* @author keanbin*/
public class CandidateView extends View {/*** The minimum width to show a item. 一个item最小的宽度*/private static final float MIN_ITEM_WIDTH = 22;/*** Suspension points used to display long items. 省略号*/private static final String SUSPENSION_POINTS = "...";/*** The width to draw candidates. 候选词区域的宽度*/private int mContentWidth;/*** The height to draw candidate content. 候选词区域的高度*/private int mContentHeight;/*** Whether footnotes are displayed. Footnote is shown when hardware keyboard* is available. 是否显示附注。附注是当硬键盘有效的时候显示的。*/private boolean mShowFootnote = true;/*** Balloon hint for candidate press/release. 当候选词被按下的时候显示的气泡*/private BalloonHint mBalloonHint;/*** Desired position of the balloon to the input view. 气泡显示的位置*/private int mHintPositionToInputView[] = new int[2];/*** Decoding result to show. 词库解码对象*/private DecodingInfo mDecInfo;/*** Listener used to notify IME that user clicks a candidate, or navigate* between them. 候选词监听器*/private CandidateViewListener mCvListener;/*** Used to notify the container to update the status of forward/backward* arrows. 箭头更新接口。在onDraw()中,当mUpdateArrowStatusWhenDraw为true,* 该接口的updateArrowStatus()方法被调用。因为箭头是放在候选词集装箱中的,不是放在候选词视图中。*/private ArrowUpdater mArrowUpdater;/*** If true, update the arrow status when drawing candidates.* 在onDraw()的时候是否更新箭头*/private boolean mUpdateArrowStatusWhenDraw = false;/*** Page number of the page displayed in this view. 候选词视图显示的页码*/private int mPageNo;/*** Active candidate position in this page. 活动(高亮)的候选词在页面的位置。*/private int mActiveCandInPage;/*** Used to decided whether the active candidate should be highlighted or* not. If user changes focus to composing view (The view to show Pinyin* string), the highlight in candidate view should be removed. 是否高亮活动的候选词*/private boolean mEnableActiveHighlight = true;/*** The page which is just calculated. 刚刚计算的页码*/private int mPageNoCalculated = -1;/*** The Drawable used to display as the background of the high-lighted item.* 高亮显示的图片*/private Drawable mActiveCellDrawable;/*** The Drawable used to display as separators between candidates. 分隔符图片*/private Drawable mSeparatorDrawable;/*** Color to draw normal candidates generated by IME. 正常候选词的颜色,来自输入法词库的候选词。*/private int mImeCandidateColor;/*** Color to draw normal candidates Recommended by application.* 推荐候选词的颜色,推荐的候选词是来自APP的。*/private int mRecommendedCandidateColor;/*** Color to draw the normal(not highlighted) candidates, it can be one of* {@link #mImeCandidateColor} or {@link #mRecommendedCandidateColor}.* 候选词的颜色,它可以是 mImeCandidateColor 和 mRecommendedCandidateColor 其中的一个。*/private int mNormalCandidateColor;/*** Color to draw the active(highlighted) candidates, including candidates* from IME and candidates from application. 高亮候选词的颜色*/private int mActiveCandidateColor;/*** Text size to draw candidates generated by IME. 正常候选词的文本大小,来自输入法词库的候选词。*/private int mImeCandidateTextSize;/*** Text size to draw candidates recommended by application.* 推荐候选词的文本大小,推荐的候选词是来自APP的。*/private int mRecommendedCandidateTextSize;/*** The current text size to draw candidates. It can be one of* {@link #mImeCandidateTextSize} or {@link #mRecommendedCandidateTextSize}.* 候选词的文本大小,它可以是 mImeCandidateTextSize 和 mRecommendedCandidateTextSize* 其中的一个。*/private int mCandidateTextSize;/*** Paint used to draw candidates. 候选词的画笔*/private Paint mCandidatesPaint;/*** Used to draw footnote. 附注的画笔*/private Paint mFootnotePaint;/*** The width to show suspension points. 省略号的宽度*/private float mSuspensionPointsWidth;/*** Rectangle used to draw the active candidate. 活动(高亮)候选词的区域*/private RectF mActiveCellRect;/*** Left and right margins for a candidate. It is specified in xml, and is* the minimum margin for a candidate. The actual gap between two candidates* is 2 * {@link #mCandidateMargin} + {@link #mSeparatorDrawable}.* getIntrinsicWidth(). Because length of candidate is not fixed, there can* be some extra space after the last candidate in the current page. In* order to achieve best look-and-feel, this extra space will be divided and* allocated to each candidates. 候选词的左边和右边间隔*/private float mCandidateMargin;/*** Left and right extra margins for a candidate. 候选词的左边和右边的额外间隔*/private float mCandidateMarginExtra;/*** Rectangles for the candidates in this page. 在本页候选词的区域向量列表**/private Vector<RectF> mCandRects;/*** FontMetricsInt used to measure the size of candidates. 候选词的字体测量对象*/private FontMetricsInt mFmiCandidates;/*** FontMetricsInt used to measure the size of footnotes. 附注的字体测量对象*/private FontMetricsInt mFmiFootnote;/*** 按下某个候选词的定时器。*/private PressTimer mTimer = new PressTimer();/*** 手势识别对象*/private GestureDetector mGestureDetector;/*** 临时位置信息*/private int mLocationTmp[] = new int[2];public CandidateView(Context context, AttributeSet attrs) {super(context, attrs);Resources r = context.getResources();// 判断是否显示附注Configuration conf = r.getConfiguration();if (conf.keyboard == Configuration.KEYBOARD_NOKEYS|| conf.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {mShowFootnote = false;}mActiveCellDrawable = r.getDrawable(R.drawable.candidate_hl_bg);mSeparatorDrawable = r.getDrawable(R.drawable.candidates_vertical_line);mCandidateMargin = r.getDimension(R.dimen.candidate_margin_left_right);mImeCandidateColor = r.getColor(R.color.candidate_color);mRecommendedCandidateColor = r.getColor(R.color.recommended_candidate_color);mNormalCandidateColor = mImeCandidateColor;mActiveCandidateColor = r.getColor(R.color.active_candidate_color);mCandidatesPaint = new Paint();mCandidatesPaint.setAntiAlias(true);mFootnotePaint = new Paint();mFootnotePaint.setAntiAlias(true);mFootnotePaint.setColor(r.getColor(R.color.footnote_color));mActiveCellRect = new RectF();mCandRects = new Vector<RectF>();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int mOldWidth = getMeasuredWidth();int mOldHeight = getMeasuredHeight();setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));if (mOldWidth != getMeasuredWidth()|| mOldHeight != getMeasuredHeight()) {onSizeChanged();}}/*** 初始化。* * @param arrowUpdater* @param balloonHint* @param gestureDetector* @param cvListener*/public void initialize(ArrowUpdater arrowUpdater, BalloonHint balloonHint,GestureDetector gestureDetector, CandidateViewListener cvListener) {mArrowUpdater = arrowUpdater;mBalloonHint = balloonHint;mGestureDetector = gestureDetector;mCvListener = cvListener;}/*** 根据候选词的来源设置候选词使用的颜色和文本大小,并计算省略号的宽度。* * @param decInfo*/public void setDecodingInfo(DecodingInfo decInfo) {if (null == decInfo)return;mDecInfo = decInfo;mPageNoCalculated = -1;// 根据候选词来源设置候选词使用的颜色和文本大小if (mDecInfo.candidatesFromApp()) {mNormalCandidateColor = mRecommendedCandidateColor;mCandidateTextSize = mRecommendedCandidateTextSize;} else {mNormalCandidateColor = mImeCandidateColor;mCandidateTextSize = mImeCandidateTextSize;}if (mCandidatesPaint.getTextSize() != mCandidateTextSize) {// 计算省略号宽度mCandidatesPaint.setTextSize(mCandidateTextSize);mFmiCandidates = mCandidatesPaint.getFontMetricsInt();mSuspensionPointsWidth = mCandidatesPaint.measureText(SUSPENSION_POINTS);}// Remove any pending timer for the previous list.mTimer.removeTimer();}/*** 获取活动(高亮)的候选词在页面的位置。* * @return*/public int getActiveCandiatePosInPage() {return mActiveCandInPage;}/*** 获取活动(高亮)的候选词在所有候选词中的位置* * @return*/public int getActiveCandiatePosGlobal() {return mDecInfo.mPageStart.get(mPageNo) + mActiveCandInPage;}/*** Show a page in the decoding result set previously.* * @param pageNo*            Which page to show.* @param activeCandInPage*            Which candidate should be set as active item.* @param enableActiveHighlight*            When false, active item will not be highlighted.*//*** 显示指定页的候选词* * @param pageNo* @param activeCandInPage* @param enableActiveHighlight*/public void showPage(int pageNo, int activeCandInPage,boolean enableActiveHighlight) {if (null == mDecInfo)return;mPageNo = pageNo;mActiveCandInPage = activeCandInPage;if (mEnableActiveHighlight != enableActiveHighlight) {mEnableActiveHighlight = enableActiveHighlight;}if (!calculatePage(mPageNo)) {mUpdateArrowStatusWhenDraw = true;} else {mUpdateArrowStatusWhenDraw = false;}invalidate();}/*** 设置是否高亮候选词* * @param enableActiveHighlight*/public void enableActiveHighlight(boolean enableActiveHighlight) {if (enableActiveHighlight == mEnableActiveHighlight)return;mEnableActiveHighlight = enableActiveHighlight;invalidate();}/*** 高亮位置向下一个候选词移动。* * @return*/public boolean activeCursorForward() {if (!mDecInfo.pageReady(mPageNo))return false;int pageSize = mDecInfo.mPageStart.get(mPageNo + 1)- mDecInfo.mPageStart.get(mPageNo);if (mActiveCandInPage + 1 < pageSize) {showPage(mPageNo, mActiveCandInPage + 1, true);return true;}return false;}/*** 高亮位置向上一个候选词移动。* * @return*/public boolean activeCurseBackward() {if (mActiveCandInPage > 0) {showPage(mPageNo, mActiveCandInPage - 1, true);return true;}return false;}/*** 计算候选词区域的宽度和高度、候选词文本大小、附注文本大小、省略号宽度。当尺寸发生改变时调用。在onMeasure()中调用。*/private void onSizeChanged() {// 计算候选词区域的宽度和高度mContentWidth = getMeasuredWidth() - getPaddingLeft()- getPaddingRight();mContentHeight = (int) ((getMeasuredHeight() - getPaddingTop() - getPaddingBottom()) * 0.95f);/*** How to decide the font size if the height for display is given? Now* it is implemented in a stupid way.*/// 根据候选词区域高度来计算候选词应该使用的文本大小int textSize = 1;mCandidatesPaint.setTextSize(textSize);mFmiCandidates = mCandidatesPaint.getFontMetricsInt();while (mFmiCandidates.bottom - mFmiCandidates.top < mContentHeight) {textSize++;mCandidatesPaint.setTextSize(textSize);mFmiCandidates = mCandidatesPaint.getFontMetricsInt();}// 设置计算出的候选词文本大小mImeCandidateTextSize = textSize;mRecommendedCandidateTextSize = textSize * 3 / 4;if (null == mDecInfo) {// 计算省略号的宽度mCandidateTextSize = mImeCandidateTextSize;mCandidatesPaint.setTextSize(mCandidateTextSize);mFmiCandidates = mCandidatesPaint.getFontMetricsInt();mSuspensionPointsWidth = mCandidatesPaint.measureText(SUSPENSION_POINTS);} else {// Reset the decoding information to update members for painting.setDecodingInfo(mDecInfo);}// 计算附注文本的大小textSize = 1;mFootnotePaint.setTextSize(textSize);mFmiFootnote = mFootnotePaint.getFontMetricsInt();while (mFmiFootnote.bottom - mFmiFootnote.top < mContentHeight / 2) {textSize++;mFootnotePaint.setTextSize(textSize);mFmiFootnote = mFootnotePaint.getFontMetricsInt();}textSize--;mFootnotePaint.setTextSize(textSize);mFmiFootnote = mFootnotePaint.getFontMetricsInt();// When the size is changed, the first page will be displayed.mPageNo = 0;mActiveCandInPage = 0;}/*** 对还没有分页的候选词进行分页,计算指定页的候选词左右的额外间隔。* * @param pageNo* @return*/private boolean calculatePage(int pageNo) {if (pageNo == mPageNoCalculated)return true;// 计算候选词区域宽度和高度mContentWidth = getMeasuredWidth() - getPaddingLeft()- getPaddingRight();mContentHeight = (int) ((getMeasuredHeight() - getPaddingTop() - getPaddingBottom()) * 0.95f);if (mContentWidth <= 0 || mContentHeight <= 0)return false;// 候选词列表的size,即候选词的数量。int candSize = mDecInfo.mCandidatesList.size();// If the size of page exists, only calculate the extra margin.boolean onlyExtraMargin = false;int fromPage = mDecInfo.mPageStart.size() - 1;if (mDecInfo.mPageStart.size() > pageNo + 1) {// pageNo是最后一页之前的页码,不包括最后一页onlyExtraMargin = true;fromPage = pageNo;}// If the previous pages have no information, calculate them first.for (int p = fromPage; p <= pageNo; p++) {int pStart = mDecInfo.mPageStart.get(p);int pSize = 0;int charNum = 0;float lastItemWidth = 0;float xPos;xPos = 0;xPos += mSeparatorDrawable.getIntrinsicWidth();while (xPos < mContentWidth && pStart + pSize < candSize) {int itemPos = pStart + pSize;String itemStr = mDecInfo.mCandidatesList.get(itemPos);float itemWidth = mCandidatesPaint.measureText(itemStr);if (itemWidth < MIN_ITEM_WIDTH)itemWidth = MIN_ITEM_WIDTH;itemWidth += mCandidateMargin * 2;itemWidth += mSeparatorDrawable.getIntrinsicWidth();if (xPos + itemWidth < mContentWidth || 0 == pSize) {xPos += itemWidth;lastItemWidth = itemWidth;pSize++;charNum += itemStr.length();} else {break;}}if (!onlyExtraMargin) {// pageNo是最后一页或者往后的一页,这里应该就是对候选词进行分页的地方,保证每页候选词都能正常显示。mDecInfo.mPageStart.add(pStart + pSize);mDecInfo.mCnToPage.add(mDecInfo.mCnToPage.get(p) + charNum);}// 计算候选词的左右间隔float marginExtra = (mContentWidth - xPos) / pSize / 2;if (mContentWidth - xPos > lastItemWidth) {// Must be the last page, because if there are more items,// the next item's width must be less than lastItemWidth.// In this case, if the last margin is less than the current// one, the last margin can be used, so that the// look-and-feeling will be the same as the previous page.if (mCandidateMarginExtra <= marginExtra) {marginExtra = mCandidateMarginExtra;}} else if (pSize == 1) {marginExtra = 0;}mCandidateMarginExtra = marginExtra;}mPageNoCalculated = pageNo;return true;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// The invisible candidate view(the one which is not in foreground) can// also be called to drawn, but its decoding result and candidate list// may be empty.if (null == mDecInfo || mDecInfo.isCandidatesListEmpty())return;// Calculate page. If the paging information is ready, the function will// return at once.calculatePage(mPageNo);int pStart = mDecInfo.mPageStart.get(mPageNo);int pSize = mDecInfo.mPageStart.get(mPageNo + 1) - pStart;float candMargin = mCandidateMargin + mCandidateMarginExtra;if (mActiveCandInPage > pSize - 1) {mActiveCandInPage = pSize - 1;}mCandRects.removeAllElements();float xPos = getPaddingLeft();int yPos = (getMeasuredHeight() - (mFmiCandidates.bottom - mFmiCandidates.top))/ 2 - mFmiCandidates.top;xPos += drawVerticalSeparator(canvas, xPos);for (int i = 0; i < pSize; i++) {float footnoteSize = 0;String footnote = null;if (mShowFootnote) {footnote = Integer.toString(i + 1);footnoteSize = mFootnotePaint.measureText(footnote);assert (footnoteSize < candMargin);}String cand = mDecInfo.mCandidatesList.get(pStart + i);float candidateWidth = mCandidatesPaint.measureText(cand);float centerOffset = 0;if (candidateWidth < MIN_ITEM_WIDTH) {centerOffset = (MIN_ITEM_WIDTH - candidateWidth) / 2;candidateWidth = MIN_ITEM_WIDTH;}float itemTotalWidth = candidateWidth + 2 * candMargin;// 画高亮背景if (mActiveCandInPage == i && mEnableActiveHighlight) {mActiveCellRect.set(xPos, getPaddingTop() + 1, xPos+ itemTotalWidth, getHeight() - getPaddingBottom() - 1);mActiveCellDrawable.setBounds((int) mActiveCellRect.left,(int) mActiveCellRect.top, (int) mActiveCellRect.right,(int) mActiveCellRect.bottom);mActiveCellDrawable.draw(canvas);}if (mCandRects.size() < pSize)mCandRects.add(new RectF());mCandRects.elementAt(i).set(xPos - 1, yPos + mFmiCandidates.top,xPos + itemTotalWidth + 1, yPos + mFmiCandidates.bottom);// Draw footnoteif (mShowFootnote) {// 画附注canvas.drawText(footnote, xPos + (candMargin - footnoteSize)/ 2, yPos, mFootnotePaint);}// Left marginxPos += candMargin;if (candidateWidth > mContentWidth - xPos - centerOffset) {cand = getLimitedCandidateForDrawing(cand, mContentWidth - xPos- centerOffset);}if (mActiveCandInPage == i && mEnableActiveHighlight) {mCandidatesPaint.setColor(mActiveCandidateColor);} else {mCandidatesPaint.setColor(mNormalCandidateColor);}// 画候选词canvas.drawText(cand, xPos + centerOffset, yPos, mCandidatesPaint);// Candidate and right marginxPos += candidateWidth + candMargin;// Draw the separator between candidates.// 画分隔符xPos += drawVerticalSeparator(canvas, xPos);}// Update the arrow status of the container.if (null != mArrowUpdater && mUpdateArrowStatusWhenDraw) {mArrowUpdater.updateArrowStatus();mUpdateArrowStatusWhenDraw = false;}}/*** 截取要显示的候选词短语+省略号* * @param rawCandidate* @param widthToDraw* @return*/private String getLimitedCandidateForDrawing(String rawCandidate,float widthToDraw) {int subLen = rawCandidate.length();if (subLen <= 1)return rawCandidate;do {subLen--;float width = mCandidatesPaint.measureText(rawCandidate, 0, subLen);if (width + mSuspensionPointsWidth <= widthToDraw || 1 >= subLen) {return rawCandidate.substring(0, subLen) + SUSPENSION_POINTS;}} while (true);}/*** 画分隔符* * @param canvas* @param xPos* @return*/private float drawVerticalSeparator(Canvas canvas, float xPos) {mSeparatorDrawable.setBounds((int) xPos, getPaddingTop(), (int) xPos+ mSeparatorDrawable.getIntrinsicWidth(), getMeasuredHeight()- getPaddingBottom());mSeparatorDrawable.draw(canvas);return mSeparatorDrawable.getIntrinsicWidth();}/*** 返回坐标点所在或者离的最近的候选词区域在mCandRects的索引* * @param x* @param y* @return*/private int mapToItemInPage(int x, int y) {// mCandRects.size() == 0 happens when the page is set, but// touch events occur before onDraw(). It usually happens with// monkey test.if (!mDecInfo.pageReady(mPageNo) || mPageNoCalculated != mPageNo|| mCandRects.size() == 0) {return -1;}int pageStart = mDecInfo.mPageStart.get(mPageNo);int pageSize = mDecInfo.mPageStart.get(mPageNo + 1) - pageStart;if (mCandRects.size() < pageSize) {return -1;}// If not found, try to find the nearest one.float nearestDis = Float.MAX_VALUE;int nearest = -1;for (int i = 0; i < pageSize; i++) {RectF r = mCandRects.elementAt(i);if (r.left < x && r.right > x && r.top < y && r.bottom > y) {return i;}float disx = (r.left + r.right) / 2 - x;float disy = (r.top + r.bottom) / 2 - y;float dis = disx * disx + disy * disy;if (dis < nearestDis) {nearestDis = dis;nearest = i;}}return nearest;}// Because the candidate view under the current focused one may also get// touching events. Here we just bypass the event to the container and let// it decide which view should handle the event.@Overridepublic boolean onTouchEvent(MotionEvent event) {return super.onTouchEvent(event);}/*** 候选词视图触摸事件处理。在候选词集装箱 CandidatesContainer 中的触摸事件处理函数中调用。* * @param event* @return*/public boolean onTouchEventReal(MotionEvent event) {// The page in the background can also be touched.if (null == mDecInfo || !mDecInfo.pageReady(mPageNo)|| mPageNoCalculated != mPageNo)return true;int x, y;x = (int) event.getX();y = (int) event.getY();// 手势处理if (mGestureDetector.onTouchEvent(event)) {mTimer.removeTimer();mBalloonHint.delayedDismiss(0);return true;}int clickedItemInPage = -1;switch (event.getAction()) {case MotionEvent.ACTION_UP:// 通知上层选择了候选词,并关闭气泡clickedItemInPage = mapToItemInPage(x, y);if (clickedItemInPage >= 0) {invalidate();mCvListener.onClickChoice(clickedItemInPage+ mDecInfo.mPageStart.get(mPageNo));}mBalloonHint.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);break;case MotionEvent.ACTION_DOWN:// 显示气泡,启动按下定时器更新按下候选词高亮效果。clickedItemInPage = mapToItemInPage(x, y);if (clickedItemInPage >= 0) {showBalloon(clickedItemInPage, true);mTimer.startTimer(BalloonHint.TIME_DELAY_SHOW, mPageNo,clickedItemInPage);}break;case MotionEvent.ACTION_CANCEL:break;case MotionEvent.ACTION_MOVE:clickedItemInPage = mapToItemInPage(x, y);if (clickedItemInPage >= 0&& (clickedItemInPage != mTimer.getActiveCandOfPageToShow() || mPageNo != mTimer.getPageToShow())) {showBalloon(clickedItemInPage, true);mTimer.startTimer(BalloonHint.TIME_DELAY_SHOW, mPageNo,clickedItemInPage);}}return true;}/*** 显示气泡* * @param candPos* @param delayedShow*/private void showBalloon(int candPos, boolean delayedShow) {mBalloonHint.removeTimer();RectF r = mCandRects.elementAt(candPos);int desired_width = (int) (r.right - r.left);int desired_height = (int) (r.bottom - r.top);mBalloonHint.setBalloonConfig(mDecInfo.mCandidatesList.get(mDecInfo.mPageStart.get(mPageNo)+ candPos), 44, true, mImeCandidateColor,desired_width, desired_height);getLocationOnScreen(mLocationTmp);mHintPositionToInputView[0] = mLocationTmp[0]+ (int) (r.left - (mBalloonHint.getWidth() - desired_width) / 2);mHintPositionToInputView[1] = -mBalloonHint.getHeight();long delay = BalloonHint.TIME_DELAY_SHOW;if (!delayedShow)delay = 0;mBalloonHint.dismiss();if (!mBalloonHint.isShowing()) {mBalloonHint.delayedShow(delay, mHintPositionToInputView);} else {mBalloonHint.delayedUpdate(0, mHintPositionToInputView, -1, -1);}}/*** 按下某个候选词的定时器。主要是刷新页面,显示按下的候选词为高亮状态。* * @ClassName PressTimer* @author keanbin*/private class PressTimer extends Handler implements Runnable {private boolean mTimerPending = false; // 是否在定时器运行期间private int mPageNoToShow; // 显示的页码private int mActiveCandOfPage; // 高亮候选词在页面的位置public PressTimer() {super();}public void startTimer(long afterMillis, int pageNo, int activeInPage) {mTimer.removeTimer();postDelayed(this, afterMillis);mTimerPending = true;mPageNoToShow = pageNo;mActiveCandOfPage = activeInPage;}public int getPageToShow() {return mPageNoToShow;}public int getActiveCandOfPageToShow() {return mActiveCandOfPage;}public boolean removeTimer() {if (mTimerPending) {mTimerPending = false;removeCallbacks(this);return true;}return false;}public boolean isPending() {return mTimerPending;}public void run() {if (mPageNoToShow >= 0 && mActiveCandOfPage >= 0) {// Always enable to highlight the clicked one.showPage(mPageNoToShow, mActiveCandOfPage, true);invalidate();}mTimerPending = false;}}
}

4、CandidateViewListener.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;/*** Interface to notify the input method when the user clicks a candidate or* makes a direction-gesture on candidate view.*/
/*** 候选词视图监听器接口* * @ClassName CandidateViewListener* @author keanbin*/
public interface CandidateViewListener {/*** 选择了候选词的处理函数* * @param choiceId*/public void onClickChoice(int choiceId);/*** 向左滑动的手势处理函数*/public void onToLeftGesture();/*** 向右滑动的手势处理函数*/public void onToRightGesture();/*** 向上滑动的手势处理函数*/public void onToTopGesture();/*** 向下滑动的手势处理函数*/public void onToBottomGesture();
}

5、ComposingView.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;/*** View used to show composing string (The Pinyin string for the unselected* syllables and the Chinese string for the selected syllables.)*/
/*** 拼音字符串View,用于显示输入的拼音。* * @ClassName ComposingView* @author keanbin*/
public class ComposingView extends View {/*** <p>* There are three statuses for the composing view.* </p>* * <p>* {@link #SHOW_PINYIN} is used to show the current Pinyin string without* highlighted effect. When user inputs Pinyin characters one by one, the* Pinyin string will be shown in this mode.* </p>* <p>* {@link #SHOW_STRING_LOWERCASE} is used to show the Pinyin string in* lowercase with highlighted effect. When user presses UP key and there is* no fixed Chinese characters, composing view will switch from* {@link #SHOW_PINYIN} to this mode, and in this mode, user can press* confirm key to input the lower-case string, so that user can input* English letter in Chinese mode.* </p>* <p>* {@link #EDIT_PINYIN} is used to edit the Pinyin string (shown with* highlighted effect). When current status is {@link #SHOW_PINYIN} and user* presses UP key, if there are fixed Characters, the input method will* switch to {@link #EDIT_PINYIN} thus user can modify some characters in* the middle of the Pinyin string. If the current status is* {@link #SHOW_STRING_LOWERCASE} and user presses LEFT and RIGHT key, it* will also switch to {@link #EDIT_PINYIN}.* </p>* <p>* Whenever user presses down key, the status switches to* {@link #SHOW_PINYIN}.* </p>* <p>* When composing view's status is {@link #SHOW_PINYIN}, the IME's status is* {@link PinyinIME.ImeState#STATE_INPUT}, otherwise, the IME's status* should be {@link PinyinIME.ImeState#STATE_COMPOSING}.* </p>*//*** 拼音字符串的状态*/public enum ComposingStatus {SHOW_PINYIN, SHOW_STRING_LOWERCASE, EDIT_PINYIN,}private static final int LEFT_RIGHT_MARGIN = 5;/*** Used to draw composing string. When drawing the active and idle part of* the spelling(Pinyin) string, the color may be changed.*/private Paint mPaint;/*** Drawable used to draw highlight effect. 高亮*/private Drawable mHlDrawable;/*** Drawable used to draw cursor for editing mode. 光标*/private Drawable mCursor;/*** Used to estimate dimensions to show the string .*/private FontMetricsInt mFmi;private int mStrColor; // 字符串普通颜色private int mStrColorHl; // 字符串高亮颜色private int mStrColorIdle; // 字符串空闲颜色private int mFontSize; // 字体大小/*** 获拼音字符串的状态*/private ComposingStatus mComposingStatus;/*** 解码操作对象*/PinyinIME.DecodingInfo mDecInfo;public ComposingView(Context context, AttributeSet attrs) {super(context, attrs);Resources r = context.getResources();mHlDrawable = r.getDrawable(R.drawable.composing_hl_bg);mCursor = r.getDrawable(R.drawable.composing_area_cursor);mStrColor = r.getColor(R.color.composing_color);mStrColorHl = r.getColor(R.color.composing_color_hl);mStrColorIdle = r.getColor(R.color.composing_color_idle);mFontSize = r.getDimensionPixelSize(R.dimen.composing_height);mPaint = new Paint();mPaint.setColor(mStrColor);mPaint.setAntiAlias(true);mPaint.setTextSize(mFontSize);mFmi = mPaint.getFontMetricsInt();}/*** 重置拼音字符串View状态*/public void reset() {mComposingStatus = ComposingStatus.SHOW_PINYIN;}/*** Set the composing string to show. If the IME status is* {@link PinyinIME.ImeState#STATE_INPUT}, the composing view's status will* be set to {@link ComposingStatus#SHOW_PINYIN}, otherwise the composing* view will set its status to {@link ComposingStatus#SHOW_STRING_LOWERCASE}* or {@link ComposingStatus#EDIT_PINYIN} automatically.*//*** 设置 解码操作对象,然后刷新View。* * @param decInfo* @param imeStatus*/public void setDecodingInfo(PinyinIME.DecodingInfo decInfo,PinyinIME.ImeState imeStatus) {mDecInfo = decInfo;if (PinyinIME.ImeState.STATE_INPUT == imeStatus) {mComposingStatus = ComposingStatus.SHOW_PINYIN;mDecInfo.moveCursorToEdge(false);} else {if (decInfo.getFixedLen() != 0|| ComposingStatus.EDIT_PINYIN == mComposingStatus) {mComposingStatus = ComposingStatus.EDIT_PINYIN;} else {mComposingStatus = ComposingStatus.SHOW_STRING_LOWERCASE;}mDecInfo.moveCursor(0);}measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);requestLayout();invalidate();}/*** 移动光标。其实可以算是对KeyEvent.KEYCODE_DPAD_LEFT和KeyEvent.* KEYCODE_DPAD_RIGHT这两个键的处理函数。* * @param keyCode* @return*/public boolean moveCursor(int keyCode) {if (keyCode != KeyEvent.KEYCODE_DPAD_LEFT&& keyCode != KeyEvent.KEYCODE_DPAD_RIGHT)return false;if (ComposingStatus.EDIT_PINYIN == mComposingStatus) {int offset = 0;if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT)offset = -1;else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)offset = 1;mDecInfo.moveCursor(offset);} else if (ComposingStatus.SHOW_STRING_LOWERCASE == mComposingStatus) {if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {mComposingStatus = ComposingStatus.EDIT_PINYIN;measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);requestLayout();}}invalidate();return true;}/*** 获取输入的音字符串的状态* * @return*/public ComposingStatus getComposingStatus() {return mComposingStatus;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {float width;int height;height = mFmi.bottom - mFmi.top + getPaddingTop() + getPaddingBottom();if (null == mDecInfo) {width = 0;} else {width = getPaddingLeft() + getPaddingRight() + LEFT_RIGHT_MARGIN* 2;String str;if (ComposingStatus.SHOW_STRING_LOWERCASE == mComposingStatus) {str = mDecInfo.getOrigianlSplStr().toString();} else {str = mDecInfo.getComposingStrForDisplay();}width += mPaint.measureText(str, 0, str.length());}setMeasuredDimension((int) (width + 0.5f), height);}@Overrideprotected void onDraw(Canvas canvas) {if (ComposingStatus.EDIT_PINYIN == mComposingStatus|| ComposingStatus.SHOW_PINYIN == mComposingStatus) {drawForPinyin(canvas);return;}// 画选中的候选词float x, y;x = getPaddingLeft() + LEFT_RIGHT_MARGIN;y = -mFmi.top + getPaddingTop();mPaint.setColor(mStrColorHl);mHlDrawable.setBounds(getPaddingLeft(), getPaddingTop(), getWidth()- getPaddingRight(), getHeight() - getPaddingBottom());mHlDrawable.draw(canvas);String splStr = mDecInfo.getOrigianlSplStr().toString();canvas.drawText(splStr, 0, splStr.length(), x, y, mPaint);}/*** 画光标* * @param canvas* @param x*/private void drawCursor(Canvas canvas, float x) {mCursor.setBounds((int) x, getPaddingTop(),(int) x + mCursor.getIntrinsicWidth(), getHeight()- getPaddingBottom());mCursor.draw(canvas);}/*** 画拼音字符串* * @param canvas*/private void drawForPinyin(Canvas canvas) {float x, y;x = getPaddingLeft() + LEFT_RIGHT_MARGIN;y = -mFmi.top + getPaddingTop();mPaint.setColor(mStrColor);int cursorPos = mDecInfo.getCursorPosInCmpsDisplay();int cmpsPos = cursorPos;String cmpsStr = mDecInfo.getComposingStrForDisplay();int activeCmpsLen = mDecInfo.getActiveCmpsDisplayLen();if (cursorPos > activeCmpsLen)cmpsPos = activeCmpsLen;canvas.drawText(cmpsStr, 0, cmpsPos, x, y, mPaint);x += mPaint.measureText(cmpsStr, 0, cmpsPos);if (cursorPos <= activeCmpsLen) {if (ComposingStatus.EDIT_PINYIN == mComposingStatus) {drawCursor(canvas, x);}canvas.drawText(cmpsStr, cmpsPos, activeCmpsLen, x, y, mPaint);}x += mPaint.measureText(cmpsStr, cmpsPos, activeCmpsLen);if (cmpsStr.length() > activeCmpsLen) {mPaint.setColor(mStrColorIdle);int oriPos = activeCmpsLen;if (cursorPos > activeCmpsLen) {if (cursorPos > cmpsStr.length())cursorPos = cmpsStr.length();canvas.drawText(cmpsStr, oriPos, cursorPos, x, y, mPaint);x += mPaint.measureText(cmpsStr, oriPos, cursorPos);if (ComposingStatus.EDIT_PINYIN == mComposingStatus) {drawCursor(canvas, x);}oriPos = cursorPos;}canvas.drawText(cmpsStr, oriPos, cmpsStr.length(), x, y, mPaint);}}
}

6、EnglishInputProcessor.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import android.view.KeyEvent;
import android.view.inputmethod.InputConnection;/*** Class to handle English input.*/
/*** 英文输入法按键处理器* * @ClassName EnglishInputProcessor* @author keanbin*/
public class EnglishInputProcessor {private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;/*** 英文输入法按键处理函数* * @param inputContext* @param event* @param upperCase* @param realAction* @return*/public boolean processKey(InputConnection inputContext, KeyEvent event,boolean upperCase, boolean realAction) {if (null == inputContext || null == event)return false;int keyCode = event.getKeyCode();CharSequence prefix = null;prefix = inputContext.getTextBeforeCursor(2, 0);int keyChar;keyChar = 0;if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {keyChar = keyCode - KeyEvent.KEYCODE_A + 'a';if (upperCase) {keyChar = keyChar + 'A' - 'a';}} else if (keyCode >= KeyEvent.KEYCODE_0&& keyCode <= KeyEvent.KEYCODE_9)keyChar = keyCode - KeyEvent.KEYCODE_0 + '0';else if (keyCode == KeyEvent.KEYCODE_COMMA)keyChar = ',';else if (keyCode == KeyEvent.KEYCODE_PERIOD)keyChar = '.';else if (keyCode == KeyEvent.KEYCODE_APOSTROPHE)keyChar = '\'';else if (keyCode == KeyEvent.KEYCODE_AT)keyChar = '@';else if (keyCode == KeyEvent.KEYCODE_SLASH)keyChar = '/';// 功能键处理if (0 == keyChar) {mLastKeyCode = keyCode;String insert = null;if (KeyEvent.KEYCODE_DEL == keyCode) {if (realAction) {inputContext.deleteSurroundingText(1, 0);}} else if (KeyEvent.KEYCODE_ENTER == keyCode) {insert = "\n";} else if (KeyEvent.KEYCODE_SPACE == keyCode) {insert = " ";} else {return false;}if (null != insert && realAction)inputContext.commitText(insert, insert.length());return true;}if (!realAction)return true;// 再一次判断大小写,根据mLastKeyCodeif (KeyEvent.KEYCODE_SHIFT_LEFT == mLastKeyCode|| KeyEvent.KEYCODE_SHIFT_LEFT == mLastKeyCode) {if (keyChar >= 'a' && keyChar <= 'z')keyChar = keyChar - 'a' + 'A';} else if (KeyEvent.KEYCODE_ALT_LEFT == mLastKeyCode) {}String result = String.valueOf((char) keyChar);inputContext.commitText(result, result.length());mLastKeyCode = keyCode;return true;}
}

7、Environment.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import android.content.Context;
import android.content.res.Configuration;
import android.view.Display;
import android.view.WindowManager;/*** Global environment configurations for showing soft keyboard and candidate* view. All original dimension values are defined in float, and the real size* is calculated from the float values of and screen size. In this way, this* input method can work even when screen size is changed.* 该类保存布局的一些尺寸。比如:屏幕的宽度、屏幕的高度* 、按键的高度、候选词区域的高度、按键气泡宽度比按键宽度大的差值、按键气泡高度比按键高度大的差值、正常按键中文本的大小* 、功能按键中文本的大小、正常按键气泡中文本的大小、功能按键气泡中文本的大小。*/
public class Environment {/*** The key height for portrait mode. It is relative to the screen height.* 竖屏按键高度,值是相对于屏幕高度。*/private static final float KEY_HEIGHT_RATIO_PORTRAIT = 0.105f;/*** The key height for landscape mode. It is relative to the screen height.* 横屏按键高度,值是相对于屏幕高度。*/private static final float KEY_HEIGHT_RATIO_LANDSCAPE = 0.147f;/*** The height of the candidates area for portrait mode. It is relative to* screen height. 竖屏候选词区域的高度,值是相对于屏幕高度。*/private static final float CANDIDATES_AREA_HEIGHT_RATIO_PORTRAIT = 0.084f;/*** The height of the candidates area for portrait mode. It is relative to* screen height. 横屏候选词区域高度,值是相对于屏幕高度。*/private static final float CANDIDATES_AREA_HEIGHT_RATIO_LANDSCAPE = 0.125f;/*** How much should the balloon width be larger than width of the real key.* It is relative to the smaller one of screen width and height.* 猜测:点击软键盘按钮时弹出来的气泡大于按键的宽度的差值,值是相对于屏幕高度和宽度较小的那一个。*/private static final float KEY_BALLOON_WIDTH_PLUS_RATIO = 0.08f;/*** How much should the balloon height be larger than that of the real key.* It is relative to the smaller one of screen width and height.* 猜测:点击软键盘按钮时弹出来的气泡大于按键的高度的差值,值是相对于屏幕高度和宽度较小的那一个。*/private static final float KEY_BALLOON_HEIGHT_PLUS_RATIO = 0.07f;/*** The text size for normal keys. It is relative to the smaller one of* screen width and height. 正常按键的文本的大小,值是相对于屏幕高度和宽度较小的那一个。*/private static final float NORMAL_KEY_TEXT_SIZE_RATIO = 0.075f;/*** The text size for function keys. It is relative to the smaller one of* screen width and height. 功能按键的文本的大小,值是相对于屏幕高度和宽度较小的那一个。*/private static final float FUNCTION_KEY_TEXT_SIZE_RATIO = 0.055f;/*** The text size balloons of normal keys. It is relative to the smaller one* of screen width and height. 正常按键弹出的气泡的文本的大小,值是相对于屏幕高度和宽度较小的那一个。*/private static final float NORMAL_BALLOON_TEXT_SIZE_RATIO = 0.14f;/*** The text size balloons of function keys. It is relative to the smaller* one of screen width and height. 功能按键弹出的气泡的文本的大小,值是相对于屏幕高度和宽度较小的那一个。*/private static final float FUNCTION_BALLOON_TEXT_SIZE_RATIO = 0.085f;/*** The configurations are managed in a singleton. 该类的实例,该类采用设计模式的单例模式。*/private static Environment mInstance;private int mScreenWidth; // 屏幕的宽度private int mScreenHeight; // 屏幕的高度private int mKeyHeight; // 按键的高度private int mCandidatesAreaHeight; // 候选词区域的高度private int mKeyBalloonWidthPlus; // 按键气泡宽度比按键宽度大的差值private int mKeyBalloonHeightPlus; // 按键气泡高度比按键高度大的差值private int mNormalKeyTextSize; // 正常按键中文本的大小private int mFunctionKeyTextSize; // 功能按键中文本的大小private int mNormalBalloonTextSize; // 正常按键气泡中文本的大小private int mFunctionBalloonTextSize; // 功能按键气泡中文本的大小private Configuration mConfig = new Configuration();private boolean mDebug = false;private Environment() {}public static Environment getInstance() {if (null == mInstance) {mInstance = new Environment();}return mInstance;}public void onConfigurationChanged(Configuration newConfig, Context context) {if (mConfig.orientation != newConfig.orientation) {WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);Display d = wm.getDefaultDisplay();mScreenWidth = d.getWidth();mScreenHeight = d.getHeight();int scale;if (mScreenHeight > mScreenWidth) {mKeyHeight = (int) (mScreenHeight * KEY_HEIGHT_RATIO_PORTRAIT);mCandidatesAreaHeight = (int) (mScreenHeight * CANDIDATES_AREA_HEIGHT_RATIO_PORTRAIT);scale = mScreenWidth;} else {mKeyHeight = (int) (mScreenHeight * KEY_HEIGHT_RATIO_LANDSCAPE);mCandidatesAreaHeight = (int) (mScreenHeight * CANDIDATES_AREA_HEIGHT_RATIO_LANDSCAPE);scale = mScreenHeight;}mNormalKeyTextSize = (int) (scale * NORMAL_KEY_TEXT_SIZE_RATIO);mFunctionKeyTextSize = (int) (scale * FUNCTION_KEY_TEXT_SIZE_RATIO);mNormalBalloonTextSize = (int) (scale * NORMAL_BALLOON_TEXT_SIZE_RATIO);mFunctionBalloonTextSize = (int) (scale * FUNCTION_BALLOON_TEXT_SIZE_RATIO);mKeyBalloonWidthPlus = (int) (scale * KEY_BALLOON_WIDTH_PLUS_RATIO);mKeyBalloonHeightPlus = (int) (scale * KEY_BALLOON_HEIGHT_PLUS_RATIO);}mConfig.updateFrom(newConfig);}public Configuration getConfiguration() {return mConfig;}public int getScreenWidth() {return mScreenWidth;}public int getScreenHeight() {return mScreenHeight;}public int getHeightForCandidates() {return mCandidatesAreaHeight;}public float getKeyXMarginFactor() {return 1.0f;}public float getKeyYMarginFactor() {if (Configuration.ORIENTATION_LANDSCAPE == mConfig.orientation) {return 0.7f;}return 1.0f;}public int getKeyHeight() {return mKeyHeight;}public int getKeyBalloonWidthPlus() {return mKeyBalloonWidthPlus;}public int getKeyBalloonHeightPlus() {return mKeyBalloonHeightPlus;}public int getSkbHeight() {if (Configuration.ORIENTATION_PORTRAIT == mConfig.orientation) {return mKeyHeight * 4;} else if (Configuration.ORIENTATION_LANDSCAPE == mConfig.orientation) {return mKeyHeight * 4;}return 0;}/*** 获得按键的文本大小* * @param isFunctionKey*            是否是功能键* @return*/public int getKeyTextSize(boolean isFunctionKey) {if (isFunctionKey) {return mFunctionKeyTextSize;} else {return mNormalKeyTextSize;}}public int getBalloonTextSize(boolean isFunctionKey) {if (isFunctionKey) {return mFunctionBalloonTextSize;} else {return mNormalBalloonTextSize;}}public boolean hasHardKeyboard() {if (mConfig.keyboard == Configuration.KEYBOARD_NOKEYS|| mConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {return false;}return true;}public boolean needDebug() {return mDebug;}
}

8、InputModeSwitcher.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import android.content.res.Resources;
import android.view.inputmethod.EditorInfo;import com.keanbin.pinyinime.SoftKeyboard.KeyRow;/*** * 输入法模式转换器。设置输入法的软键盘。* * @author keanbin* */
public class InputModeSwitcher {/*** User defined key code, used by soft keyboard.* 用户定义的key的code,用于软键盘。shift键的code。*/private static final int USERDEF_KEYCODE_SHIFT_1 = -1;/*** User defined key code, used by soft keyboard. 语言键的code*/private static final int USERDEF_KEYCODE_LANG_2 = -2;/*** User defined key code, used by soft keyboard. “?123”键的code,作用是与数字键盘的切换。*/private static final int USERDEF_KEYCODE_SYM_3 = -3;/*** User defined key code, used by soft keyboard.*/public static final int USERDEF_KEYCODE_PHONE_SYM_4 = -4;/*** User defined key code, used by soft keyboard.*/private static final int USERDEF_KEYCODE_MORE_SYM_5 = -5;/*** User defined key code, used by soft keyboard. 微笑按键的code,比如中文时,语言键的上面那个按键。*/private static final int USERDEF_KEYCODE_SMILEY_6 = -6;/*** Bits used to indicate soft keyboard layout. If none bit is set, the* current input mode does not require a soft keyboard.* 第8位指明软键盘的布局。如果最8位为0,那么就表明当前输入法模式不需要软键盘。**/private static final int MASK_SKB_LAYOUT = 0xf0000000;/*** A kind of soft keyboard layout. An input mode should be anded with* {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout. 指明标准的传统键盘*/private static final int MASK_SKB_LAYOUT_QWERTY = 0x10000000;/*** A kind of soft keyboard layout. An input mode should be anded with* {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout. 指明符号软键盘一*/private static final int MASK_SKB_LAYOUT_SYMBOL1 = 0x20000000;/*** A kind of soft keyboard layout. An input mode should be anded with* {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout. 指明符号软键盘二*/private static final int MASK_SKB_LAYOUT_SYMBOL2 = 0x30000000;/*** A kind of soft keyboard layout. An input mode should be anded with* {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout. 指明微笑软键盘*/private static final int MASK_SKB_LAYOUT_SMILEY = 0x40000000;/*** A kind of soft keyboard layout. An input mode should be anded with* {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout. 指明电话软键盘*/private static final int MASK_SKB_LAYOUT_PHONE = 0x50000000;/*** Used to indicate which language the current input mode is in. If the* current input mode works with a none-QWERTY soft keyboard, these bits are* also used to get language information. For example, a Chinese symbol soft* keyboard and an English one are different in an icon which is used to* tell user the language information. BTW, the smiley soft keyboard mode* should be set with {@link #MASK_LANGUAGE_CN} because it can only be* launched from Chinese QWERTY soft keyboard, and it has Chinese icon on* soft keyboard. 第7位指明语言。*/private static final int MASK_LANGUAGE = 0x0f000000;/*** Used to indicate the current language. An input mode should be anded with* {@link #MASK_LANGUAGE} to get this information. 指明中文语言。*/private static final int MASK_LANGUAGE_CN = 0x01000000;/*** Used to indicate the current language. An input mode should be anded with* {@link #MASK_LANGUAGE} to get this information. 指明英文语言。*/private static final int MASK_LANGUAGE_EN = 0x02000000;/*** Used to indicate which case the current input mode is in. For example,* English QWERTY has lowercase and uppercase. For the Chinese QWERTY, these* bits are ignored. For phone keyboard layout, these bits can be* {@link #MASK_CASE_UPPER} to request symbol page for phone soft keyboard.* 第6位指明软键盘当前的状态,比如高(大写),低(小写)。*/private static final int MASK_CASE = 0x00f00000;/*** Used to indicate the current case information. An input mode should be* anded with {@link #MASK_CASE} to get this information. 指明软键盘状态为低(小写)。*/private static final int MASK_CASE_LOWER = 0x00100000;/*** Used to indicate the current case information. An input mode should be* anded with {@link #MASK_CASE} to get this information. 指明软键盘状态为高(大写)。*/private static final int MASK_CASE_UPPER = 0x00200000;/*** Mode for inputing Chinese with soft keyboard. 中文标准软键盘模式*/public static final int MODE_SKB_CHINESE = (MASK_SKB_LAYOUT_QWERTY | MASK_LANGUAGE_CN);/*** Mode for inputing basic symbols for Chinese mode with soft keyboard.* 中文符号软键盘一模式*/public static final int MODE_SKB_SYMBOL1_CN = (MASK_SKB_LAYOUT_SYMBOL1 | MASK_LANGUAGE_CN);/*** Mode for inputing more symbols for Chinese mode with soft keyboard.* 中文符号软键盘二模式*/public static final int MODE_SKB_SYMBOL2_CN = (MASK_SKB_LAYOUT_SYMBOL2 | MASK_LANGUAGE_CN);/*** Mode for inputing English lower characters with soft keyboard. 英文小写软键盘模式*/public static final int MODE_SKB_ENGLISH_LOWER = (MASK_SKB_LAYOUT_QWERTY| MASK_LANGUAGE_EN | MASK_CASE_LOWER);/*** Mode for inputing English upper characters with soft keyboard. 英文大写软键盘模式*/public static final int MODE_SKB_ENGLISH_UPPER = (MASK_SKB_LAYOUT_QWERTY| MASK_LANGUAGE_EN | MASK_CASE_UPPER);/*** Mode for inputing basic symbols for English mode with soft keyboard.* 英文符号软键盘一模式*/public static final int MODE_SKB_SYMBOL1_EN = (MASK_SKB_LAYOUT_SYMBOL1 | MASK_LANGUAGE_EN);/*** Mode for inputing more symbols for English mode with soft keyboard.* 英文符号软键盘二模式*/public static final int MODE_SKB_SYMBOL2_EN = (MASK_SKB_LAYOUT_SYMBOL2 | MASK_LANGUAGE_EN);/*** Mode for inputing smileys with soft keyboard. 中文笑脸软键盘模式*/public static final int MODE_SKB_SMILEY = (MASK_SKB_LAYOUT_SMILEY | MASK_LANGUAGE_CN);/*** Mode for inputing phone numbers. 电话号码软键盘模式*/public static final int MODE_SKB_PHONE_NUM = (MASK_SKB_LAYOUT_PHONE);/*** Mode for inputing phone numbers. 电话号码大写软键盘模式*/public static final int MODE_SKB_PHONE_SYM = (MASK_SKB_LAYOUT_PHONE | MASK_CASE_UPPER);/*** Mode for inputing Chinese with a hardware keyboard. 中文硬键盘模式。(即不需要软键盘)*/public static final int MODE_HKB_CHINESE = (MASK_LANGUAGE_CN);/*** Mode for inputing English with a hardware keyboard 英文硬键盘模式。(即不需要软键盘)*/public static final int MODE_HKB_ENGLISH = (MASK_LANGUAGE_EN);/*** Unset mode. 未设置输入法模式。*/public static final int MODE_UNSET = 0;/*** Maximum toggle states for a soft keyboard.* 一个软键盘的切换状态的最大数量。这里的切换状态是指要显示的按键的状态* ,所以这些状态不可能有两个同时出现在一个按键当中。如果有两个同时出现在一个按键当中,那就 不知道要显示哪一个了。*/public static final int MAX_TOGGLE_STATES = 4;/*** The input mode for the current edit box. 当前输入法的模式*/private int mInputMode = MODE_UNSET;/*** Used to remember previous input mode. When user enters an edit field, the* previous input mode will be tried. If the previous mode can not be used* for the current situation (For example, previous mode is a soft keyboard* mode to input symbols, and we have a hardware keyboard for the current* situation), {@link #mRecentLauageInputMode} will be tried. 前一个输入法的模式**/private int mPreviousInputMode = MODE_SKB_CHINESE;/*** Used to remember recent mode to input language. 最近的语言输入法模式*/private int mRecentLauageInputMode = MODE_SKB_CHINESE;/*** Editor information of the current edit box. 当前编辑框的 EditorInfo 。*/private EditorInfo mEditorInfo;/*** Used to indicate required toggling operations.* 控制当前输入法模式软键盘布局要显示的按键切换状态和要显示的行ID。比如当前软键盘布局中* ,有一个按键有默认状态、和两个切换状态,ToggleStates中的mKeyStates[]保存的就是当前要显示的切换状态* ,如果按键的两个切换状态都没有在mKeyStates[]中* ,那么按键就显示默认状态,如果两个切换状态中有一个在mKeyStates[]中,就显示那个切换状态* 。注意:绝对不可能有一个按键的两个或两个以上的切换状态同时在mKeyStates* []中。ToggleStates不仅控制按键的显示状态,还可以直接控制一行按键的显示* 。mRowIdToEnable保存的就是可显示的行的ID(每行的ID不是唯一的 ,一个ID同时赋值给多行)。只有ID为* mRowIdToEnable 和 ALWAYS_SHOW_ROW_ID 的行才会被显示出来。*/private ToggleStates mToggleStates = new ToggleStates();/*** The current field is a short message field? 当前的字段是否是一个短语字段? 当* editorInfo.inputType & EditorInfo.TYPE_MASK_VARIATION ==* EditorInfo.TYPE_MASK_VARIATION 时,被设置为true,否则为false。*/private boolean mShortMessageField;/*** Is return key in normal state? 是否是正常状态下的 Enter 键?*/private boolean mEnterKeyNormal = true;/*** Current icon. 0 for none icon. 当前输入法的图标。显示在信号栏的图标。*/int mInputIcon = R.drawable.ime_pinyin;/*** IME service. 输入法服务*/private PinyinIME mImeService;/*** Key toggling state for Chinese mode. 软键盘中的按键的切换状态的ID:中文状态。*/private int mToggleStateCn;/*** Key toggling state for Chinese mode with candidates.* 软键盘中的按键的切换状态的ID:中文候选词状态。*/private int mToggleStateCnCand;/*** Key toggling state for English lowwercase mode. 软键盘中的按键的切换状态的ID:英文小写状态。*/private int mToggleStateEnLower;/*** Key toggling state for English upppercase mode. 软键盘中的按键的切换状态的ID:英文大写状态。*/private int mToggleStateEnUpper;/*** Key toggling state for English symbol mode for the first page.* 软键盘中的按键的切换状态的ID:英文符号一状态。*/private int mToggleStateEnSym1;/*** Key toggling state for English symbol mode for the second page.* 软键盘中的按键的切换状态的ID:英文符号二状态。*/private int mToggleStateEnSym2;/*** Key toggling state for smiley mode. 软键盘中的按键的切换状态的ID:笑脸状态。*/private int mToggleStateSmiley;/*** Key toggling state for phone symbol mode. 软键盘中的按键的切换状态的ID:电话号码状态。*/private int mToggleStatePhoneSym;/*** Key toggling state for GO action of ENTER key.* 软键盘中的按键的切换状态的ID:Enter键作为go操作状态。*/private int mToggleStateGo;/*** Key toggling state for SEARCH action of ENTER key.* 软键盘中的按键的切换状态的ID:Enter键作为搜索操作状态。*/private int mToggleStateSearch;/*** Key toggling state for SEND action of ENTER key.* 软键盘中的按键的切换状态的ID:Enter键作为发送操作状态。*/private int mToggleStateSend;/*** Key toggling state for NEXT action of ENTER key.* 软键盘中的按键的切换状态的ID:Enter键作为下一步操作状态。*/private int mToggleStateNext;/*** Key toggling state for SEND action of ENTER key.* 软键盘中的按键的切换状态的ID:Enter键作为完成操作状态。*/private int mToggleStateDone;/*** QWERTY row toggling state for Chinese input. 软键盘中的行的ID:中文状态。*/private int mToggleRowCn;/*** QWERTY row toggling state for English input. 软键盘中的行的ID:英文状态。*/private int mToggleRowEn;/*** QWERTY row toggling state for URI input. 软键盘中的行的ID:Uri输入状态。*/private int mToggleRowUri;/*** QWERTY row toggling state for email address input. 软键盘中的行的ID:邮件地址输入状态。*/private int mToggleRowEmailAddress;/*** 控制当前输入法模式软键盘布局要显示的按键切换状态和要显示的行ID的管理类。比如当前软键盘布局中* ,有一个按键有默认状态、和两个切换状态,ToggleStates中的mKeyStates[]保存的就是当前要显示的切换状态* ,如果按键的两个切换状态都没有在mKeyStates[]中* ,那么按键就显示默认状态,如果两个切换状态中有一个在mKeyStates[]中,就显示那个切换状态* 。注意:绝对不可能有一个按键的两个或两个以上的切换状态同时在mKeyStates* []中。ToggleStates不仅控制按键的显示状态,还可以直接控制一行按键的显示* 。mRowIdToEnable保存的就是可显示的行的ID(每行的ID不是唯一的 ,一个ID同时赋值给多行)。只有ID为* mRowIdToEnable 和 ALWAYS_SHOW_ROW_ID 的行才会被显示出来。* * @ClassName ToggleStates* @author keanbin*/class ToggleStates {/*** If it is true, this soft keyboard is a QWERTY one. 是否是标准键盘*/boolean mQwerty;/*** If {@link #mQwerty} is true, this variable is used to decide the* letter case of the QWERTY keyboard. 是否是标准键盘大写模式*/boolean mQwertyUpperCase;/*** The id of enabled row in the soft keyboard. Refer to* {@link com.android.inputmethod.pinyin.SoftKeyboard.KeyRow} for* details. 软键盘中要显示的行的ID。只有ID为 mRowIdToEnable 和 ALWAYS_SHOW_ROW_ID* 的行才会被显示出来。*/public int mRowIdToEnable;/*** Used to store all other toggle states for the current input mode.* 这里保持此刻要显示的按键的状态,所以这些状态不可能有两个同时出现在一个按键当中。如果有两个同时出现在一个按键当中,那就* 不知道要显示哪一个了。*/public int mKeyStates[] = new int[MAX_TOGGLE_STATES];/*** Number of states to toggle. 按键切换状态的数量。mKeyStates[]的有用的长度。*/public int mKeyStatesNum;}public InputModeSwitcher(PinyinIME imeService) {mImeService = imeService;Resources r = mImeService.getResources();// 初始化按键各种切换状态的ID 和 行的IDmToggleStateCn = Integer.parseInt(r.getString(R.string.toggle_cn));mToggleStateCnCand = Integer.parseInt(r.getString(R.string.toggle_cn_cand));mToggleStateEnLower = Integer.parseInt(r.getString(R.string.toggle_en_lower));mToggleStateEnUpper = Integer.parseInt(r.getString(R.string.toggle_en_upper));mToggleStateEnSym1 = Integer.parseInt(r.getString(R.string.toggle_en_sym1));mToggleStateEnSym2 = Integer.parseInt(r.getString(R.string.toggle_en_sym2));mToggleStateSmiley = Integer.parseInt(r.getString(R.string.toggle_smiley));mToggleStatePhoneSym = Integer.parseInt(r.getString(R.string.toggle_phone_sym));mToggleStateGo = Integer.parseInt(r.getString(R.string.toggle_enter_go));mToggleStateSearch = Integer.parseInt(r.getString(R.string.toggle_enter_search));mToggleStateSend = Integer.parseInt(r.getString(R.string.toggle_enter_send));mToggleStateNext = Integer.parseInt(r.getString(R.string.toggle_enter_next));mToggleStateDone = Integer.parseInt(r.getString(R.string.toggle_enter_done));mToggleRowCn = Integer.parseInt(r.getString(R.string.toggle_row_cn));mToggleRowEn = Integer.parseInt(r.getString(R.string.toggle_row_en));mToggleRowUri = Integer.parseInt(r.getString(R.string.toggle_row_uri));mToggleRowEmailAddress = Integer.parseInt(r.getString(R.string.toggle_row_emailaddress));}/*** 获取当前的输入法模式* * @有效值 *      MODE_UNSET(未设置输入法模式)、MODE_SKB_CHINESE(中文标准软键盘模式)、MODE_SKB_SYMBOL1_CN*      (中文符号软键盘一模式)、MODE_SKB_SYMBOL2_CN(中文符号软键盘二模式)、*      MODE_SKB_ENGLISH_LOWER*      (英文小写软键盘模式)、MODE_SKB_ENGLISH_UPPER(英文大写软键盘模式*      )、MODE_SKB_SYMBOL1_EN(英文符号软键盘一模式*      )、MODE_SKB_SYMBOL2_EN(英文符号软键盘二模式)、MODE_SKB_SMILEY*      (中文笑脸软键盘模式)、MODE_SKB_PHONE_NUM*      (电话号码软键盘模式)、MODE_SKB_PHONE_SYM(电话号码大写软键盘模式*      )、MODE_HKB_CHINESE(中文硬键盘模式)、MODE_HKB_ENGLISH(英文硬键盘模式)。* @return*/public int getInputMode() {return mInputMode;}/*** 获取控制当前输入法模式软键盘布局的按键切换状态和可显示行ID的对象。* * @return*/public ToggleStates getToggleStates() {return mToggleStates;}/*** 更加软键盘 LAYOUT 获取软键盘布局文件资源ID* * @return*/public int getSkbLayout() {int layout = (mInputMode & MASK_SKB_LAYOUT);switch (layout) {case MASK_SKB_LAYOUT_QWERTY:return R.xml.skb_qwerty;case MASK_SKB_LAYOUT_SYMBOL1:return R.xml.skb_sym1;case MASK_SKB_LAYOUT_SYMBOL2:return R.xml.skb_sym2;case MASK_SKB_LAYOUT_SMILEY:return R.xml.skb_smiley;case MASK_SKB_LAYOUT_PHONE:return R.xml.skb_phone;}return 0;}/*** 切换硬键盘的语言模式,返回切换后的语言模式的图标。* 先设置新的输入法语言是中文模式,再判断当前的输入法语言模式是否是中文模式,是的话,就改成英文模式。* * @return the icon to update*/public int switchLanguageWithHkb() {int newInputMode = MODE_HKB_CHINESE;mInputIcon = R.drawable.ime_pinyin;if (MODE_HKB_CHINESE == mInputMode) {newInputMode = MODE_HKB_ENGLISH;mInputIcon = R.drawable.ime_en;}saveInputMode(newInputMode);return mInputIcon;}/*** 通过我们定义的软键盘的按键,切换输入法模式。* * @param userKey* @return the icon to update.*/public int switchModeForUserKey(int userKey) {int newInputMode = MODE_UNSET;if (USERDEF_KEYCODE_LANG_2 == userKey) {// 语言键:显示中文或者英文、中符、英符的键if (MODE_SKB_CHINESE == mInputMode) {newInputMode = MODE_SKB_ENGLISH_LOWER;} else if (MODE_SKB_ENGLISH_LOWER == mInputMode|| MODE_SKB_ENGLISH_UPPER == mInputMode) {newInputMode = MODE_SKB_CHINESE;} else if (MODE_SKB_SYMBOL1_CN == mInputMode) {newInputMode = MODE_SKB_SYMBOL1_EN;} else if (MODE_SKB_SYMBOL1_EN == mInputMode) {newInputMode = MODE_SKB_SYMBOL1_CN;} else if (MODE_SKB_SYMBOL2_CN == mInputMode) {newInputMode = MODE_SKB_SYMBOL2_EN;} else if (MODE_SKB_SYMBOL2_EN == mInputMode) {newInputMode = MODE_SKB_SYMBOL2_CN;} else if (MODE_SKB_SMILEY == mInputMode) {newInputMode = MODE_SKB_CHINESE;}} else if (USERDEF_KEYCODE_SYM_3 == userKey) {// 系统键:显示“?123”的键if (MODE_SKB_CHINESE == mInputMode) {newInputMode = MODE_SKB_SYMBOL1_CN;} else if (MODE_SKB_ENGLISH_UPPER == mInputMode|| MODE_SKB_ENGLISH_LOWER == mInputMode) {newInputMode = MODE_SKB_SYMBOL1_EN;} else if (MODE_SKB_SYMBOL1_EN == mInputMode|| MODE_SKB_SYMBOL2_EN == mInputMode) {newInputMode = MODE_SKB_ENGLISH_LOWER;} else if (MODE_SKB_SYMBOL1_CN == mInputMode|| MODE_SKB_SYMBOL2_CN == mInputMode) {newInputMode = MODE_SKB_CHINESE;} else if (MODE_SKB_SMILEY == mInputMode) {newInputMode = MODE_SKB_SYMBOL1_CN;}} else if (USERDEF_KEYCODE_SHIFT_1 == userKey) {// shift键:显示“,” 或者 大小写图标的按键。if (MODE_SKB_ENGLISH_LOWER == mInputMode) {newInputMode = MODE_SKB_ENGLISH_UPPER;} else if (MODE_SKB_ENGLISH_UPPER == mInputMode) {newInputMode = MODE_SKB_ENGLISH_LOWER;}} else if (USERDEF_KEYCODE_MORE_SYM_5 == userKey) {// 更多系统键,显示“ALT”的按键int sym = (MASK_SKB_LAYOUT & mInputMode);if (MASK_SKB_LAYOUT_SYMBOL1 == sym) {sym = MASK_SKB_LAYOUT_SYMBOL2;} else {sym = MASK_SKB_LAYOUT_SYMBOL1;}newInputMode = ((mInputMode & (~MASK_SKB_LAYOUT)) | sym);} else if (USERDEF_KEYCODE_SMILEY_6 == userKey) {// 笑脸键:显示机器人笑脸图标的按键if (MODE_SKB_CHINESE == mInputMode) {newInputMode = MODE_SKB_SMILEY;} else {newInputMode = MODE_SKB_CHINESE;}} else if (USERDEF_KEYCODE_PHONE_SYM_4 == userKey) {// 电话键:显示“*#{”或者“123”的按键if (MODE_SKB_PHONE_NUM == mInputMode) {newInputMode = MODE_SKB_PHONE_SYM;} else {newInputMode = MODE_SKB_PHONE_NUM;}}if (newInputMode == mInputMode || MODE_UNSET == newInputMode) {return mInputIcon;}// 保存新的输入法模式saveInputMode(newInputMode);// 准备切换输入法状态prepareToggleStates(true);return mInputIcon;}/*** 根据编辑框的 EditorInfo 信息获取硬键盘的输入法模式。* * @param editorInfo* @return the icon to update.*/public int requestInputWithHkb(EditorInfo editorInfo) {mShortMessageField = false;boolean english = false;int newInputMode = MODE_HKB_CHINESE;switch (editorInfo.inputType & EditorInfo.TYPE_MASK_CLASS) {case EditorInfo.TYPE_CLASS_NUMBER:case EditorInfo.TYPE_CLASS_PHONE:case EditorInfo.TYPE_CLASS_DATETIME:english = true;break;case EditorInfo.TYPE_CLASS_TEXT:int v = editorInfo.inputType & EditorInfo.TYPE_MASK_VARIATION;if (v == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS|| v == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD|| v == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD|| v == EditorInfo.TYPE_TEXT_VARIATION_URI) {english = true;} else if (v == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {mShortMessageField = true;}break;default:}if (english) {// If the application request English mode, we switch to it.newInputMode = MODE_HKB_ENGLISH;} else {// If the application do not request English mode, we will// try to keep the previous mode to input language text.// Because there is not soft keyboard, we need discard all// soft keyboard related information from the previous language// mode.// 如果最近一次的输入法模式是中文,那么就改为中文。if ((mRecentLauageInputMode & MASK_LANGUAGE) == MASK_LANGUAGE_CN) {newInputMode = MODE_HKB_CHINESE;} else {newInputMode = MODE_HKB_ENGLISH;}}mEditorInfo = editorInfo;saveInputMode(newInputMode);prepareToggleStates(false);return mInputIcon;}/*** 根据编辑框的 EditorInfo 信息获取软键盘的输入法模式。* * @param editorInfo* @return the icon to update.*/public int requestInputWithSkb(EditorInfo editorInfo) {mShortMessageField = false;int newInputMode = MODE_SKB_CHINESE;switch (editorInfo.inputType & EditorInfo.TYPE_MASK_CLASS) {case EditorInfo.TYPE_CLASS_NUMBER:case EditorInfo.TYPE_CLASS_DATETIME:newInputMode = MODE_SKB_SYMBOL1_EN;break;case EditorInfo.TYPE_CLASS_PHONE:newInputMode = MODE_SKB_PHONE_NUM;break;case EditorInfo.TYPE_CLASS_TEXT:int v = editorInfo.inputType & EditorInfo.TYPE_MASK_VARIATION;if (v == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS|| v == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD|| v == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD|| v == EditorInfo.TYPE_TEXT_VARIATION_URI) {// If the application request English mode, we switch to it.newInputMode = MODE_SKB_ENGLISH_LOWER;} else {if (v == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {mShortMessageField = true;}// If the application do not request English mode, we will// try to keep the previous mode.int skbLayout = (mInputMode & MASK_SKB_LAYOUT);newInputMode = mInputMode;if (0 == skbLayout) {if ((mInputMode & MASK_LANGUAGE) == MASK_LANGUAGE_CN) {newInputMode = MODE_SKB_CHINESE;} else {newInputMode = MODE_SKB_ENGLISH_LOWER;}}}break;default:// Try to keep the previous mode.int skbLayout = (mInputMode & MASK_SKB_LAYOUT);newInputMode = mInputMode;if (0 == skbLayout) {if ((mInputMode & MASK_LANGUAGE) == MASK_LANGUAGE_CN) {newInputMode = MODE_SKB_CHINESE;} else {newInputMode = MODE_SKB_ENGLISH_LOWER;}}break;}mEditorInfo = editorInfo;saveInputMode(newInputMode);prepareToggleStates(true);return mInputIcon;}/*** 请求返还上一次输入法模式* * @return*/public int requestBackToPreviousSkb() {int layout = (mInputMode & MASK_SKB_LAYOUT);int lastLayout = (mPreviousInputMode & MASK_SKB_LAYOUT);if (0 != layout && 0 != lastLayout) {// TODO 进行以下代码后 mInputMode 和 mPreviousInputMode 就一样了,这样好吗?mInputMode = mPreviousInputMode;saveInputMode(mInputMode);prepareToggleStates(true);return mInputIcon;}return 0;}/*** 获得中文候选词模式状态* * @return*/public int getTooggleStateForCnCand() {return mToggleStateCnCand;}/*** 是否是硬键盘输入法模式* * @return*/public boolean isEnglishWithHkb() {return MODE_HKB_ENGLISH == mInputMode;}/*** 是否是软件盘英语模式* * @return*/public boolean isEnglishWithSkb() {return MODE_SKB_ENGLISH_LOWER == mInputMode|| MODE_SKB_ENGLISH_UPPER == mInputMode;}/*** 是否是软键盘高(大写)模式* * @return*/public boolean isEnglishUpperCaseWithSkb() {return MODE_SKB_ENGLISH_UPPER == mInputMode;}/*** 是否是中文语言(传统标准软键盘或者硬键盘)。* * @return*/public boolean isChineseText() {int skbLayout = (mInputMode & MASK_SKB_LAYOUT);if (MASK_SKB_LAYOUT_QWERTY == skbLayout || 0 == skbLayout) {int language = (mInputMode & MASK_LANGUAGE);if (MASK_LANGUAGE_CN == language)return true;}return false;}/*** 是否是硬键盘的中文语言* * @return*/public boolean isChineseTextWithHkb() {int skbLayout = (mInputMode & MASK_SKB_LAYOUT);if (0 == skbLayout) {int language = (mInputMode & MASK_LANGUAGE);if (MASK_LANGUAGE_CN == language)return true;}return false;}/*** 是否是软键盘的中文语言* * @return*/public boolean isChineseTextWithSkb() {int skbLayout = (mInputMode & MASK_SKB_LAYOUT);if (MASK_SKB_LAYOUT_QWERTY == skbLayout) {int language = (mInputMode & MASK_LANGUAGE);if (MASK_LANGUAGE_CN == language)return true;}return false;}/*** 是否是软键盘的符号* * @return*/public boolean isSymbolWithSkb() {int skbLayout = (mInputMode & MASK_SKB_LAYOUT);if (MASK_SKB_LAYOUT_SYMBOL1 == skbLayout|| MASK_SKB_LAYOUT_SYMBOL2 == skbLayout) {return true;}return false;}/*** 是否是正常Enter键状态* * @return*/public boolean isEnterNoramlState() {return mEnterKeyNormal;}/*** @param 长按处理* @return*/public boolean tryHandleLongPressSwitch(int keyCode) {if (USERDEF_KEYCODE_LANG_2 == keyCode|| USERDEF_KEYCODE_PHONE_SYM_4 == keyCode) {mImeService.showOptionsMenu();return true;}return false;}/*** 保存新的输入法模式* * @param newInputMode*/private void saveInputMode(int newInputMode) {// 保存当前输入法模式mPreviousInputMode = mInputMode;// 设置新的输入法模式为当前的输入法模式mInputMode = newInputMode;// 输入法模式的布局属性(第8位)int skbLayout = (mInputMode & MASK_SKB_LAYOUT);if (MASK_SKB_LAYOUT_QWERTY == skbLayout || 0 == skbLayout) {// 保存最近的语言输入法模式mRecentLauageInputMode = mInputMode;}// 设置输入法图标mInputIcon = R.drawable.ime_pinyin;if (isEnglishWithHkb()) {mInputIcon = R.drawable.ime_en;} else if (isChineseTextWithHkb()) {mInputIcon = R.drawable.ime_pinyin;}// 如果有硬键盘,就设置输入法模式为未设置状态。if (!Environment.getInstance().hasHardKeyboard()) {mInputIcon = 0;}}/*** 准备设置控制显示的按键切换状态和可显示行ID的对象的数据,封装mToggleStates的数据。* * @param needSkb*/private void prepareToggleStates(boolean needSkb) {mEnterKeyNormal = true;if (!needSkb)return;mToggleStates.mQwerty = false;mToggleStates.mKeyStatesNum = 0;int states[] = mToggleStates.mKeyStates;int statesNum = 0;// Toggle state for language.int language = (mInputMode & MASK_LANGUAGE);int layout = (mInputMode & MASK_SKB_LAYOUT);int charcase = (mInputMode & MASK_CASE);int variation = mEditorInfo.inputType & EditorInfo.TYPE_MASK_VARIATION;// 更加输入法模式添加要显示的按键的切换状态if (MASK_SKB_LAYOUT_PHONE != layout) {if (MASK_LANGUAGE_CN == language) {// Chinese and Chinese symbol are always the default states,// do not add a toggling operation.if (MASK_SKB_LAYOUT_QWERTY == layout) {mToggleStates.mQwerty = true;mToggleStates.mQwertyUpperCase = true;if (mShortMessageField) {states[statesNum] = mToggleStateSmiley;statesNum++;}}} else if (MASK_LANGUAGE_EN == language) {if (MASK_SKB_LAYOUT_QWERTY == layout) {mToggleStates.mQwerty = true;mToggleStates.mQwertyUpperCase = false;states[statesNum] = mToggleStateEnLower;if (MASK_CASE_UPPER == charcase) {mToggleStates.mQwertyUpperCase = true;states[statesNum] = mToggleStateEnUpper;}statesNum++;} else if (MASK_SKB_LAYOUT_SYMBOL1 == layout) {states[statesNum] = mToggleStateEnSym1;statesNum++;} else if (MASK_SKB_LAYOUT_SYMBOL2 == layout) {states[statesNum] = mToggleStateEnSym2;statesNum++;}}// Toggle rows for QWERTY.mToggleStates.mRowIdToEnable = KeyRow.DEFAULT_ROW_ID;if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {mToggleStates.mRowIdToEnable = mToggleRowEmailAddress;} else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {mToggleStates.mRowIdToEnable = mToggleRowUri;} else if (MASK_LANGUAGE_CN == language) {mToggleStates.mRowIdToEnable = mToggleRowCn;} else if (MASK_LANGUAGE_EN == language) {mToggleStates.mRowIdToEnable = mToggleRowEn;}} else {if (MASK_CASE_UPPER == charcase) {states[statesNum] = mToggleStatePhoneSym;statesNum++;}}// Toggle state for enter key.// 根据EditorInfo.imeOptions添加 要显示的按键的切换状态 ,以下只添加 Enter 键的切换状态。int action = mEditorInfo.imeOptions& (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);if (action == EditorInfo.IME_ACTION_GO) {states[statesNum] = mToggleStateGo;statesNum++;mEnterKeyNormal = false;} else if (action == EditorInfo.IME_ACTION_SEARCH) {states[statesNum] = mToggleStateSearch;statesNum++;mEnterKeyNormal = false;} else if (action == EditorInfo.IME_ACTION_SEND) {states[statesNum] = mToggleStateSend;statesNum++;mEnterKeyNormal = false;} else if (action == EditorInfo.IME_ACTION_NEXT) {int f = mEditorInfo.inputType & EditorInfo.TYPE_MASK_FLAGS;if (f != EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {states[statesNum] = mToggleStateNext;statesNum++;mEnterKeyNormal = false;}} else if (action == EditorInfo.IME_ACTION_DONE) {states[statesNum] = mToggleStateDone;statesNum++;mEnterKeyNormal = false;}mToggleStates.mKeyStatesNum = statesNum;}
}

9、KeyMapDream.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import android.view.KeyEvent;/*** Class used to map the symbols on Dream's hardware keyboard to corresponding* Chinese full-width symbols.* * 硬键盘符号对应表,对应中文全角符号。*/
public class KeyMapDream {// Number of shift bits to store full-width symbolsprivate static final int SHIFT_FWCH = 8;private static final int[] mKeyMap = {KeyEvent.KEYCODE_UNKNOWN,KeyEvent.KEYCODE_SOFT_LEFT,KeyEvent.KEYCODE_SOFT_RIGHT,KeyEvent.KEYCODE_HOME,KeyEvent.KEYCODE_BACK,KeyEvent.KEYCODE_CALL,KeyEvent.KEYCODE_ENDCALL,KeyEvent.KEYCODE_0 | ('\uff09' << SHIFT_FWCH), // )KeyEvent.KEYCODE_1 | ('\uff01' << SHIFT_FWCH), // !KeyEvent.KEYCODE_2 | ('\uff20' << SHIFT_FWCH), // @KeyEvent.KEYCODE_3 | ('\uff03' << SHIFT_FWCH), // #KeyEvent.KEYCODE_4 | ('\uffe5' << SHIFT_FWCH), // $ - fullwidth YuanKeyEvent.KEYCODE_5 | ('\uff05' << SHIFT_FWCH), // %KeyEvent.KEYCODE_6 | ('\u2026' << SHIFT_FWCH), // ^ - ApostropheKeyEvent.KEYCODE_7 | ('\uff06' << SHIFT_FWCH), // &KeyEvent.KEYCODE_8 | ('\uff0a' << SHIFT_FWCH), // *KeyEvent.KEYCODE_9 | ('\uff08' << SHIFT_FWCH), // (KeyEvent.KEYCODE_STAR,KeyEvent.KEYCODE_POUND,KeyEvent.KEYCODE_DPAD_UP,KeyEvent.KEYCODE_DPAD_DOWN,KeyEvent.KEYCODE_DPAD_LEFT,KeyEvent.KEYCODE_DPAD_RIGHT,KeyEvent.KEYCODE_DPAD_CENTER,KeyEvent.KEYCODE_VOLUME_UP,KeyEvent.KEYCODE_VOLUME_DOWN,KeyEvent.KEYCODE_POWER,KeyEvent.KEYCODE_CAMERA,KeyEvent.KEYCODE_CLEAR,KeyEvent.KEYCODE_A,KeyEvent.KEYCODE_B | ('\uff3d' << SHIFT_FWCH), // ]KeyEvent.KEYCODE_C | ('\u00a9' << SHIFT_FWCH), // copyrightKeyEvent.KEYCODE_D | ('\u3001' << SHIFT_FWCH), // \\KeyEvent.KEYCODE_E | ('_' << SHIFT_FWCH), // _KeyEvent.KEYCODE_F | ('\uff5b' << SHIFT_FWCH), // {KeyEvent.KEYCODE_G | ('\uff5d' << SHIFT_FWCH), // }KeyEvent.KEYCODE_H | ('\uff1a' << SHIFT_FWCH), // :KeyEvent.KEYCODE_I | ('\uff0d' << SHIFT_FWCH), // -KeyEvent.KEYCODE_J | ('\uff1b' << SHIFT_FWCH), // ;KeyEvent.KEYCODE_K | ('\u201c' << SHIFT_FWCH), // "KeyEvent.KEYCODE_L | ('\u2019' << SHIFT_FWCH), // 'KeyEvent.KEYCODE_M | ('\u300b' << SHIFT_FWCH), // > - French quotesKeyEvent.KEYCODE_N | ('\u300a' << SHIFT_FWCH), // < - French quotesKeyEvent.KEYCODE_O | ('\uff0b' << SHIFT_FWCH), // +KeyEvent.KEYCODE_P | ('\uff1d' << SHIFT_FWCH), // =KeyEvent.KEYCODE_Q | ('\t' << SHIFT_FWCH), // \tKeyEvent.KEYCODE_R | ('\u00ae' << SHIFT_FWCH), // trademarkKeyEvent.KEYCODE_S | ('\uff5c' << SHIFT_FWCH), // |KeyEvent.KEYCODE_T | ('\u20ac' << SHIFT_FWCH), //KeyEvent.KEYCODE_U | ('\u00d7' << SHIFT_FWCH), // multiplierKeyEvent.KEYCODE_V | ('\uff3b' << SHIFT_FWCH), // [KeyEvent.KEYCODE_W | ('\uff40' << SHIFT_FWCH), // `KeyEvent.KEYCODE_X, KeyEvent.KEYCODE_Y | ('\u00f7' << SHIFT_FWCH),KeyEvent.KEYCODE_Z,KeyEvent.KEYCODE_COMMA | ('\uff1f' << SHIFT_FWCH),KeyEvent.KEYCODE_PERIOD | ('\uff0f' << SHIFT_FWCH),KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT,KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT,KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_SYM,KeyEvent.KEYCODE_EXPLORER, KeyEvent.KEYCODE_ENVELOPE,KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_DEL,KeyEvent.KEYCODE_GRAVE, KeyEvent.KEYCODE_MINUS,KeyEvent.KEYCODE_EQUALS, KeyEvent.KEYCODE_LEFT_BRACKET,KeyEvent.KEYCODE_RIGHT_BRACKET, KeyEvent.KEYCODE_BACKSLASH,KeyEvent.KEYCODE_SEMICOLON, KeyEvent.KEYCODE_APOSTROPHE,KeyEvent.KEYCODE_SLASH,KeyEvent.KEYCODE_AT | ('\uff5e' << SHIFT_FWCH),KeyEvent.KEYCODE_NUM, KeyEvent.KEYCODE_HEADSETHOOK,KeyEvent.KEYCODE_FOCUS, KeyEvent.KEYCODE_PLUS,KeyEvent.KEYCODE_MENU, KeyEvent.KEYCODE_NOTIFICATION,KeyEvent.KEYCODE_SEARCH, };/*** 获取中文全角字符* * @param keyCode* @return*/static public char getChineseLabel(int keyCode) {if (keyCode <= 0 || keyCode >= KeyEvent.getMaxKeyCode())return 0;assert ((mKeyMap[keyCode] & 0x000000ff) == keyCode);return (char) (mKeyMap[keyCode] >> SHIFT_FWCH);}
}

10、MyReceiver.java:

package com.keanbin.pinyinime;import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;/*** 广播接收器,接收android.intent.idatachina.RFID.BARCODE.SCANINFO的广播,取出Intent中字段* "idatachina.SCAN_DATA"存储的数据,调用拼音服务PinyinIME发送给EditText* * @ClassName MyReceiver* @author keanbin*/
public class MyReceiver extends BroadcastReceiver {PinyinIME ss = new PinyinIME();public void onReceive(Context context, Intent intent) {// TODO Auto-generated method stub// MainActivity.onrecvintend(intent);String tinfo = intent.getStringExtra("idatachina.SCAN_DATA");ss.pinyinIME.SetText(tinfo);}
}

11、PinyinDecoderService.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Vector;import android.app.Service;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.os.IBinder;
import android.util.Log;/*** This class is used to separate the input method kernel in an individual* service so that both IME and IME-syncer can use it.* * 词库解码JNI函数服务* * @ClassName PinyinDecoderService* @author keanbin*/
public class PinyinDecoderService extends Service {native static boolean nativeImOpenDecoder(byte fn_sys_dict[],byte fn_usr_dict[]);/*** JNI函数:打开解码器* * @param fd* @param startOffset* @param length* @param fn_usr_dict* @return*/native static boolean nativeImOpenDecoderFd(FileDescriptor fd,long startOffset, long length, byte fn_usr_dict[]);/*** JNI函数:设置最大的长度* * @param maxSpsLen* @param maxHzsLen*/native static void nativeImSetMaxLens(int maxSpsLen, int maxHzsLen);/*** JNI函数:关闭解码器* * @return*/native static boolean nativeImCloseDecoder();/*** JNI函数:根据拼音查询候选词* * @param pyBuf* @param pyLen* @return*/native static int nativeImSearch(byte pyBuf[], int pyLen);/*** JNI函数:删除指定位置的拼音后进行查询* * @param pos* @param is_pos_in_splid* @param clear_fixed_this_step* @return*/native static int nativeImDelSearch(int pos, boolean is_pos_in_splid,boolean clear_fixed_this_step);/*** JNI函数:重置拼音查询,应该是清除之前查询的数据*/native static void nativeImResetSearch();/*** JNI函数:增加字母。* * @备注 目前没有使用。* @param ch* @return*/native static int nativeImAddLetter(byte ch);/*** JNI函数:获取拼音字符串* * @param decoded* @return*/native static String nativeImGetPyStr(boolean decoded);/*** JNI函数:获取拼音字符串的长度* * @param decoded* @return*/native static int nativeImGetPyStrLen(boolean decoded);/*** JNI函数:获取每个拼写的开始位置,猜测:第一个元素是拼写的总数量?* * @return*/native static int[] nativeImGetSplStart();/*** JNI函数:获取指定位置的候选词* * @param choiceId* @return*/native static String nativeImGetChoice(int choiceId);/*** JNI函数:获取候选词的数量* * @param choiceId* @return*/native static int nativeImChoose(int choiceId);/*** JNI函数:取消最后的选择* * @备注 目前没有使用* @return*/native static int nativeImCancelLastChoice();/*** JNI函数:获取固定字符的长度* * @return*/native static int nativeImGetFixedLen();/*** JNI函数:取消输入* * @备注 目前没有使用* @return*/native static boolean nativeImCancelInput();/*** JNI函数:刷新缓存* * @备注 目前没有使用* @return*/native static boolean nativeImFlushCache();/*** JNI函数:根据字符串 fixedStr 获取预报的候选词* * @param fixedStr* @return*/native static int nativeImGetPredictsNum(String fixedStr);/*** JNI函数:获取指定位置的预报候选词* * @param predictNo* @return*/native static String nativeImGetPredictItem(int predictNo);// Sync related/*** JNI函数:同步到用户词典,猜测:是不是记住用户的常用词。* * @备注 目前没有使用* @param user_dict* @param tomerge* @return*/native static String nativeSyncUserDict(byte[] user_dict, String tomerge);/*** JNI函数:开始用户词典同步* * @备注 目前没有使用* @param user_dict* @return*/native static boolean nativeSyncBegin(byte[] user_dict);/*** JNI函数:同步结束* * @备注 目前没有使用* @return*/native static boolean nativeSyncFinish();/*** JNI函数:同步获取Lemmas* * @备注 目前没有使用* @return*/native static String nativeSyncGetLemmas();/*** JNI函数:同步存入Lemmas* * @备注 目前没有使用* @param tomerge* @return*/native static int nativeSyncPutLemmas(String tomerge);/*** JNI函数:同步获取最后的数量* * @备注 目前没有使用* @return*/native static int nativeSyncGetLastCount();/*** JNI函数:同步获取总数量* * @备注 目前没有使用* @return*/native static int nativeSyncGetTotalCount();/*** JNI函数:同步清空最后获取* * @备注 目前没有使用* @return*/native static boolean nativeSyncClearLastGot();/*** JNI函数:同步获取容量* * @备注 目前没有使用* @return*/native static int nativeSyncGetCapacity();/*** 最大的文件路径长度*/private final static int MAX_PATH_FILE_LENGTH = 100;/*** 是否完成初始化*/private static boolean inited = false;/*** 用户的词典文件*/private String mUsr_dict_file;// 导入本地函数库static {try {System.loadLibrary("jni_pinyinime");} catch (UnsatisfiedLinkError ule) {Log.e("PinyinDecoderService","WARNING: Could not load jni_pinyinime natives");}}/*** Get file name of the specified dictionary 获取用户词典的文件名* * @param usr_dict* @return*/private boolean getUsrDictFileName(byte usr_dict[]) {if (null == usr_dict) {return false;}for (int i = 0; i < mUsr_dict_file.length(); i++)usr_dict[i] = (byte) mUsr_dict_file.charAt(i);usr_dict[mUsr_dict_file.length()] = 0;return true;}/*** 初始化拼音引擎*/private void initPinyinEngine() {byte usr_dict[];usr_dict = new byte[MAX_PATH_FILE_LENGTH];// Here is how we open a built-in dictionary for access through// a file descriptor...// 获取词库 R.raw.dict_pinyin 的文件描述符AssetFileDescriptor afd = getResources().openRawResourceFd(R.raw.dict_pinyin);if (Environment.getInstance().needDebug()) {Log.i("foo", "Dict: start=" + afd.getStartOffset() + ", length="+ afd.getLength() + ", fd=" + afd.getParcelFileDescriptor());}if (getUsrDictFileName(usr_dict)) {// JNI函数:打开解码器inited = nativeImOpenDecoderFd(afd.getFileDescriptor(),afd.getStartOffset(), afd.getLength(), usr_dict);}try {afd.close();} catch (IOException e) {}}@Overridepublic void onCreate() {super.onCreate();// 获取用户词典"usr_dict.dat"的路径。"usr_dict.dat"放在file目录下。// 猜测:调用getFileStreamPath("usr_dict.dat"),如果"usr_dict.dat"不存在,会调用openFileOutput()创建该文件。mUsr_dict_file = getFileStreamPath("usr_dict.dat").getPath();// This is a hack to make sure our "files" directory has been// created.try {openFileOutput("dummy", 0).close();} catch (FileNotFoundException e) {} catch (IOException e) {}initPinyinEngine();}@Overridepublic void onDestroy() {// JNI函数:关闭解码器nativeImCloseDecoder();inited = false;super.onDestroy();}/*** 给外部调用的接口*/private final IPinyinDecoderService.Stub mBinder = new IPinyinDecoderService.Stub() {/*** 返回12345*/public int getInt() {return 12345;}/*** 设置最大的长度*/public void setMaxLens(int maxSpsLen, int maxHzsLen) {nativeImSetMaxLens(maxSpsLen, maxHzsLen);}/*** 根据拼音查询候选词*/public int imSearch(byte[] pyBuf, int pyLen) {return nativeImSearch(pyBuf, pyLen);}/*** 删除指定位置的拼音后进行查询*/public int imDelSearch(int pos, boolean is_pos_in_splid,boolean clear_fixed_this_step) {return nativeImDelSearch(pos, is_pos_in_splid,clear_fixed_this_step);}/*** 重置拼音查询,应该是清除之前查询的数据*/public void imResetSearch() {nativeImResetSearch();}/*** 增加字母。* * @备注 目前没有使用。*/public int imAddLetter(byte ch) {return nativeImAddLetter(ch);}/*** 获取拼音字符串*/public String imGetPyStr(boolean decoded) {return nativeImGetPyStr(decoded);}/*** 获取拼音字符串的长度*/public int imGetPyStrLen(boolean decoded) {return nativeImGetPyStrLen(decoded);}/*** 获取每个拼写的开始位置,猜测:第一个元素是拼写的总数量?*/public int[] imGetSplStart() {return nativeImGetSplStart();}/*** 获取指定位置的候选词*/public String imGetChoice(int choiceId) {return nativeImGetChoice(choiceId);}/*** 获取多个候选词* * @备注 目前没有使用。*/public String imGetChoices(int choicesNum) {String retStr = null;for (int i = 0; i < choicesNum; i++) {if (null == retStr)retStr = nativeImGetChoice(i);elseretStr += " " + nativeImGetChoice(i);}return retStr;}/*** 获取候选词列表。choicesStart位置的候选词从sentFixedLen开始截取。*/public List<String> imGetChoiceList(int choicesStart, int choicesNum,int sentFixedLen) {Vector<String> choiceList = new Vector<String>();for (int i = choicesStart; i < choicesStart + choicesNum; i++) {String retStr = nativeImGetChoice(i);if (0 == i)retStr = retStr.substring(sentFixedLen);choiceList.add(retStr);}return choiceList;}/*** 获取候选词的数量*/public int imChoose(int choiceId) {return nativeImChoose(choiceId);}/*** 取消最后的选择* * @备注 目前没有使用*/public int imCancelLastChoice() {return nativeImCancelLastChoice();}/*** 获取固定字符的长度*/public int imGetFixedLen() {return nativeImGetFixedLen();}/*** 取消输入* * @备注 目前没有使用*/public boolean imCancelInput() {return nativeImCancelInput();}/*** 刷新缓存* * @备注 目前没有使用*/public void imFlushCache() {nativeImFlushCache();}/*** 根据字符串 fixedStr 获取预报的候选词*/public int imGetPredictsNum(String fixedStr) {return nativeImGetPredictsNum(fixedStr);}/*** 获取指定位置的预报候选词*/public String imGetPredictItem(int predictNo) {return nativeImGetPredictItem(predictNo);}/*** 获取候选词列表*/public List<String> imGetPredictList(int predictsStart, int predictsNum) {Vector<String> predictList = new Vector<String>();for (int i = predictsStart; i < predictsStart + predictsNum; i++) {predictList.add(nativeImGetPredictItem(i));}return predictList;}/*** 同步到用户词典,猜测:是不是记住用户的常用词。* * @备注 目前没有使用*/public String syncUserDict(String tomerge) {byte usr_dict[];usr_dict = new byte[MAX_PATH_FILE_LENGTH];if (getUsrDictFileName(usr_dict)) {return nativeSyncUserDict(usr_dict, tomerge);}return null;}/*** 开始用户词典同步* * @备注 目前没有使用*/public boolean syncBegin() {byte usr_dict[];usr_dict = new byte[MAX_PATH_FILE_LENGTH];if (getUsrDictFileName(usr_dict)) {return nativeSyncBegin(usr_dict);}return false;}/*** 同步结束* * @备注 目前没有使用*/public void syncFinish() {nativeSyncFinish();}/*** 同步存入Lemmas* * @备注 目前没有使用*/public int syncPutLemmas(String tomerge) {return nativeSyncPutLemmas(tomerge);}/*** 同步获取Lemmas* * @备注 目前没有使用*/public String syncGetLemmas() {return nativeSyncGetLemmas();}/*** 同步获取最后的数量* * @备注 目前没有使用*/public int syncGetLastCount() {return nativeSyncGetLastCount();}/*** 同步获取总数量* * @备注 目前没有使用*/public int syncGetTotalCount() {return nativeSyncGetTotalCount();}/*** 同步清空最后获取* * @备注 目前没有使用*/public void syncClearLastGot() {nativeSyncClearLastGot();}/*** 同步获取容量* * @备注 目前没有使用*/public int imSyncGetCapacity() {return nativeSyncGetCapacity();}};@Overridepublic IBinder onBind(Intent intent) {return mBinder;}
}

12、PinyinIME.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import java.util.ArrayList;
import java.util.List;
import java.util.Vector;import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Configuration;
import android.inputmethodservice.InputMethodService;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.LinearLayout;
import android.widget.PopupWindow;/*** Main class of the Pinyin input method. 输入法服务*/
public class PinyinIME extends InputMethodService {/*** TAG for debug.*/static final String TAG = "PinyinIME";static PinyinIME pinyinIME;/*** If is is true, IME will simulate key events for delete key, and send the* events back to the application.*/private static final boolean SIMULATE_KEY_DELETE = true;/*** Necessary environment configurations like screen size for this IME.* 该对象保存了布局的一些尺寸,它的类是单例模式。*/private Environment mEnvironment;/*** Used to switch input mode. 输入法状态变换器*/private InputModeSwitcher mInputModeSwitcher;/*** Soft keyboard container view to host real soft keyboard view. 软键盘集装箱*/private SkbContainer mSkbContainer;/*** The floating container which contains the composing view. If necessary,* some other view like candiates container can also be put here. 浮动视图集装箱*/private LinearLayout mFloatingContainer;/*** View to show the composing string. 组成字符串的View,用于显示输入的拼音。*/private ComposingView mComposingView;/*** Window to show the composing string. 用于输入拼音字符串的窗口。*/private PopupWindow mFloatingWindow;/*** Used to show the floating window. 显示输入的拼音字符串PopupWindow 定时器*/private PopupTimer mFloatingWindowTimer = new PopupTimer();/*** View to show candidates list. 候选词视图集装箱*/private CandidatesContainer mCandidatesContainer;/*** Balloon used when user presses a candidate. 候选词气泡*/private BalloonHint mCandidatesBalloon;/*** Used to notify the input method when the user touch a candidate.* 当用户选择了候选词或者在候选词视图滑动了手势时的通知输入法。 实现了候选词视图的监听器CandidateViewListener。*/private ChoiceNotifier mChoiceNotifier;/*** Used to notify gestures from soft keyboard. 软键盘的手势监听器*/private OnGestureListener mGestureListenerSkb;/*** Used to notify gestures from candidates view. 候选词的手势监听器*/private OnGestureListener mGestureListenerCandidates;/*** The on-screen movement gesture detector for soft keyboard. 软键盘的手势检测器*/private GestureDetector mGestureDetectorSkb;/*** The on-screen movement gesture detector for candidates view. 候选词的手势检测器*/private GestureDetector mGestureDetectorCandidates;/*** Option dialog to choose settings and other IMEs. 功能对话框*/private AlertDialog mOptionsDialog;/*** Connection used to bind the decoding service. 链接* 词库解码远程服务PinyinDecoderService 的监听器*/private PinyinDecoderServiceConnection mPinyinDecoderServiceConnection;/*** The current IME status. 当前的输入法状态* * @see com.android.inputmethod.pinyin.PinyinIME.ImeState*/private ImeState mImeState = ImeState.STATE_IDLE;/*** The decoding information, include spelling(Pinyin) string, decoding* result, etc. 词库解码操作对象*/private DecodingInfo mDecInfo = new DecodingInfo();/*** For English input. 英文输入法按键处理器* */private EnglishInputProcessor mImEn;// receive ringer mode changes/*** 声音模式改变时的广播接收器*/private BroadcastReceiver mReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {SoundManager.getInstance(context).updateRingerMode();}};@Overridepublic void onCreate() {mEnvironment = Environment.getInstance();if (mEnvironment.needDebug()) {Log.d(TAG, "onCreate.");}super.onCreate();pinyinIME = this;// 绑定词库解码远程服务PinyinDecoderServicestartPinyinDecoderService();mImEn = new EnglishInputProcessor();Settings.getInstance(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));mInputModeSwitcher = new InputModeSwitcher(this);mChoiceNotifier = new ChoiceNotifier(this);mGestureListenerSkb = new OnGestureListener(false);mGestureListenerCandidates = new OnGestureListener(true);mGestureDetectorSkb = new GestureDetector(this, mGestureListenerSkb);mGestureDetectorCandidates = new GestureDetector(this,mGestureListenerCandidates);mEnvironment.onConfigurationChanged(getResources().getConfiguration(),this);}@Overridepublic void onDestroy() {if (mEnvironment.needDebug()) {Log.d(TAG, "onDestroy.");}// 解绑定词库解码远程服务PinyinDecoderServiceunbindService(mPinyinDecoderServiceConnection);// 释放设置类的引用Settings.releaseInstance();super.onDestroy();}@Overridepublic void onConfigurationChanged(Configuration newConfig) {Environment env = Environment.getInstance();if (mEnvironment.needDebug()) {Log.d(TAG, "onConfigurationChanged");Log.d(TAG, "--last config: " + env.getConfiguration().toString());Log.d(TAG, "---new config: " + newConfig.toString());}// We need to change the local environment first so that UI components// can get the environment instance to handle size issues. When// super.onConfigurationChanged() is called, onCreateCandidatesView()// and onCreateInputView() will be executed if necessary.env.onConfigurationChanged(newConfig, this);// Clear related UI of the previous configuration.if (null != mSkbContainer) {mSkbContainer.dismissPopups();}if (null != mCandidatesBalloon) {mCandidatesBalloon.dismiss();}super.onConfigurationChanged(newConfig);// 重置到空闲状态resetToIdleState(false);}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {if (processKey(event, 0 != event.getRepeatCount()))return true;return super.onKeyDown(keyCode, event);}@Overridepublic boolean onKeyUp(int keyCode, KeyEvent event) {if (processKey(event, true))return true;return super.onKeyUp(keyCode, event);}/*** 给EditText发送文本,在广播接收器MyReceiver的onReceive()中调用。* * @param text*/public void SetText(CharSequence text) {InputConnection ic = getCurrentInputConnection();if (ic == null)return;ic.beginBatchEdit();ic.commitText(text, 0);ic.endBatchEdit();}/*** 按键处理函数* * @param event* @param realAction* @return*/private boolean processKey(KeyEvent event, boolean realAction) {if (ImeState.STATE_BYPASS == mImeState)return false;int keyCode = event.getKeyCode();// SHIFT-SPACE is used to switch between Chinese and English// when HKB is on.// SHIFT + SPACE 按键组合处理if (KeyEvent.KEYCODE_SPACE == keyCode && event.isShiftPressed()) {if (!realAction)return true;updateIcon(mInputModeSwitcher.switchLanguageWithHkb());resetToIdleState(false);// 清除alt shift sym 键按住的状态int allMetaState = KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON| KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_SHIFT_ON| KeyEvent.META_SHIFT_LEFT_ON| KeyEvent.META_SHIFT_RIGHT_ON | KeyEvent.META_SYM_ON;getCurrentInputConnection().clearMetaKeyStates(allMetaState);return true;}// If HKB is on to input English, by-pass the key event so that// default key listener will handle it.// 如果是硬键盘英文输入状态,就忽略掉该按键,让默认的按键监听器去处理它。if (mInputModeSwitcher.isEnglishWithHkb()) {return false;}// 功能键处理if (processFunctionKeys(keyCode, realAction)) {return true;}int keyChar = 0;if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {keyChar = keyCode - KeyEvent.KEYCODE_A + 'a';} else if (keyCode >= KeyEvent.KEYCODE_0&& keyCode <= KeyEvent.KEYCODE_9) {keyChar = keyCode - KeyEvent.KEYCODE_0 + '0';} else if (keyCode == KeyEvent.KEYCODE_COMMA) {keyChar = ',';} else if (keyCode == KeyEvent.KEYCODE_PERIOD) {keyChar = '.';} else if (keyCode == KeyEvent.KEYCODE_SPACE) {keyChar = ' ';} else if (keyCode == KeyEvent.KEYCODE_APOSTROPHE) {keyChar = '\'';}if (mInputModeSwitcher.isEnglishWithSkb()) {// 英语软键盘处理return mImEn.processKey(getCurrentInputConnection(), event,mInputModeSwitcher.isEnglishUpperCaseWithSkb(), realAction);} else if (mInputModeSwitcher.isChineseText()) {// 中文输入法模式if (mImeState == ImeState.STATE_IDLE|| mImeState == ImeState.STATE_APP_COMPLETION) {mImeState = ImeState.STATE_IDLE;return processStateIdle(keyChar, keyCode, event, realAction);} else if (mImeState == ImeState.STATE_INPUT) {return processStateInput(keyChar, keyCode, event, realAction);} else if (mImeState == ImeState.STATE_PREDICT) {return processStatePredict(keyChar, keyCode, event, realAction);} else if (mImeState == ImeState.STATE_COMPOSING) {return processStateEditComposing(keyChar, keyCode, event,realAction);}} else {// 符号处理if (0 != keyChar && realAction) {// 发送文本给EditTextcommitResultText(String.valueOf((char) keyChar));}}return false;}// keyCode can be from both hard key or soft key./*** 功能键处理函数* * @param keyCode* @param realAction* @return*/private boolean processFunctionKeys(int keyCode, boolean realAction) {// Back key is used to dismiss all popup UI in a soft keyboard.// 后退键的处理。副软键盘弹出框显示的时候,如果realAction为true,那么就调用dismissPopupSkb()隐藏副软键盘弹出框,显示主软键盘视图。if (keyCode == KeyEvent.KEYCODE_BACK) {if (isInputViewShown()) {if (mSkbContainer.handleBack(realAction))return true;}}// Chinese related input is handle separately.// 中文相关输入是单独处理的,不在这边处理。if (mInputModeSwitcher.isChineseText()) {return false;}if (null != mCandidatesContainer && mCandidatesContainer.isShown()&& !mDecInfo.isCandidatesListEmpty()) {// 候选词视图显示的时候if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {if (!realAction)return true;// 选择当前高亮的候选词chooseCandidate(-1);return true;}if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {if (!realAction)return true;// 高亮位置向上一个候选词移动或者移动到上一页的最后一个候选词的位置。mCandidatesContainer.activeCurseBackward();return true;}if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {if (!realAction)return true;// 高亮位置向下一个候选词移动或者移动到下一页的第一个候选词的位置。mCandidatesContainer.activeCurseForward();return true;}if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {if (!realAction)return true;// 到上一页候选词mCandidatesContainer.pageBackward(false, true);return true;}if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {if (!realAction)return true;// 到下一页候选词mCandidatesContainer.pageForward(false, true);return true;}// 在预报状态下的删除键处理if (keyCode == KeyEvent.KEYCODE_DEL&& ImeState.STATE_PREDICT == mImeState) {if (!realAction)return true;resetToIdleState(false);return true;}} else {// 没有候选词显示的时候if (keyCode == KeyEvent.KEYCODE_DEL) {if (!realAction)return true;if (SIMULATE_KEY_DELETE) {// 给EditText发送一个删除按键的按下和弹起事件。simulateKeyEventDownUp(keyCode);} else {// 发送删除一个字符的操作给EditTextgetCurrentInputConnection().deleteSurroundingText(1, 0);}return true;}if (keyCode == KeyEvent.KEYCODE_ENTER) {if (!realAction)return true;// 发送Enter键给EditTextsendKeyChar('\n');return true;}if (keyCode == KeyEvent.KEYCODE_SPACE) {if (!realAction)return true;// 发送' '字符给EditTextsendKeyChar(' ');return true;}}return false;}/*** 当 mImeState == ImeState.STATE_IDLE 或者 mImeState ==* ImeState.STATE_APP_COMPLETION 时的按键处理函数* * @param keyChar* @param keyCode* @param event* @param realAction* @return*/private boolean processStateIdle(int keyChar, int keyCode, KeyEvent event,boolean realAction) {// In this status, when user presses keys in [a..z], the status will// change to input state.if (keyChar >= 'a' && keyChar <= 'z' && !event.isAltPressed()) {if (!realAction)return true;mDecInfo.addSplChar((char) keyChar, true);// 对输入的拼音进行查询chooseAndUpdate(-1);return true;} else if (keyCode == KeyEvent.KEYCODE_DEL) {if (!realAction)return true;if (SIMULATE_KEY_DELETE) {// 模拟删除键发送给 EditTextsimulateKeyEventDownUp(keyCode);} else {// 发送删除一个字符的操作给 EditTextgetCurrentInputConnection().deleteSurroundingText(1, 0);}return true;} else if (keyCode == KeyEvent.KEYCODE_ENTER) {if (!realAction)return true;// 发送 ENTER 键给 EditTextsendKeyChar('\n');return true;} else if (keyCode == KeyEvent.KEYCODE_ALT_LEFT|| keyCode == KeyEvent.KEYCODE_ALT_RIGHT|| keyCode == KeyEvent.KEYCODE_SHIFT_LEFT|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {return true;} else if (event.isAltPressed()) {// 获取中文全角字符char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);if (0 != fullwidth_char) {if (realAction) {String result = String.valueOf(fullwidth_char);commitResultText(result);}return true;} else {if (keyCode >= KeyEvent.KEYCODE_A&& keyCode <= KeyEvent.KEYCODE_Z) {return true;}}} else if (keyChar != 0 && keyChar != '\t') {if (realAction) {if (keyChar == ',' || keyChar == '.') {// 发送 '\uff0c' 或者 '\u3002' 给EditTextinputCommaPeriod("", keyChar, false, ImeState.STATE_IDLE);} else {if (0 != keyChar) {String result = String.valueOf((char) keyChar);commitResultText(result);}}}return true;}return false;}/*** 当 mImeState == ImeState.STATE_INPUT 时的按键处理函数* * @param keyChar* @param keyCode* @param event* @param realAction* @return*/private boolean processStateInput(int keyChar, int keyCode, KeyEvent event,boolean realAction) {// If ALT key is pressed, input alternative key. But if the// alternative key is quote key, it will be used for input a splitter// in Pinyin string.// 如果 ALT 被按住if (event.isAltPressed()) {if ('\'' != event.getUnicodeChar(event.getMetaState())) {if (realAction) {// 获取中文全角字符char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);if (0 != fullwidth_char) {// 发送高亮的候选词 + 中文全角字符 给 EditViewcommitResultText(mDecInfo.getCurrentFullSent(mCandidatesContainer.getActiveCandiatePos())+ String.valueOf(fullwidth_char));resetToIdleState(false);}}return true;} else {keyChar = '\'';}}if (keyChar >= 'a' && keyChar <= 'z' || keyChar == '\''&& !mDecInfo.charBeforeCursorIsSeparator()|| keyCode == KeyEvent.KEYCODE_DEL) {if (!realAction)return true;// 添加输入的拼音,然后进行词库查询,或者删除输入的拼音指定的字符或字符串,然后进行词库查询。return processSurfaceChange(keyChar, keyCode);} else if (keyChar == ',' || keyChar == '.') {if (!realAction)return true;// 发送 '\uff0c' 或者 '\u3002' 给EditTextinputCommaPeriod(mDecInfo.getCurrentFullSent(mCandidatesContainer.getActiveCandiatePos()), keyChar, true,ImeState.STATE_IDLE);return true;} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP|| keyCode == KeyEvent.KEYCODE_DPAD_DOWN|| keyCode == KeyEvent.KEYCODE_DPAD_LEFT|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {if (!realAction)return true;if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {// 高亮位置向上一个候选词移动或者移动到上一页的最后一个候选词的位置。mCandidatesContainer.activeCurseBackward();} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {// 高亮位置向下一个候选词移动或者移动到下一页的第一个候选词的位置。mCandidatesContainer.activeCurseForward();} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {// If it has been the first page, a up key will shift// the state to edit composing string.// 到上一页候选词if (!mCandidatesContainer.pageBackward(false, true)) {mCandidatesContainer.enableActiveHighlight(false);changeToStateComposing(true);updateComposingText(true);}} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {// 到下一页候选词mCandidatesContainer.pageForward(false, true);}return true;} else if (keyCode >= KeyEvent.KEYCODE_1&& keyCode <= KeyEvent.KEYCODE_9) {if (!realAction)return true;int activePos = keyCode - KeyEvent.KEYCODE_1;int currentPage = mCandidatesContainer.getCurrentPage();if (activePos < mDecInfo.getCurrentPageSize(currentPage)) {activePos = activePos+ mDecInfo.getCurrentPageStart(currentPage);if (activePos >= 0) {// 选择候选词,并根据条件是否进行下一步的预报。chooseAndUpdate(activePos);}}return true;} else if (keyCode == KeyEvent.KEYCODE_ENTER) {if (!realAction)return true;if (mInputModeSwitcher.isEnterNoramlState()) {// 把输入的拼音字符串发送给EditTextcommitResultText(mDecInfo.getOrigianlSplStr().toString());resetToIdleState(false);} else {// 把高亮的候选词发送给EditTextcommitResultText(mDecInfo.getCurrentFullSent(mCandidatesContainer.getActiveCandiatePos()));// 把ENTER发送给EditTextsendKeyChar('\n');resetToIdleState(false);}return true;} else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER|| keyCode == KeyEvent.KEYCODE_SPACE) {if (!realAction)return true;// 选择高亮的候选词chooseCandidate(-1);return true;} else if (keyCode == KeyEvent.KEYCODE_BACK) {if (!realAction)return true;resetToIdleState(false);// 关闭输入法requestHideSelf(0);return true;}return false;}/*** 当 mImeState == ImeState.STATE_PREDICT 时的按键处理函数* * @param keyChar* @param keyCode* @param event* @param realAction* @return*/private boolean processStatePredict(int keyChar, int keyCode,KeyEvent event, boolean realAction) {if (!realAction)return true;// If ALT key is pressed, input alternative key.// 按住Alt键if (event.isAltPressed()) {// 获取中文全角字符char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);if (0 != fullwidth_char) {// 发送高亮的候选词 + 中文全角字符 给 EditViewcommitResultText(mDecInfo.getCandidate(mCandidatesContainer.getActiveCandiatePos())+ String.valueOf(fullwidth_char));resetToIdleState(false);}return true;}// In this status, when user presses keys in [a..z], the status will// change to input state.if (keyChar >= 'a' && keyChar <= 'z') {changeToStateInput(true);// 加一个字符进输入的拼音字符串中mDecInfo.addSplChar((char) keyChar, true);// 对输入的拼音进行查询。chooseAndUpdate(-1);} else if (keyChar == ',' || keyChar == '.') {// 发送 '\uff0c' 或者 '\u3002' 给EditTextinputCommaPeriod("", keyChar, true, ImeState.STATE_IDLE);} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP|| keyCode == KeyEvent.KEYCODE_DPAD_DOWN|| keyCode == KeyEvent.KEYCODE_DPAD_LEFT|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {// 高亮位置向上一个候选词移动或者移动到上一页的最后一个候选词的位置。mCandidatesContainer.activeCurseBackward();}if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {// 高亮位置向下一个候选词移动或者移动到下一页的第一个候选词的位置。mCandidatesContainer.activeCurseForward();}if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {// 到上一页候选词mCandidatesContainer.pageBackward(false, true);}if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {// 到下一页候选词mCandidatesContainer.pageForward(false, true);}} else if (keyCode == KeyEvent.KEYCODE_DEL) {resetToIdleState(false);} else if (keyCode == KeyEvent.KEYCODE_BACK) {resetToIdleState(false);// 关闭输入法requestHideSelf(0);} else if (keyCode >= KeyEvent.KEYCODE_1&& keyCode <= KeyEvent.KEYCODE_9) {int activePos = keyCode - KeyEvent.KEYCODE_1;int currentPage = mCandidatesContainer.getCurrentPage();if (activePos < mDecInfo.getCurrentPageSize(currentPage)) {activePos = activePos+ mDecInfo.getCurrentPageStart(currentPage);if (activePos >= 0) {// 选择候选词chooseAndUpdate(activePos);}}} else if (keyCode == KeyEvent.KEYCODE_ENTER) {// 发生ENTER键给EditTextsendKeyChar('\n');resetToIdleState(false);} else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER|| keyCode == KeyEvent.KEYCODE_SPACE) {// 选择候选词chooseCandidate(-1);}return true;}/*** 当 mImeState == ImeState.STATE_COMPOSING 时的按键处理函数* * @param keyChar* @param keyCode* @param event* @param realAction* @return*/private boolean processStateEditComposing(int keyChar, int keyCode,KeyEvent event, boolean realAction) {if (!realAction)return true;// 获取输入的音字符串的状态ComposingView.ComposingStatus cmpsvStatus = mComposingView.getComposingStatus();// If ALT key is pressed, input alternative key. But if the// alternative key is quote key, it will be used for input a splitter// in Pinyin string.// 按住 ALT 键if (event.isAltPressed()) {if ('\'' != event.getUnicodeChar(event.getMetaState())) {// 获取中文全角字符char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);if (0 != fullwidth_char) {String retStr;if (ComposingView.ComposingStatus.SHOW_STRING_LOWERCASE == cmpsvStatus) {// 获取原始的输入拼音的字符retStr = mDecInfo.getOrigianlSplStr().toString();} else {// 获取组合的输入拼音的字符(有可能存在选中的候选词)retStr = mDecInfo.getComposingStr();}// 发送文本给EditTextcommitResultText(retStr + String.valueOf(fullwidth_char));resetToIdleState(false);}return true;} else {keyChar = '\'';}}if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {if (!mDecInfo.selectionFinished()) {changeToStateInput(true);}} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {// 移动候选词的光标mComposingView.moveCursor(keyCode);} else if ((keyCode == KeyEvent.KEYCODE_ENTER && mInputModeSwitcher.isEnterNoramlState())|| keyCode == KeyEvent.KEYCODE_DPAD_CENTER|| keyCode == KeyEvent.KEYCODE_SPACE) {if (ComposingView.ComposingStatus.SHOW_STRING_LOWERCASE == cmpsvStatus) {// 获取原始的输入拼音的字符String str = mDecInfo.getOrigianlSplStr().toString();if (!tryInputRawUnicode(str)) {// 发送文本给EditTextcommitResultText(str);}} else if (ComposingView.ComposingStatus.EDIT_PINYIN == cmpsvStatus) {// 获取组合的输入拼音的字符(有可能存在选中的候选词)String str = mDecInfo.getComposingStr();// 对开头或者结尾为"unicode"的字符串进行转换if (!tryInputRawUnicode(str)) {// 发送文本给EditTextcommitResultText(str);}} else {// 发生 组合的输入拼音的字符(有可能存在选中的候选词) 给 EditTextcommitResultText(mDecInfo.getComposingStr());}resetToIdleState(false);} else if (keyCode == KeyEvent.KEYCODE_ENTER&& !mInputModeSwitcher.isEnterNoramlState()) {String retStr;if (!mDecInfo.isCandidatesListEmpty()) {// 获取当前高亮的候选词retStr = mDecInfo.getCurrentFullSent(mCandidatesContainer.getActiveCandiatePos());} else {// 获取组合的输入拼音的字符(有可能存在选中的候选词)retStr = mDecInfo.getComposingStr();}// 发送文本给EditTextcommitResultText(retStr);// 发生ENTER键给EditTextsendKeyChar('\n');resetToIdleState(false);} else if (keyCode == KeyEvent.KEYCODE_BACK) {resetToIdleState(false);// 关闭输入法requestHideSelf(0);return true;} else {// 添加输入的拼音,然后进行词库查询,或者删除输入的拼音指定的字符或字符串,然后进行词库查询。return processSurfaceChange(keyChar, keyCode);}return true;}/*** 对开头或者结尾为"unicode"的字符串进行转换* * @param str* @return*/private boolean tryInputRawUnicode(String str) {if (str.length() > 7) {if (str.substring(0, 7).compareTo("unicode") == 0) {// str是"unicode"开头try {// 截取"unicode"后面的字符串String digitStr = str.substring(7);int startPos = 0;int radix = 10;if (digitStr.length() > 2 && digitStr.charAt(0) == '0'&& digitStr.charAt(1) == 'x') {startPos = 2;radix = 16;}digitStr = digitStr.substring(startPos);// 取digitStr对应的整数int unicode = Integer.parseInt(digitStr, radix);if (unicode > 0) {char low = (char) (unicode & 0x0000ffff);char high = (char) ((unicode & 0xffff0000) >> 16);commitResultText(String.valueOf(low));if (0 != high) {commitResultText(String.valueOf(high));}}return true;} catch (NumberFormatException e) {return false;}} else if (str.substring(str.length() - 7, str.length()).compareTo("unicode") == 0) {// str是"unicode"结尾String resultStr = "";for (int pos = 0; pos < str.length() - 7; pos++) {if (pos > 0) {resultStr += " ";}resultStr += "0x" + Integer.toHexString(str.charAt(pos));}commitResultText(String.valueOf(resultStr));return true;}}return false;}/*** 添加输入的拼音,然后进行词库查询,或者删除输入的拼音指定的字符或字符串,然后进行词库查询。* * @param keyChar* @param keyCode* @return*/private boolean processSurfaceChange(int keyChar, int keyCode) {if (mDecInfo.isSplStrFull() && KeyEvent.KEYCODE_DEL != keyCode) {return true;}if ((keyChar >= 'a' && keyChar <= 'z')|| (keyChar == '\'' && !mDecInfo.charBeforeCursorIsSeparator())|| (((keyChar >= '0' && keyChar <= '9') || keyChar == ' ') && ImeState.STATE_COMPOSING == mImeState)) {mDecInfo.addSplChar((char) keyChar, false);chooseAndUpdate(-1);} else if (keyCode == KeyEvent.KEYCODE_DEL) {mDecInfo.prepareDeleteBeforeCursor();chooseAndUpdate(-1);}return true;}/*** 设置输入法状态为 mImeState = ImeState.STATE_COMPOSING;* * @param updateUi*            是否更新UI*/private void changeToStateComposing(boolean updateUi) {mImeState = ImeState.STATE_COMPOSING;if (!updateUi)return;if (null != mSkbContainer && mSkbContainer.isShown()) {mSkbContainer.toggleCandidateMode(true);}}/*** 设置输入法状态为 mImeState = ImeState.STATE_INPUT;* * @param updateUi*            是否更新UI*/private void changeToStateInput(boolean updateUi) {mImeState = ImeState.STATE_INPUT;if (!updateUi)return;if (null != mSkbContainer && mSkbContainer.isShown()) {mSkbContainer.toggleCandidateMode(true);}showCandidateWindow(true);}/*** 模拟按下一个按键* * @param keyCode*/private void simulateKeyEventDownUp(int keyCode) {InputConnection ic = getCurrentInputConnection();if (null == ic)return;ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));}/*** 发送字符串给编辑框* * @param resultText*/private void commitResultText(String resultText) {InputConnection ic = getCurrentInputConnection();if (null != ic)ic.commitText(resultText, 1);if (null != mComposingView) {mComposingView.setVisibility(View.INVISIBLE);mComposingView.invalidate();}}/*** 设置是否显示输入拼音的view* * @param visible*/private void updateComposingText(boolean visible) {if (!visible) {mComposingView.setVisibility(View.INVISIBLE);} else {mComposingView.setDecodingInfo(mDecInfo, mImeState);mComposingView.setVisibility(View.VISIBLE);}mComposingView.invalidate();}/*** 发送 '\uff0c' 或者 '\u3002' 给EditText* * @param preEdit* @param keyChar* @param dismissCandWindow*            是否重置候选词窗口* @param nextState*            mImeState的下一个状态*/private void inputCommaPeriod(String preEdit, int keyChar,boolean dismissCandWindow, ImeState nextState) {if (keyChar == ',')preEdit += '\uff0c';else if (keyChar == '.')preEdit += '\u3002';elsereturn;commitResultText(preEdit);if (dismissCandWindow)resetCandidateWindow();mImeState = nextState;}/*** 重置到空闲状态* * @param resetInlineText*/private void resetToIdleState(boolean resetInlineText) {if (ImeState.STATE_IDLE == mImeState)return;mImeState = ImeState.STATE_IDLE;mDecInfo.reset();// 重置显示输入拼音字符串的 Viewif (null != mComposingView)mComposingView.reset();if (resetInlineText)commitResultText("");resetCandidateWindow();}/*** 选择候选词,并根据条件是否进行下一步的预报。* * @param candId*            如果candId小于0 ,就对输入的拼音进行查询。*/private void chooseAndUpdate(int candId) {// 不是中文输入法状态if (!mInputModeSwitcher.isChineseText()) {String choice = mDecInfo.getCandidate(candId);if (null != choice) {commitResultText(choice);}resetToIdleState(false);return;}if (ImeState.STATE_PREDICT != mImeState) {// Get result candidate list, if choice_id < 0, do a new decoding.// If choice_id >=0, select the candidate, and get the new candidate// list.mDecInfo.chooseDecodingCandidate(candId);} else {// Choose a prediction item.mDecInfo.choosePredictChoice(candId);}if (mDecInfo.getComposingStr().length() > 0) {String resultStr;// 获取选择了的候选词resultStr = mDecInfo.getComposingStrActivePart();// choiceId >= 0 means user finishes a choice selection.if (candId >= 0 && mDecInfo.canDoPrediction()) {// 发生选择了的候选词给EditTextcommitResultText(resultStr);// 设置输入法状态为预报mImeState = ImeState.STATE_PREDICT;// TODO 这一步是做什么?if (null != mSkbContainer && mSkbContainer.isShown()) {mSkbContainer.toggleCandidateMode(false);}// Try to get the prediction list.// 获取预报的候选词列表if (Settings.getPrediction()) {InputConnection ic = getCurrentInputConnection();if (null != ic) {CharSequence cs = ic.getTextBeforeCursor(3, 0);if (null != cs) {mDecInfo.preparePredicts(cs);}}} else {mDecInfo.resetCandidates();}if (mDecInfo.mCandidatesList.size() > 0) {showCandidateWindow(false);} else {resetToIdleState(false);}} else {if (ImeState.STATE_IDLE == mImeState) {if (mDecInfo.getSplStrDecodedLen() == 0) {changeToStateComposing(true);} else {changeToStateInput(true);}} else {if (mDecInfo.selectionFinished()) {changeToStateComposing(true);}}showCandidateWindow(true);}} else {resetToIdleState(false);}}// If activeCandNo is less than 0, get the current active candidate number// from candidate view, otherwise use activeCandNo./*** 选择候选词* * @param activeCandNo*            如果小于0,就选择当前高亮的候选词。*/private void chooseCandidate(int activeCandNo) {if (activeCandNo < 0) {activeCandNo = mCandidatesContainer.getActiveCandiatePos();}if (activeCandNo >= 0) {chooseAndUpdate(activeCandNo);}}/*** 绑定词库解码远程服务PinyinDecoderService* * @return*/private boolean startPinyinDecoderService() {if (null == mDecInfo.mIPinyinDecoderService) {Intent serviceIntent = new Intent();serviceIntent.setClass(this, PinyinDecoderService.class);if (null == mPinyinDecoderServiceConnection) {mPinyinDecoderServiceConnection = new PinyinDecoderServiceConnection();}// Bind serviceif (bindService(serviceIntent, mPinyinDecoderServiceConnection,Context.BIND_AUTO_CREATE)) {return true;} else {return false;}}return true;}@Overridepublic View onCreateCandidatesView() {if (mEnvironment.needDebug()) {Log.d(TAG, "onCreateCandidatesView.");}LayoutInflater inflater = getLayoutInflater();// 设置显示输入拼音字符串View的集装箱// Inflate the floating container viewmFloatingContainer = (LinearLayout) inflater.inflate(R.layout.floating_container, null);// The first child is the composing view.mComposingView = (ComposingView) mFloatingContainer.getChildAt(0);// 设置候选词集装箱mCandidatesContainer = (CandidatesContainer) inflater.inflate(R.layout.candidates_container, null);// Create balloon hint for candidates view. 创建候选词气泡mCandidatesBalloon = new BalloonHint(this, mCandidatesContainer,MeasureSpec.UNSPECIFIED);mCandidatesBalloon.setBalloonBackground(getResources().getDrawable(R.drawable.candidate_balloon_bg));mCandidatesContainer.initialize(mChoiceNotifier, mCandidatesBalloon,mGestureDetectorCandidates);// The floating windowif (null != mFloatingWindow && mFloatingWindow.isShowing()) {mFloatingWindowTimer.cancelShowing();mFloatingWindow.dismiss();}mFloatingWindow = new PopupWindow(this);mFloatingWindow.setClippingEnabled(false);mFloatingWindow.setBackgroundDrawable(null);mFloatingWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);mFloatingWindow.setContentView(mFloatingContainer);setCandidatesViewShown(true);return mCandidatesContainer;}/*** 响应软键盘按键的处理函数。在软键盘集装箱SkbContainer中responseKeyEvent()的调用。* 软键盘集装箱SkbContainer的responseKeyEvent()在自身类中调用。* * @param sKey*/public void responseSoftKeyEvent(SoftKey sKey) {if (null == sKey)return;InputConnection ic = getCurrentInputConnection();if (ic == null)return;int keyCode = sKey.getKeyCode();// Process some general keys, including KEYCODE_DEL, KEYCODE_SPACE,// KEYCODE_ENTER and KEYCODE_DPAD_CENTER.if (sKey.isKeyCodeKey()) {// 是系统的keycode// 功能键处理函数if (processFunctionKeys(keyCode, true))return;}if (sKey.isUserDefKey()) {// 是用户定义的keycode// 通过我们定义的软键盘的按键,切换输入法模式。updateIcon(mInputModeSwitcher.switchModeForUserKey(keyCode));resetToIdleState(false);mSkbContainer.updateInputMode();} else {if (sKey.isKeyCodeKey()) {// 是系统的keycodeKeyEvent eDown = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,keyCode, 0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);KeyEvent eUp = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode,0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);onKeyDown(keyCode, eDown);onKeyUp(keyCode, eUp);} else if (sKey.isUniStrKey()) {// 是字符按键boolean kUsed = false;// 获取按键的字符String keyLabel = sKey.getKeyLabel();if (mInputModeSwitcher.isChineseTextWithSkb()&& (ImeState.STATE_INPUT == mImeState || ImeState.STATE_COMPOSING == mImeState)) {if (mDecInfo.length() > 0 && keyLabel.length() == 1&& keyLabel.charAt(0) == '\'') {// 加入拼音分隔符,然后进行词库查询processSurfaceChange('\'', 0);kUsed = true;}}if (!kUsed) {if (ImeState.STATE_INPUT == mImeState) {// 发送高亮候选词给EditTextcommitResultText(mDecInfo.getCurrentFullSent(mCandidatesContainer.getActiveCandiatePos()));} else if (ImeState.STATE_COMPOSING == mImeState) {// 发送 拼音字符串(有可能存在选中的候选词) 给EditTextcommitResultText(mDecInfo.getComposingStr());}// 发送 按键的字符 给EditTextcommitResultText(keyLabel);resetToIdleState(false);}}// If the current soft keyboard is not sticky, IME needs to go// back to the previous soft keyboard automatically.// 如果当前的软键盘不是粘性的,那么输入法需要返回上一个输入法模式。if (!mSkbContainer.isCurrentSkbSticky()) {updateIcon(mInputModeSwitcher.requestBackToPreviousSkb());resetToIdleState(false);mSkbContainer.updateInputMode();}}}/*** 显示候选词视图* * @param showComposingView*            是否显示输入的拼音View*/private void showCandidateWindow(boolean showComposingView) {if (mEnvironment.needDebug()) {Log.d(TAG, "Candidates window is shown. Parent = "+ mCandidatesContainer);}setCandidatesViewShown(true);if (null != mSkbContainer)mSkbContainer.requestLayout();if (null == mCandidatesContainer) {resetToIdleState(false);return;}updateComposingText(showComposingView);mCandidatesContainer.showCandidates(mDecInfo,ImeState.STATE_COMPOSING != mImeState);mFloatingWindowTimer.postShowFloatingWindow();}/*** 关闭候选词窗口,并且关闭用于输入拼音字符串的窗口*/private void dismissCandidateWindow() {if (mEnvironment.needDebug()) {Log.d(TAG, "Candidates window is to be dismissed");}if (null == mCandidatesContainer)return;try {mFloatingWindowTimer.cancelShowing();mFloatingWindow.dismiss();} catch (Exception e) {Log.e(TAG, "Fail to show the PopupWindow.");}setCandidatesViewShown(false);if (null != mSkbContainer && mSkbContainer.isShown()) {mSkbContainer.toggleCandidateMode(false);}}/*** 重置候选词区域*/private void resetCandidateWindow() {if (mEnvironment.needDebug()) {Log.d(TAG, "Candidates window is to be reset");}if (null == mCandidatesContainer)return;try {mFloatingWindowTimer.cancelShowing();mFloatingWindow.dismiss();} catch (Exception e) {Log.e(TAG, "Fail to show the PopupWindow.");}if (null != mSkbContainer && mSkbContainer.isShown()) {mSkbContainer.toggleCandidateMode(false);}mDecInfo.resetCandidates();if (null != mCandidatesContainer && mCandidatesContainer.isShown()) {showCandidateWindow(false);}}/*** 更新输入法服务的图标* * @param iconId*/private void updateIcon(int iconId) {if (iconId > 0) {showStatusIcon(iconId);} else {hideStatusIcon();}}@Overridepublic View onCreateInputView() {if (mEnvironment.needDebug()) {Log.d(TAG, "onCreateInputView.");}LayoutInflater inflater = getLayoutInflater();mSkbContainer = (SkbContainer) inflater.inflate(R.layout.skb_container,null);mSkbContainer.setService(this);mSkbContainer.setInputModeSwitcher(mInputModeSwitcher);mSkbContainer.setGestureDetector(mGestureDetectorSkb);return mSkbContainer;}@Overridepublic void onStartInput(EditorInfo editorInfo, boolean restarting) {if (mEnvironment.needDebug()) {Log.d(TAG,"onStartInput " + " ccontentType: "+ String.valueOf(editorInfo.inputType)+ " Restarting:" + String.valueOf(restarting));}updateIcon(mInputModeSwitcher.requestInputWithHkb(editorInfo));resetToIdleState(false);}@Overridepublic void onStartInputView(EditorInfo editorInfo, boolean restarting) {if (mEnvironment.needDebug()) {Log.d(TAG,"onStartInputView " + " contentType: "+ String.valueOf(editorInfo.inputType)+ " Restarting:" + String.valueOf(restarting));}updateIcon(mInputModeSwitcher.requestInputWithSkb(editorInfo));resetToIdleState(false);mSkbContainer.updateInputMode();setCandidatesViewShown(false);}@Overridepublic void onFinishInputView(boolean finishingInput) {if (mEnvironment.needDebug()) {Log.d(TAG, "onFinishInputView.");}resetToIdleState(false);super.onFinishInputView(finishingInput);}@Overridepublic void onFinishInput() {if (mEnvironment.needDebug()) {Log.d(TAG, "onFinishInput.");}resetToIdleState(false);super.onFinishInput();}@Overridepublic void onFinishCandidatesView(boolean finishingInput) {if (mEnvironment.needDebug()) {Log.d(TAG, "onFinishCandidateView.");}resetToIdleState(false);super.onFinishCandidatesView(finishingInput);}@Overridepublic void onDisplayCompletions(CompletionInfo[] completions) {// TODO 该函数什么情况下被调用?if (!isFullscreenMode())return;if (null == completions || completions.length <= 0)return;if (null == mSkbContainer || !mSkbContainer.isShown())return;if (!mInputModeSwitcher.isChineseText()|| ImeState.STATE_IDLE == mImeState|| ImeState.STATE_PREDICT == mImeState) {mImeState = ImeState.STATE_APP_COMPLETION;// 准备从app获取候选词mDecInfo.prepareAppCompletions(completions);showCandidateWindow(false);}}/*** 选择候选词后的处理函数。在ChoiceNotifier中实现CandidateViewListener监听器的onClickChoice()中调用* 。* * @param activeCandNo*/private void onChoiceTouched(int activeCandNo) {if (mImeState == ImeState.STATE_COMPOSING) {changeToStateInput(true);} else if (mImeState == ImeState.STATE_INPUT|| mImeState == ImeState.STATE_PREDICT) {// 选择候选词chooseCandidate(activeCandNo);} else if (mImeState == ImeState.STATE_APP_COMPLETION) {if (null != mDecInfo.mAppCompletions && activeCandNo >= 0&& activeCandNo < mDecInfo.mAppCompletions.length) {CompletionInfo ci = mDecInfo.mAppCompletions[activeCandNo];if (null != ci) {InputConnection ic = getCurrentInputConnection();// 发送从APP中获取的候选词给EditTextic.commitCompletion(ci);}}resetToIdleState(false);}}@Overridepublic void requestHideSelf(int flags) {if (mEnvironment.needDebug()) {Log.d(TAG, "DimissSoftInput.");}dismissCandidateWindow();if (null != mSkbContainer && mSkbContainer.isShown()) {mSkbContainer.dismissPopups();}super.requestHideSelf(flags);}/*** 选项菜单对话框*/public void showOptionsMenu() {AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setCancelable(true);builder.setIcon(R.drawable.app_icon);builder.setNegativeButton(android.R.string.cancel, null);CharSequence itemSettings = getString(R.string.ime_settings_activity_name);CharSequence itemInputMethod = "";// =// getString(com.android.internal.R.string.inputMethod);builder.setItems(new CharSequence[] { itemSettings, itemInputMethod },new DialogInterface.OnClickListener() {public void onClick(DialogInterface di, int position) {di.dismiss();switch (position) {case 0:launchSettings();break;case 1:// InputMethodManager.getInstance(PinyinIME.this)// .showInputMethodPicker();break;}}});builder.setTitle(getString(R.string.ime_name));mOptionsDialog = builder.create();Window window = mOptionsDialog.getWindow();WindowManager.LayoutParams lp = window.getAttributes();lp.token = mSkbContainer.getWindowToken();lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;window.setAttributes(lp);window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);mOptionsDialog.show();}/*** 启动系统的设置页面*/private void launchSettings() {Intent intent = new Intent();intent.setClass(PinyinIME.this, SettingsActivity.class);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);}/*** 显示输入的拼音字符串PopupWindow 定时器* * @ClassName PopupTimer* @author keanbin*/private class PopupTimer extends Handler implements Runnable {private int mParentLocation[] = new int[2];void postShowFloatingWindow() {mFloatingContainer.measure(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);mFloatingWindow.setWidth(mFloatingContainer.getMeasuredWidth());mFloatingWindow.setHeight(mFloatingContainer.getMeasuredHeight());post(this);}void cancelShowing() {if (mFloatingWindow.isShowing()) {mFloatingWindow.dismiss();}removeCallbacks(this);}public void run() {// 获取候选集装箱的位置mCandidatesContainer.getLocationInWindow(mParentLocation);if (!mFloatingWindow.isShowing()) {// 显示候选词PopupWindowmFloatingWindow.showAtLocation(mCandidatesContainer,Gravity.LEFT | Gravity.TOP, mParentLocation[0],mParentLocation[1] - mFloatingWindow.getHeight());} else {// 更新候选词PopupWindowmFloatingWindow.update(mParentLocation[0], mParentLocation[1]- mFloatingWindow.getHeight(),mFloatingWindow.getWidth(),mFloatingWindow.getHeight());}}}/*** Used to notify IME that the user selects a candidate or performs an* gesture. 当用户选择了候选词或者在候选词视图滑动了手势时的通知输入法。实现了候选词视图的监听器CandidateViewListener,* 有选择候选词的处理函数、手势向右滑动的处理函数、手势向左滑动的处理函数 、手势向上滑动的处理函数、手势向下滑动的处理函数。*/public class ChoiceNotifier extends Handler implementsCandidateViewListener {PinyinIME mIme;ChoiceNotifier(PinyinIME ime) {mIme = ime;}public void onClickChoice(int choiceId) {if (choiceId >= 0) {mIme.onChoiceTouched(choiceId);}}public void onToLeftGesture() {if (ImeState.STATE_COMPOSING == mImeState) {changeToStateInput(true);}mCandidatesContainer.pageForward(true, false);}public void onToRightGesture() {if (ImeState.STATE_COMPOSING == mImeState) {changeToStateInput(true);}mCandidatesContainer.pageBackward(true, false);}public void onToTopGesture() {}public void onToBottomGesture() {}}/*** 手势监听器* * @ClassName OnGestureListener* @author keanbin*/public class OnGestureListener extendsGestureDetector.SimpleOnGestureListener {/*** When user presses and drags, the minimum x-distance to make a* response to the drag event. 当用户拖拽的时候,x轴上最小的差值才可以产生拖拽事件。*/private static final int MIN_X_FOR_DRAG = 60;/*** When user presses and drags, the minimum y-distance to make a* response to the drag event.当用户拖拽的时候,y轴上最小的差值才可以产生拖拽事件。*/private static final int MIN_Y_FOR_DRAG = 40;/*** Velocity threshold for a screen-move gesture. If the minimum* x-velocity is less than it, no* gesture.x轴上的手势的最小速率阀值,小于这个阀值,就不是手势。只要在滑动的期间* ,有任意一段的速率小于这个值,就判断这次的滑动不是手势mNotGesture = true,就算接下去滑动的速率变高也是没用。*/static private final float VELOCITY_THRESHOLD_X1 = 0.3f;/*** Velocity threshold for a screen-move gesture. If the maximum* x-velocity is less than it, no* gesture.x轴上的手势的最大速率阀值,大于这个阀值,就一定是手势,mGestureRecognized = true。*/static private final float VELOCITY_THRESHOLD_X2 = 0.7f;/*** Velocity threshold for a screen-move gesture. If the minimum* y-velocity is less than it, no* gesture.y轴上的手势的最小速率阀值,小于这个阀值,就不是手势。只要在滑动的期间* ,有任意一段的速率小于这个值,就判断这次的滑动不是手势mNotGesture =* true,就算接下去滑动的速率变高也是没用,mGestureRecognized = true。*/static private final float VELOCITY_THRESHOLD_Y1 = 0.2f;/*** Velocity threshold for a screen-move gesture. If the maximum* y-velocity is less than it, no gesture.y轴上的手势的最大速率阀值,大于这个阀值,就一定是手势。*/static private final float VELOCITY_THRESHOLD_Y2 = 0.45f;/** If it false, we will not response detected gestures. 是否响应检测到的手势 */private boolean mReponseGestures;/** The minimum X velocity observed in the gesture. 能检测到的x最小速率的手势 */private float mMinVelocityX = Float.MAX_VALUE;/** The minimum Y velocity observed in the gesture. 能检测到y最小速率的手势 */private float mMinVelocityY = Float.MAX_VALUE;/*** The first down time for the series of touch events for an* action.第一次触摸事件的时间*/private long mTimeDown;/** The last time when onScroll() is called.最后一次 onScroll()被调用的时间 */private long mTimeLastOnScroll;/*** This flag used to indicate that this gesture is not a gesture.* 是否不是一个手势?*/private boolean mNotGesture;/*** This flag used to indicate that this gesture has been recognized.* 是否是一个公认的手势?*/private boolean mGestureRecognized;public OnGestureListener(boolean reponseGestures) {mReponseGestures = reponseGestures;}@Overridepublic boolean onDown(MotionEvent e) {mMinVelocityX = Integer.MAX_VALUE;mMinVelocityY = Integer.MAX_VALUE;mTimeDown = e.getEventTime();mTimeLastOnScroll = mTimeDown;mNotGesture = false;mGestureRecognized = false;return false;}@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY) {if (mNotGesture)return false;if (mGestureRecognized)return true;if (Math.abs(e1.getX() - e2.getX()) < MIN_X_FOR_DRAG&& Math.abs(e1.getY() - e2.getY()) < MIN_Y_FOR_DRAG)return false;long timeNow = e2.getEventTime();long spanTotal = timeNow - mTimeDown;long spanThis = timeNow - mTimeLastOnScroll;if (0 == spanTotal)spanTotal = 1;if (0 == spanThis)spanThis = 1;// 计算总速率float vXTotal = (e2.getX() - e1.getX()) / spanTotal;float vYTotal = (e2.getY() - e1.getY()) / spanTotal;// The distances are from the current point to the previous one.// 计算这次 onScroll 的速率float vXThis = -distanceX / spanThis;float vYThis = -distanceY / spanThis;float kX = vXTotal * vXThis;float kY = vYTotal * vYThis;float k1 = kX + kY;float k2 = Math.abs(kX) + Math.abs(kY);// TODO 这个是什么计算公式?if (k1 / k2 < 0.8) {mNotGesture = true;return false;}float absVXTotal = Math.abs(vXTotal);float absVYTotal = Math.abs(vYTotal);if (absVXTotal < mMinVelocityX) {mMinVelocityX = absVXTotal;}if (absVYTotal < mMinVelocityY) {mMinVelocityY = absVYTotal;}// 如果最小的速率比规定的小,那么就不是手势。if (mMinVelocityX < VELOCITY_THRESHOLD_X1&& mMinVelocityY < VELOCITY_THRESHOLD_Y1) {mNotGesture = true;return false;}// 判断是什么手势?并调用手势处理函数。if (vXTotal > VELOCITY_THRESHOLD_X2&& absVYTotal < VELOCITY_THRESHOLD_Y2) {if (mReponseGestures)onDirectionGesture(Gravity.RIGHT);mGestureRecognized = true;} else if (vXTotal < -VELOCITY_THRESHOLD_X2&& absVYTotal < VELOCITY_THRESHOLD_Y2) {if (mReponseGestures)onDirectionGesture(Gravity.LEFT);mGestureRecognized = true;} else if (vYTotal > VELOCITY_THRESHOLD_Y2&& absVXTotal < VELOCITY_THRESHOLD_X2) {if (mReponseGestures)onDirectionGesture(Gravity.BOTTOM);mGestureRecognized = true;} else if (vYTotal < -VELOCITY_THRESHOLD_Y2&& absVXTotal < VELOCITY_THRESHOLD_X2) {if (mReponseGestures)onDirectionGesture(Gravity.TOP);mGestureRecognized = true;}mTimeLastOnScroll = timeNow;return mGestureRecognized;}@Overridepublic boolean onFling(MotionEvent me1, MotionEvent me2,float velocityX, float velocityY) {return mGestureRecognized;}/*** 手势的处理函数* * @param gravity*            手势的类别*/public void onDirectionGesture(int gravity) {if (Gravity.NO_GRAVITY == gravity) {return;}if (Gravity.LEFT == gravity || Gravity.RIGHT == gravity) {if (mCandidatesContainer.isShown()) {if (Gravity.LEFT == gravity) {mCandidatesContainer.pageForward(true, true);} else {mCandidatesContainer.pageBackward(true, true);}return;}}}}/*** Connection used for binding to the Pinyin decoding service.* 词库解码远程服务PinyinDecoderService 的监听器*/public class PinyinDecoderServiceConnection implements ServiceConnection {public void onServiceConnected(ComponentName name, IBinder service) {mDecInfo.mIPinyinDecoderService = IPinyinDecoderService.Stub.asInterface(service);}public void onServiceDisconnected(ComponentName name) {}}/*** 输入法状态*/public enum ImeState {STATE_BYPASS, STATE_IDLE, STATE_INPUT, STATE_COMPOSING, STATE_PREDICT, STATE_APP_COMPLETION}/*** 词库解码操作对象* * @ClassName DecodingInfo* @author keanbin*/public class DecodingInfo {/*** Maximum length of the Pinyin string* 最大的字符串的长度,其实只有27,因为最后一位为0,是mPyBuf[]的长度*/private static final int PY_STRING_MAX = 28;/*** Maximum number of candidates to display in one page. 一页显示候选词的最大个数*/private static final int MAX_PAGE_SIZE_DISPLAY = 10;/*** Spelling (Pinyin) string. 拼音字符串*/private StringBuffer mSurface;/*** Byte buffer used as the Pinyin string parameter for native function* call. 字符缓冲区作为拼音字符串参数给本地函数调用,它的长度为PY_STRING_MAX,最后一位为0*/private byte mPyBuf[];/*** The length of surface string successfully decoded by engine.* 成功解码的字符串长度*/private int mSurfaceDecodedLen;/*** Composing string. 拼音字符串*/private String mComposingStr;/*** Length of the active composing string. 活动的拼音字符串长度*/private int mActiveCmpsLen;/*** Composing string for display, it is copied from mComposingStr, and* add spaces between spellings.* 显示的拼音字符串,是从mComposingStr复制过来的,并且在拼写之间加上了空格。**/private String mComposingStrDisplay;/*** Length of the active composing string for display. 显示的拼音字符串的长度*/private int mActiveCmpsDisplayLen;/*** The first full sentence choice. 第一个完整句子,第一个候选词。*/private String mFullSent;/*** Number of characters which have been fixed. 固定的字符的数量*/private int mFixedLen;/*** If this flag is true, selection is finished. 是否选择完成了?*/private boolean mFinishSelection;/*** The starting position for each spelling. The first one is the number* of the real starting position elements. 每个拼写的开始位置,猜测:第一个元素是拼写的总数量?*/private int mSplStart[];/*** Editing cursor in mSurface. 光标的位置*/private int mCursorPos;/*** Remote Pinyin-to-Hanzi decoding engine service. 解码引擎远程服务*/private IPinyinDecoderService mIPinyinDecoderService;/*** The complication information suggested by application. 应用的并发建议信息*/private CompletionInfo[] mAppCompletions;/*** The total number of choices for display. The list may only contains* the first part. If user tries to navigate to next page which is not* in the result list, we need to get these items. 显示的可选择的总数**/public int mTotalChoicesNum;/*** Candidate list. The first one is the full-sentence candidate. 候选词列表*/public List<String> mCandidatesList = new Vector<String>();/*** Element i stores the starting position of page i. 页的开始位置*/public Vector<Integer> mPageStart = new Vector<Integer>();/*** Element i stores the number of characters to page i. 每一页的数量*/public Vector<Integer> mCnToPage = new Vector<Integer>();/*** The position to delete in Pinyin string. If it is less than 0, IME* will do an incremental search, otherwise IME will do a deletion* operation. if {@link #mIsPosInSpl} is true, IME will delete the whole* string for mPosDelSpl-th spelling, otherwise it will only delete* mPosDelSpl-th character in the Pinyin string. 在拼音字符串中的删除位置*/public int mPosDelSpl = -1;/*** If {@link #mPosDelSpl} is big than or equal to 0, this member is used* to indicate that whether the postion is counted in spelling id or* character. 如果 mPosDelSpl 大于等于 0,那么这个参数就用于表明是否是 拼写的id 或者 字符。*/public boolean mIsPosInSpl;public DecodingInfo() {mSurface = new StringBuffer();mSurfaceDecodedLen = 0;}/*** 重置*/public void reset() {mSurface.delete(0, mSurface.length());mSurfaceDecodedLen = 0;mCursorPos = 0;mFullSent = "";mFixedLen = 0;mFinishSelection = false;mComposingStr = "";mComposingStrDisplay = "";mActiveCmpsLen = 0;mActiveCmpsDisplayLen = 0;resetCandidates();}/*** 候选词列表是否为空* * @return*/public boolean isCandidatesListEmpty() {return mCandidatesList.size() == 0;}/*** 拼写的字符串是否已满* * @return*/public boolean isSplStrFull() {if (mSurface.length() >= PY_STRING_MAX - 1)return true;return false;}/*** 增加拼写字符* * @param ch* @param reset*            拼写字符是否重置*/public void addSplChar(char ch, boolean reset) {if (reset) {mSurface.delete(0, mSurface.length());mSurfaceDecodedLen = 0;mCursorPos = 0;try {mIPinyinDecoderService.imResetSearch();} catch (RemoteException e) {}}mSurface.insert(mCursorPos, ch);mCursorPos++;}// Prepare to delete before cursor. We may delete a spelling char if// the cursor is in the range of unfixed part, delete a whole spelling// if the cursor in inside the range of the fixed part.// This function only marks the position used to delete./*** 删除前的准备。该函数只是标记要删除的位置。*/public void prepareDeleteBeforeCursor() {if (mCursorPos > 0) {int pos;for (pos = 0; pos < mFixedLen; pos++) {if (mSplStart[pos + 2] >= mCursorPos&& mSplStart[pos + 1] < mCursorPos) {// 删除一个拼写字符串mPosDelSpl = pos;mCursorPos = mSplStart[pos + 1];mIsPosInSpl = true;break;}}if (mPosDelSpl < 0) {// 删除一个字符mPosDelSpl = mCursorPos - 1;mCursorPos--;mIsPosInSpl = false;}}}/*** 获取拼音字符串长度* * @return*/public int length() {return mSurface.length();}/*** 获得拼音字符串中指定位置的字符* * @param index* @return*/public char charAt(int index) {return mSurface.charAt(index);}/*** 获得拼音字符串* * @return*/public StringBuffer getOrigianlSplStr() {return mSurface;}/*** 获得成功解码的字符串长度* * @return*/public int getSplStrDecodedLen() {return mSurfaceDecodedLen;}/*** 获得每个拼写字符串的开始位置* * @return*/public int[] getSplStart() {return mSplStart;}/*** 获取拼音字符串,有可能存在选中的候选词* * @return*/public String getComposingStr() {return mComposingStr;}/*** 获取活动的拼音字符串,就是选择了的候选词。* * @return*/public String getComposingStrActivePart() {assert (mActiveCmpsLen <= mComposingStr.length());return mComposingStr.substring(0, mActiveCmpsLen);}/*** 获得活动的拼音字符串长度* * @return*/public int getActiveCmpsLen() {return mActiveCmpsLen;}/*** 获取显示的拼音字符串* * @return*/public String getComposingStrForDisplay() {return mComposingStrDisplay;}/*** 显示的拼音字符串的长度* * @return*/public int getActiveCmpsDisplayLen() {return mActiveCmpsDisplayLen;}/*** 第一个完整句子* * @return*/public String getFullSent() {return mFullSent;}/*** 获取当前完整句子* * @param activeCandPos* @return*/public String getCurrentFullSent(int activeCandPos) {try {String retStr = mFullSent.substring(0, mFixedLen);retStr += mCandidatesList.get(activeCandPos);return retStr;} catch (Exception e) {return "";}}/*** 重置候选词列表*/public void resetCandidates() {mCandidatesList.clear();mTotalChoicesNum = 0;mPageStart.clear();mPageStart.add(0);mCnToPage.clear();mCnToPage.add(0);}/*** 候选词来自app,判断输入法状态 mImeState == ImeState.STATE_APP_COMPLETION。* * @return*/public boolean candidatesFromApp() {return ImeState.STATE_APP_COMPLETION == mImeState;}/*** 判断 mComposingStr.length() == mFixedLen ?* * @return*/public boolean canDoPrediction() {return mComposingStr.length() == mFixedLen;}/*** 选择是否完成* * @return*/public boolean selectionFinished() {return mFinishSelection;}// After the user chooses a candidate, input method will do a// re-decoding and give the new candidate list.// If candidate id is less than 0, means user is inputting Pinyin,// not selecting any choice./*** 如果candId〉0,就选择一个候选词,并且重新获取一个候选词列表,选择的候选词存放在mComposingStr中,通过mDecInfo.* getComposingStrActivePart()取出来。如果candId小于0 ,就对输入的拼音进行查询。* * @param candId*/private void chooseDecodingCandidate(int candId) {if (mImeState != ImeState.STATE_PREDICT) {resetCandidates();int totalChoicesNum = 0;try {if (candId < 0) {if (length() == 0) {totalChoicesNum = 0;} else {if (mPyBuf == null)mPyBuf = new byte[PY_STRING_MAX];for (int i = 0; i < length(); i++)mPyBuf[i] = (byte) charAt(i);mPyBuf[length()] = 0;if (mPosDelSpl < 0) {totalChoicesNum = mIPinyinDecoderService.imSearch(mPyBuf, length());} else {boolean clear_fixed_this_step = true;if (ImeState.STATE_COMPOSING == mImeState) {clear_fixed_this_step = false;}totalChoicesNum = mIPinyinDecoderService.imDelSearch(mPosDelSpl, mIsPosInSpl,clear_fixed_this_step);mPosDelSpl = -1;}}} else {totalChoicesNum = mIPinyinDecoderService.imChoose(candId);}} catch (RemoteException e) {}updateDecInfoForSearch(totalChoicesNum);}}/*** 更新查询词库后的信息* * @param totalChoicesNum*/private void updateDecInfoForSearch(int totalChoicesNum) {mTotalChoicesNum = totalChoicesNum;if (mTotalChoicesNum < 0) {mTotalChoicesNum = 0;return;}try {String pyStr;mSplStart = mIPinyinDecoderService.imGetSplStart();pyStr = mIPinyinDecoderService.imGetPyStr(false);mSurfaceDecodedLen = mIPinyinDecoderService.imGetPyStrLen(true);assert (mSurfaceDecodedLen <= pyStr.length());mFullSent = mIPinyinDecoderService.imGetChoice(0);mFixedLen = mIPinyinDecoderService.imGetFixedLen();// Update the surface string to the one kept by engine.mSurface.replace(0, mSurface.length(), pyStr);if (mCursorPos > mSurface.length())mCursorPos = mSurface.length();mComposingStr = mFullSent.substring(0, mFixedLen)+ mSurface.substring(mSplStart[mFixedLen + 1]);mActiveCmpsLen = mComposingStr.length();if (mSurfaceDecodedLen > 0) {mActiveCmpsLen = mActiveCmpsLen- (mSurface.length() - mSurfaceDecodedLen);}// Prepare the display string.if (0 == mSurfaceDecodedLen) {mComposingStrDisplay = mComposingStr;mActiveCmpsDisplayLen = mComposingStr.length();} else {mComposingStrDisplay = mFullSent.substring(0, mFixedLen);for (int pos = mFixedLen + 1; pos < mSplStart.length - 1; pos++) {mComposingStrDisplay += mSurface.substring(mSplStart[pos], mSplStart[pos + 1]);if (mSplStart[pos + 1] < mSurfaceDecodedLen) {mComposingStrDisplay += " ";}}mActiveCmpsDisplayLen = mComposingStrDisplay.length();if (mSurfaceDecodedLen < mSurface.length()) {mComposingStrDisplay += mSurface.substring(mSurfaceDecodedLen);}}if (mSplStart.length == mFixedLen + 2) {mFinishSelection = true;} else {mFinishSelection = false;}} catch (RemoteException e) {Log.w(TAG, "PinyinDecoderService died", e);} catch (Exception e) {mTotalChoicesNum = 0;mComposingStr = "";}// Prepare page 0.if (!mFinishSelection) {preparePage(0);}}/*** 选择预报候选词* * @param choiceId*/private void choosePredictChoice(int choiceId) {if (ImeState.STATE_PREDICT != mImeState || choiceId < 0|| choiceId >= mTotalChoicesNum) {return;}String tmp = mCandidatesList.get(choiceId);resetCandidates();mCandidatesList.add(tmp);mTotalChoicesNum = 1;mSurface.replace(0, mSurface.length(), "");mCursorPos = 0;mFullSent = tmp;mFixedLen = tmp.length();mComposingStr = mFullSent;mActiveCmpsLen = mFixedLen;mFinishSelection = true;}/*** 获得指定的候选词* * @param candId* @return*/public String getCandidate(int candId) {// Only loaded items can be gotten, so we use mCandidatesList.size()// instead mTotalChoiceNum.if (candId < 0 || candId > mCandidatesList.size()) {return null;}return mCandidatesList.get(candId);}/*** 从缓存中获取一页的候选词,然后放进mCandidatesList中。三种不同的获取方式:1、mIPinyinDecoderService.* imGetChoiceList* ();2、mIPinyinDecoderService.imGetPredictList;3、从mAppCompletions[]取。*/private void getCandiagtesForCache() {int fetchStart = mCandidatesList.size();int fetchSize = mTotalChoicesNum - fetchStart;if (fetchSize > MAX_PAGE_SIZE_DISPLAY) {fetchSize = MAX_PAGE_SIZE_DISPLAY;}try {List<String> newList = null;if (ImeState.STATE_INPUT == mImeState|| ImeState.STATE_IDLE == mImeState|| ImeState.STATE_COMPOSING == mImeState) {newList = mIPinyinDecoderService.imGetChoiceList(fetchStart, fetchSize, mFixedLen);} else if (ImeState.STATE_PREDICT == mImeState) {newList = mIPinyinDecoderService.imGetPredictList(fetchStart, fetchSize);} else if (ImeState.STATE_APP_COMPLETION == mImeState) {newList = new ArrayList<String>();if (null != mAppCompletions) {for (int pos = fetchStart; pos < fetchSize; pos++) {CompletionInfo ci = mAppCompletions[pos];if (null != ci) {CharSequence s = ci.getText();if (null != s)newList.add(s.toString());}}}}mCandidatesList.addAll(newList);} catch (RemoteException e) {Log.w(TAG, "PinyinDecoderService died", e);}}/*** 判断指定页是否准备好了?* * @param pageNo* @return*/public boolean pageReady(int pageNo) {// If the page number is less than 0, return falseif (pageNo < 0)return false;// Page pageNo's ending information is not ready.if (mPageStart.size() <= pageNo + 1) {return false;}return true;}/*** 准备指定页,从缓存中取出指定页的候选词。* * @param pageNo* @return*/public boolean preparePage(int pageNo) {// If the page number is less than 0, return falseif (pageNo < 0)return false;// Make sure the starting information for page pageNo is ready.if (mPageStart.size() <= pageNo) {return false;}// Page pageNo's ending information is also ready.if (mPageStart.size() > pageNo + 1) {return true;}// If cached items is enough for page pageNo.if (mCandidatesList.size() - mPageStart.elementAt(pageNo) >= MAX_PAGE_SIZE_DISPLAY) {return true;}// Try to get more items from enginegetCandiagtesForCache();// Try to find if there are available new items to display.// If no new item, return false;if (mPageStart.elementAt(pageNo) >= mCandidatesList.size()) {return false;}// If there are new items, return true;return true;}/*** 准备预报候选词* * @param history*/public void preparePredicts(CharSequence history) {if (null == history)return;resetCandidates();if (Settings.getPrediction()) {String preEdit = history.toString();int predictNum = 0;if (null != preEdit) {try {mTotalChoicesNum = mIPinyinDecoderService.imGetPredictsNum(preEdit);} catch (RemoteException e) {return;}}}preparePage(0);mFinishSelection = false;}/*** 准备从app获取候选词* * @param completions*/private void prepareAppCompletions(CompletionInfo completions[]) {resetCandidates();mAppCompletions = completions;mTotalChoicesNum = completions.length;preparePage(0);mFinishSelection = false;return;}/*** 获取当前页的长度* * @param currentPage* @return*/public int getCurrentPageSize(int currentPage) {if (mPageStart.size() <= currentPage + 1)return 0;return mPageStart.elementAt(currentPage + 1)- mPageStart.elementAt(currentPage);}/*** 获取当前页的开始位置* * @param currentPage* @return*/public int getCurrentPageStart(int currentPage) {if (mPageStart.size() < currentPage + 1)return mTotalChoicesNum;return mPageStart.elementAt(currentPage);}/*** 是否还有下一页?* * @param currentPage* @return*/public boolean pageForwardable(int currentPage) {if (mPageStart.size() <= currentPage + 1)return false;if (mPageStart.elementAt(currentPage + 1) >= mTotalChoicesNum) {return false;}return true;}/*** 是否有上一页* * @param currentPage* @return*/public boolean pageBackwardable(int currentPage) {if (currentPage > 0)return true;return false;}/*** 光标前面的字符是否是分隔符“'”* * @return*/public boolean charBeforeCursorIsSeparator() {int len = mSurface.length();if (mCursorPos > len)return false;if (mCursorPos > 0 && mSurface.charAt(mCursorPos - 1) == '\'') {return true;}return false;}/*** 获取光标位置* * @return*/public int getCursorPos() {return mCursorPos;}/*** 获取光标在拼音字符串中的位置* * @return*/public int getCursorPosInCmps() {int cursorPos = mCursorPos;int fixedLen = 0;for (int hzPos = 0; hzPos < mFixedLen; hzPos++) {if (mCursorPos >= mSplStart[hzPos + 2]) {cursorPos -= mSplStart[hzPos + 2] - mSplStart[hzPos + 1];cursorPos += 1;}}return cursorPos;}/*** 获取光标在显示的拼音字符串中的位置* * @return*/public int getCursorPosInCmpsDisplay() {int cursorPos = getCursorPosInCmps();// +2 is because: one for mSplStart[0], which is used for other// purpose(The length of the segmentation string), and another// for the first spelling which does not need a space before it.for (int pos = mFixedLen + 2; pos < mSplStart.length - 1; pos++) {if (mCursorPos <= mSplStart[pos]) {break;} else {cursorPos++;}}return cursorPos;}/*** 移动光标到末尾* * @param left*/public void moveCursorToEdge(boolean left) {if (left)mCursorPos = 0;elsemCursorPos = mSurface.length();}// Move cursor. If offset is 0, this function can be used to adjust// the cursor into the bounds of the string./*** 移动光标* * @param offset*/public void moveCursor(int offset) {if (offset > 1 || offset < -1)return;if (offset != 0) {int hzPos = 0;for (hzPos = 0; hzPos <= mFixedLen; hzPos++) {if (mCursorPos == mSplStart[hzPos + 1]) {if (offset < 0) {if (hzPos > 0) {offset = mSplStart[hzPos]- mSplStart[hzPos + 1];}} else {if (hzPos < mFixedLen) {offset = mSplStart[hzPos + 2]- mSplStart[hzPos + 1];}}break;}}}mCursorPos += offset;if (mCursorPos < 0) {mCursorPos = 0;} else if (mCursorPos > mSurface.length()) {mCursorPos = mSurface.length();}}/*** 获取拼写字符串的数量* * @return*/public int getSplNum() {return mSplStart[0];}/*** 获取固定的字符的数量* * @return*/public int getFixedLen() {return mFixedLen;}}
}

13、Settings.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;/*** Class used to maintain settings. 设置类,对配置文件进行读取写入操作。该类采用了单例模式。* * @ClassName Settings* @author keanbin*/
public class Settings {private static final String ANDPY_CONFS_KEYSOUND_KEY = "Sound";private static final String ANDPY_CONFS_VIBRATE_KEY = "Vibrate";private static final String ANDPY_CONFS_PREDICTION_KEY = "Prediction";private static boolean mKeySound;private static boolean mVibrate;private static boolean mPrediction;private static Settings mInstance = null;/*** 引用计数*/private static int mRefCount = 0;private static SharedPreferences mSharedPref = null;protected Settings(SharedPreferences pref) {mSharedPref = pref;initConfs();}/*** 获得该实例* * @param pref* @return*/public static Settings getInstance(SharedPreferences pref) {if (mInstance == null) {mInstance = new Settings(pref);}assert (pref == mSharedPref);mRefCount++;return mInstance;}/*** 设置震动、声音、预报开关标记进入配置文件*/public static void writeBack() {Editor editor = mSharedPref.edit();editor.putBoolean(ANDPY_CONFS_VIBRATE_KEY, mVibrate);editor.putBoolean(ANDPY_CONFS_KEYSOUND_KEY, mKeySound);editor.putBoolean(ANDPY_CONFS_PREDICTION_KEY, mPrediction);editor.commit();}/*** 释放对该实例的使用。*/public static void releaseInstance() {mRefCount--;if (mRefCount == 0) {mInstance = null;}}/*** 初始化,从配置文件中取出震动、声音、预报开关标记。*/private void initConfs() {mKeySound = mSharedPref.getBoolean(ANDPY_CONFS_KEYSOUND_KEY, true);mVibrate = mSharedPref.getBoolean(ANDPY_CONFS_VIBRATE_KEY, false);mPrediction = mSharedPref.getBoolean(ANDPY_CONFS_PREDICTION_KEY, true);}/*** 获得按键声音的开关* * @return*/public static boolean getKeySound() {return mKeySound;}/*** 设置按键声音的开关* * @param v*/public static void setKeySound(boolean v) {if (mKeySound == v)return;mKeySound = v;}/*** 获得震动开关* * @return*/public static boolean getVibrate() {return mVibrate;}/*** 设置震动开关* * @param v*/public static void setVibrate(boolean v) {if (mVibrate == v)return;mVibrate = v;}/*** 获得预报开关* * @return*/public static boolean getPrediction() {return mPrediction;}/*** 设置预报开关* * @param v*/public static void setPrediction(boolean v) {if (mPrediction == v)return;mPrediction = v;}
}

14、SettingsActivity.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import java.util.List;import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;/*** Setting activity of Pinyin IME. 设置页面* * @ClassName SettingsActivity* @author keanbin*/
public class SettingsActivity extends PreferenceActivity implementsPreference.OnPreferenceChangeListener {private static String TAG = "SettingsActivity";private CheckBoxPreference mKeySoundPref;private CheckBoxPreference mVibratePref;private CheckBoxPreference mPredictionPref;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);addPreferencesFromResource(R.xml.settings);PreferenceScreen prefSet = getPreferenceScreen();mKeySoundPref = (CheckBoxPreference) prefSet.findPreference(getString(R.string.setting_sound_key));mVibratePref = (CheckBoxPreference) prefSet.findPreference(getString(R.string.setting_vibrate_key));mPredictionPref = (CheckBoxPreference) prefSet.findPreference(getString(R.string.setting_prediction_key));prefSet.setOnPreferenceChangeListener(this);Settings.getInstance(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));updatePreference(prefSet, getString(R.string.setting_advanced_key));updateWidgets();}@Overrideprotected void onResume() {super.onResume();updateWidgets();}@Overrideprotected void onDestroy() {Settings.releaseInstance();super.onDestroy();}@Overrideprotected void onPause() {super.onPause();// 把用户的设置存入配置文件中Settings.setKeySound(mKeySoundPref.isChecked());Settings.setVibrate(mVibratePref.isChecked());Settings.setPrediction(mPredictionPref.isChecked());Settings.writeBack();}public boolean onPreferenceChange(Preference preference, Object newValue) {return true;}/*** 从配置文件获取之前的设置,更像UI*/private void updateWidgets() {mKeySoundPref.setChecked(Settings.getKeySound());mVibratePref.setChecked(Settings.getVibrate());mPredictionPref.setChecked(Settings.getPrediction());}/*** 设置PreferenceScreen* * @param parentPref* @param prefKey*/public void updatePreference(PreferenceGroup parentPref, String prefKey) {Preference preference = parentPref.findPreference(prefKey);if (preference == null) {return;}Intent intent = preference.getIntent();if (intent != null) {PackageManager pm = getPackageManager();List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);int listSize = list.size();if (listSize == 0)parentPref.removePreference(preference);}}
}

15、SkbContainer.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import android.content.Context;
import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.os.Handler;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.ViewFlipper;/*** The top container to host soft keyboard view(s). 软键盘View的集装箱,主持一个软件盘View。*/
public class SkbContainer extends RelativeLayout implements OnTouchListener {/*** For finger touch, user tends to press the bottom part of the target key,* or he/she even presses the area out of it, so it is necessary to make a* simple bias correction. If the input method runs on emulator, no bias* correction will be used. 对于手指触摸点的Y坐标进行的偏好 。*/private static final int Y_BIAS_CORRECTION = -10;/*** Used to skip these move events whose position is too close to the* previous touch events. 触摸移动的x坐标和y坐标的差值如果在MOVE_TOLERANCE之内,触摸的移动事件就被抛弃。*/private static final int MOVE_TOLERANCE = 6;/*** If this member is true, PopupWindow is used to show on-key highlight* effect. 弹出框是否用于重点显示效果。*/private static boolean POPUPWINDOW_FOR_PRESSED_UI = false;/*** The current soft keyboard layout. 当前的软键盘布局文件资源ID。* * @see com.android.inputmethod.pinyin.InputModeSwitcher for detailed layout*      definitions.* * */private int mSkbLayout = 0;/*** The input method service. 输入法服务*/private InputMethodService mService;/*** Input mode switcher used to switch between different modes like Chinese,* English, etc. 输入法变换器*/private InputModeSwitcher mInputModeSwitcher;/*** The gesture detector. 手势识别*/private GestureDetector mGestureDetector;private Environment mEnvironment;/*** view切换管理*/private ViewFlipper mSkbFlipper;/*** The popup balloon hint for key press/release. 气泡*/private BalloonHint mBalloonPopup;/*** The on-key balloon hint for key press/release. 气泡*/private BalloonHint mBalloonOnKey = null;/** The major sub soft keyboard. 主要视图:软键盘视图。 */private SoftKeyboardView mMajorView;/*** The last parameter when function {@link #toggleCandidateMode(boolean)}* was called. 最后的候选词显示。*/private boolean mLastCandidatesShowing;/*** Used to indicate whether a popup soft keyboard is shown. 一个弹出副的软件盘是否在显示 ?*/private boolean mPopupSkbShow = false;/*** Used to indicate whether a popup soft keyboard is just shown, and waits* for the touch event to release. After the release, the popup window can* response to touch events.* 是否一个副软键盘弹出框正在显示,并且等待触摸事件释放,触摸事件释放之后,副软键盘可以响应触摸事件?**/private boolean mPopupSkbNoResponse = false;/** Popup sub keyboard. 副软键盘弹出框 */private PopupWindow mPopupSkb;/** The view of the popup sub soft keyboard. 副软键盘弹出框中的软键盘视图 */private SoftKeyboardView mPopupSkbView;private int mPopupX;private int mPopupY;/*** When user presses a key, a timer is started, when it times out, it is* necessary to detect whether user still holds the key.* 当用户按下一个按键,一个定时器启动,定时器时间到的时候,需要检查用户是否还按住这个键。*/private volatile boolean mWaitForTouchUp = false;/*** When user drags on the soft keyboard and the distance is enough, this* drag will be recognized as a gesture and a gesture-based action will be* taken, in this situation, ignore the consequent events.* 当用户在键盘上拖拽足够的距离后,是否忽略随之而生的事件?*/private volatile boolean mDiscardEvent = false;/*** For finger touch, user tends to press the bottom part of the target key,* or he/she even presses the area out of it, so it is necessary to make a* simple bias correction in Y. 对用户点击的触摸点进行Y坐标的纠正。*/private int mYBiasCorrection = 0;/*** The x coordination of the last touch event. 最后触摸事件的x坐标。*/private int mXLast;/*** The y coordination of the last touch event. 最后触摸事件的 y坐标。*/private int mYLast;/*** The soft keyboard view. 软键盘视图*/private SoftKeyboardView mSkv;/*** The position of the soft keyboard view in the container. 软键盘视图的集装箱的位置*/private int mSkvPosInContainer[] = new int[2];/*** The key pressed by user.用户按下的按键*/private SoftKey mSoftKeyDown = null;/*** Used to timeout a press if user holds the key for a long time. 长按定时器*/private LongPressTimer mLongPressTimer;Context mContext;/*** For temporary use. 临时使用*/private int mXyPosTmp[] = new int[2];public SkbContainer(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;mEnvironment = Environment.getInstance();mLongPressTimer = new LongPressTimer(this);// If it runs on an emulator, no bias correction// if ("1".equals(SystemProperties.get("ro.kernel.qemu"))) {// mYBiasCorrection = 0;// } else {mYBiasCorrection = Y_BIAS_CORRECTION;// }// 创建弹出气泡mBalloonPopup = new BalloonHint(context, this, MeasureSpec.AT_MOST);if (POPUPWINDOW_FOR_PRESSED_UI) {mBalloonOnKey = new BalloonHint(context, this, MeasureSpec.AT_MOST);}// 常见弹出软键盘mPopupSkb = new PopupWindow(mContext);mPopupSkb.setBackgroundDrawable(null);mPopupSkb.setClippingEnabled(false);}public void setService(InputMethodService service) {mService = service;}public void setInputModeSwitcher(InputModeSwitcher inputModeSwitcher) {mInputModeSwitcher = inputModeSwitcher;}public void setGestureDetector(GestureDetector gestureDetector) {mGestureDetector = gestureDetector;}// TODO 这个函数用来做什么?public boolean isCurrentSkbSticky() {if (null == mMajorView)return true;SoftKeyboard skb = mMajorView.getSoftKeyboard();if (null != skb) {return skb.getStickyFlag();}return true;}/*** 切换候选词模式。逻辑简介:先从mInputModeSwitcher输入法模式交换器中获得中文候选词模式状态,然后判断是否是要切入候选词模式,* 如果是键盘就变为中文候选词模式状态* ,如果不是,键盘就消除中文候选词模式状态,变为mInputModeSwitcher中的mToggleStates设置键盘的状态。* * @param candidatesShowing*/public void toggleCandidateMode(boolean candidatesShowing) {if (null == mMajorView || !mInputModeSwitcher.isChineseText()|| mLastCandidatesShowing == candidatesShowing)return;mLastCandidatesShowing = candidatesShowing;SoftKeyboard skb = mMajorView.getSoftKeyboard();if (null == skb)return;int state = mInputModeSwitcher.getTooggleStateForCnCand();if (!candidatesShowing) {skb.disableToggleState(state, false);skb.enableToggleStates(mInputModeSwitcher.getToggleStates());} else {skb.enableToggleState(state, false);}mMajorView.invalidate();}/*** 更新输入法模式。逻辑简介:先获取软键盘xml布局文件,然后更新软键盘布局,设置软键盘状态。*/public void updateInputMode() {int skbLayout = mInputModeSwitcher.getSkbLayout();if (mSkbLayout != skbLayout) {mSkbLayout = skbLayout;updateSkbLayout();}mLastCandidatesShowing = false;if (null == mMajorView)return;SoftKeyboard skb = mMajorView.getSoftKeyboard();if (null == skb)return;skb.enableToggleStates(mInputModeSwitcher.getToggleStates());invalidate();return;}/*** 更新软键盘布局*/private void updateSkbLayout() {int screenWidth = mEnvironment.getScreenWidth();int keyHeight = mEnvironment.getKeyHeight();int skbHeight = mEnvironment.getSkbHeight();Resources r = mContext.getResources();if (null == mSkbFlipper) {mSkbFlipper = (ViewFlipper) findViewById(R.id.alpha_floatable);}mMajorView = (SoftKeyboardView) mSkbFlipper.getChildAt(0);SoftKeyboard majorSkb = null;SkbPool skbPool = SkbPool.getInstance();switch (mSkbLayout) {case R.xml.skb_qwerty:majorSkb = skbPool.getSoftKeyboard(R.xml.skb_qwerty,R.xml.skb_qwerty, screenWidth, skbHeight, mContext);break;case R.xml.skb_sym1:majorSkb = skbPool.getSoftKeyboard(R.xml.skb_sym1, R.xml.skb_sym1,screenWidth, skbHeight, mContext);break;case R.xml.skb_sym2:majorSkb = skbPool.getSoftKeyboard(R.xml.skb_sym2, R.xml.skb_sym2,screenWidth, skbHeight, mContext);break;case R.xml.skb_smiley:majorSkb = skbPool.getSoftKeyboard(R.xml.skb_smiley,R.xml.skb_smiley, screenWidth, skbHeight, mContext);break;case R.xml.skb_phone:majorSkb = skbPool.getSoftKeyboard(R.xml.skb_phone,R.xml.skb_phone, screenWidth, skbHeight, mContext);break;default:}if (null == majorSkb || !mMajorView.setSoftKeyboard(majorSkb)) {return;}mMajorView.setBalloonHint(mBalloonOnKey, mBalloonPopup, false);mMajorView.invalidate();}/*** 响应按键事件。调用输入法服务的响应按键事件方法,把按键事件流到输入法服务里面去处理。* * @param sKey*/private void responseKeyEvent(SoftKey sKey) {if (null == sKey)return;((PinyinIME) mService).responseSoftKeyEvent(sKey);return;}/*** 返回软键盘视图。逻辑简介:先判断副软键盘弹出框是否在显示,是的话,就判断坐标点是否在副软键盘的区域内,如果是,就返回副软键盘弹出框,* 否则返回null。如果副软键盘弹出框没有显示,就直接返回主软键盘视图mMajorView。* * @param x* @param y* @param positionInParent* @return*/private SoftKeyboardView inKeyboardView(int x, int y,int positionInParent[]) {if (mPopupSkbShow) {if (mPopupX <= x && mPopupX + mPopupSkb.getWidth() > x&& mPopupY <= y && mPopupY + mPopupSkb.getHeight() > y) {positionInParent[0] = mPopupX;positionInParent[1] = mPopupY;mPopupSkbView.setOffsetToSkbContainer(positionInParent);return mPopupSkbView;}return null;}return mMajorView;}/*** 弹出副软键盘弹出框。副软键盘弹出框的软键盘xml资源ID是存放在按下的按键mSoftKeyDown的属性mPopupSkbId中的。* 弹出副软键盘弹出框后,主软键盘视图mMajorView会被隐藏。*/private void popupSymbols() {int popupResId = mSoftKeyDown.getPopupResId();if (popupResId > 0) {int skbContainerWidth = getWidth();int skbContainerHeight = getHeight();// The paddings of the background are not included.int miniSkbWidth = (int) (skbContainerWidth * 0.8);int miniSkbHeight = (int) (skbContainerHeight * 0.23);SkbPool skbPool = SkbPool.getInstance();SoftKeyboard skb = skbPool.getSoftKeyboard(popupResId, popupResId,miniSkbWidth, miniSkbHeight, mContext);if (null == skb)return;mPopupX = (skbContainerWidth - skb.getSkbTotalWidth()) / 2;mPopupY = (skbContainerHeight - skb.getSkbTotalHeight()) / 2;if (null == mPopupSkbView) {mPopupSkbView = new SoftKeyboardView(mContext, null);mPopupSkbView.onMeasure(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);}mPopupSkbView.setOnTouchListener(this);mPopupSkbView.setSoftKeyboard(skb);mPopupSkbView.setBalloonHint(mBalloonOnKey, mBalloonPopup, true);mPopupSkb.setContentView(mPopupSkbView);mPopupSkb.setWidth(skb.getSkbCoreWidth()+ mPopupSkbView.getPaddingLeft()+ mPopupSkbView.getPaddingRight());mPopupSkb.setHeight(skb.getSkbCoreHeight()+ mPopupSkbView.getPaddingTop()+ mPopupSkbView.getPaddingBottom());getLocationInWindow(mXyPosTmp);mPopupSkb.showAtLocation(this, Gravity.NO_GRAVITY, mPopupX, mPopupY+ mXyPosTmp[1]);mPopupSkbShow = true;mPopupSkbNoResponse = true;// Invalidate itself to dim the current soft keyboards.dimSoftKeyboard(true);resetKeyPress(0);}}/*** 隐藏主软键盘视图* * @param dimSkb*/private void dimSoftKeyboard(boolean dimSkb) {mMajorView.dimSoftKeyboard(dimSkb);}/*** 隐藏副软键盘弹出框,显示主软键盘视图。*/private void dismissPopupSkb() {mPopupSkb.dismiss();mPopupSkbShow = false;dimSoftKeyboard(false);resetKeyPress(0);}/*** 重置按下按键* * @param delay*/private void resetKeyPress(long delay) {mLongPressTimer.removeTimer();if (null != mSkv) {mSkv.resetKeyPress(delay);}}/*** 副软键盘弹出框显示的时候,如果realAction为true,那么就调用dismissPopupSkb()隐藏副软键盘弹出框,显示主软键盘视图。* * @param realAction* @return*/public boolean handleBack(boolean realAction) {if (mPopupSkbShow) {if (!realAction)return true;dismissPopupSkb();mDiscardEvent = true;return true;}return false;}/*** 隐藏副软键盘弹出框*/public void dismissPopups() {handleBack(true);resetKeyPress(0);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {Environment env = Environment.getInstance();int measuredWidth = env.getScreenWidth();int measuredHeight = getPaddingTop();measuredHeight += env.getSkbHeight();widthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth,MeasureSpec.EXACTLY);heightMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight,MeasureSpec.EXACTLY);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overridepublic boolean onTouchEvent(MotionEvent event) {super.onTouchEvent(event);if (mSkbFlipper.isFlipping()) {resetKeyPress(0);return true;}int x = (int) event.getX();int y = (int) event.getY();// Bias correctiony = y + mYBiasCorrection;// Ignore short-distance movement event to get better performance.if (event.getAction() == MotionEvent.ACTION_MOVE) {if (Math.abs(x - mXLast) <= MOVE_TOLERANCE&& Math.abs(y - mYLast) <= MOVE_TOLERANCE) {return true;}}mXLast = x;mYLast = y;if (!mPopupSkbShow) {// mGestureDetector的监听器在输入法服务PinyinIME中。if (mGestureDetector.onTouchEvent(event)) {resetKeyPress(0);mDiscardEvent = true;return true;}}switch (event.getAction()) {case MotionEvent.ACTION_DOWN:resetKeyPress(0);mWaitForTouchUp = true;mDiscardEvent = false;mSkv = null;mSoftKeyDown = null;mSkv = inKeyboardView(x, y, mSkvPosInContainer);if (null != mSkv) {mSoftKeyDown = mSkv.onKeyPress(x - mSkvPosInContainer[0], y- mSkvPosInContainer[1], mLongPressTimer, false);}break;case MotionEvent.ACTION_MOVE:if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {break;}if (mDiscardEvent) {resetKeyPress(0);break;}if (mPopupSkbShow && mPopupSkbNoResponse) {break;}SoftKeyboardView skv = inKeyboardView(x, y, mSkvPosInContainer);if (null != skv) {if (skv != mSkv) {mSkv = skv;mSoftKeyDown = mSkv.onKeyPress(x - mSkvPosInContainer[0], y- mSkvPosInContainer[1], mLongPressTimer, true);} else if (null != skv) {if (null != mSkv) {mSoftKeyDown = mSkv.onKeyMove(x - mSkvPosInContainer[0], y- mSkvPosInContainer[1]);if (null == mSoftKeyDown) {mDiscardEvent = true;}}}}break;case MotionEvent.ACTION_UP:if (mDiscardEvent) {resetKeyPress(0);break;}mWaitForTouchUp = false;// The view which got the {@link MotionEvent#ACTION_DOWN} event is// always used to handle this event.if (null != mSkv) {mSkv.onKeyRelease(x - mSkvPosInContainer[0], y- mSkvPosInContainer[1]);}if (!mPopupSkbShow || !mPopupSkbNoResponse) {responseKeyEvent(mSoftKeyDown);}if (mSkv == mPopupSkbView && !mPopupSkbNoResponse) {dismissPopupSkb();}mPopupSkbNoResponse = false;break;case MotionEvent.ACTION_CANCEL:break;}if (null == mSkv) {return false;}return true;}// Function for interface OnTouchListener, it is used to handle touch events// which will be delivered to the popup soft keyboard view.public boolean onTouch(View v, MotionEvent event) {// Translate the event to fit to the container.MotionEvent newEv = MotionEvent.obtain(event.getDownTime(),event.getEventTime(), event.getAction(),event.getX() + mPopupX, event.getY() + mPopupY,event.getPressure(), event.getSize(), event.getMetaState(),event.getXPrecision(), event.getYPrecision(),event.getDeviceId(), event.getEdgeFlags());boolean ret = onTouchEvent(newEv);return ret;}/*** 长按定时器* * @ClassName LongPressTimer* @author keanbin*/class LongPressTimer extends Handler implements Runnable {/*** When user presses a key for a long time, the timeout interval to* generate first {@link #LONG_PRESS_KEYNUM1} key events. 长按时间一*/public static final int LONG_PRESS_TIMEOUT1 = 500;/*** When user presses a key for a long time, after the first* {@link #LONG_PRESS_KEYNUM1} key events, this timeout interval will be* used. 长按时间二*/private static final int LONG_PRESS_TIMEOUT2 = 100;/*** When user presses a key for a long time, after the first* {@link #LONG_PRESS_KEYNUM2} key events, this timeout interval will be* used. 长按时间三*/private static final int LONG_PRESS_TIMEOUT3 = 100;/*** When user presses a key for a long time, after the first* {@link #LONG_PRESS_KEYNUM1} key events, timeout interval* {@link #LONG_PRESS_TIMEOUT2} will be used instead.* */public static final int LONG_PRESS_KEYNUM1 = 1;/*** When user presses a key for a long time, after the first* {@link #LONG_PRESS_KEYNUM2} key events, timeout interval* {@link #LONG_PRESS_TIMEOUT3} will be used instead.*/public static final int LONG_PRESS_KEYNUM2 = 3;SkbContainer mSkbContainer;private int mResponseTimes = 0;public LongPressTimer(SkbContainer skbContainer) {mSkbContainer = skbContainer;}public void startTimer() {postAtTime(this, SystemClock.uptimeMillis() + LONG_PRESS_TIMEOUT1);mResponseTimes = 0;}public boolean removeTimer() {removeCallbacks(this);return true;}public void run() {if (mWaitForTouchUp) {mResponseTimes++;if (mSoftKeyDown.repeatable()) {if (mSoftKeyDown.isUserDefKey()) {// 用户定义的按键if (1 == mResponseTimes) {if (mInputModeSwitcher.tryHandleLongPressSwitch(mSoftKeyDown.mKeyCode)) {mDiscardEvent = true;resetKeyPress(0);}}} else {// 系统定义的按键,长按相当于执行重复按键功能,mResponseTimes是按的次数responseKeyEvent(mSoftKeyDown);long timeout;if (mResponseTimes < LONG_PRESS_KEYNUM1) {timeout = LONG_PRESS_TIMEOUT1;} else if (mResponseTimes < LONG_PRESS_KEYNUM2) {timeout = LONG_PRESS_TIMEOUT2;} else {timeout = LONG_PRESS_TIMEOUT3;}postAtTime(this, SystemClock.uptimeMillis() + timeout);}} else {if (1 == mResponseTimes) {popupSymbols();}}}}}
}

16、SkbPool.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import java.util.Vector;import android.content.Context;/*** Class used to cache previously loaded soft keyboard layouts.*/
/*** 软键盘内存池,该类采用单例模式,它有两个向量列表:软键盘模版列表、软键盘列表。* * @ClassName SkbPool* @author keanbin*/
public class SkbPool {private static SkbPool mInstance = null;private Vector<SkbTemplate> mSkbTemplates = new Vector<SkbTemplate>();private Vector<SoftKeyboard> mSoftKeyboards = new Vector<SoftKeyboard>();private SkbPool() {}public static SkbPool getInstance() {if (null == mInstance)mInstance = new SkbPool();return mInstance;}public void resetCachedSkb() {mSoftKeyboards.clear();}/*** 获取软件盘模版。逻辑简介:首先先从mSkbTemplates列表中获取,如果没有获取到,* 就调用XmlKeyboardLoader解析资源文件ID为skbTemplateId的软键盘模版xml文件* ,生成一个模版,并加入mSkbTemplates列表中。* * @param skbTemplateId* @param context* @return*/public SkbTemplate getSkbTemplate(int skbTemplateId, Context context) {for (int i = 0; i < mSkbTemplates.size(); i++) {SkbTemplate t = mSkbTemplates.elementAt(i);if (t.getSkbTemplateId() == skbTemplateId) {return t;}}if (null != context) {XmlKeyboardLoader xkbl = new XmlKeyboardLoader(context);SkbTemplate t = xkbl.loadSkbTemplate(skbTemplateId);if (null != t) {mSkbTemplates.add(t);return t;}}return null;}// Try to find the keyboard in the pool with the cache id. If there is no// keyboard found, try to load it with the given xml id./*** 获取软件盘。逻辑简介:首先先从mSoftKeyboards列表中获取,如果没有获取到,* 就调用XmlKeyboardLoader解析资源文件ID为skbXmlId的软键盘xml文件* ,生成一个软键盘,并加入mSoftKeyboards列表中。* * @param skbCacheId* @param skbXmlId* @param skbWidth* @param skbHeight* @param context* @return*/public SoftKeyboard getSoftKeyboard(int skbCacheId, int skbXmlId,int skbWidth, int skbHeight, Context context) {for (int i = 0; i < mSoftKeyboards.size(); i++) {SoftKeyboard skb = mSoftKeyboards.elementAt(i);if (skb.getCacheId() == skbCacheId && skb.getSkbXmlId() == skbXmlId) {skb.setSkbCoreSize(skbWidth, skbHeight);skb.setNewlyLoadedFlag(false);return skb;}}if (null != context) {XmlKeyboardLoader xkbl = new XmlKeyboardLoader(context);SoftKeyboard skb = xkbl.loadKeyboard(skbXmlId, skbWidth, skbHeight);if (skb != null) {if (skb.getCacheFlag()) {skb.setCacheId(skbCacheId);mSoftKeyboards.add(skb);}}return skb;}return null;}
}

17、SkbTemplate.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import java.util.Vector;import android.graphics.drawable.Drawable;/*** Key icon definition. It is defined in soft keyboard template. A soft keyboard* can refer to such an icon in its xml file directly to improve performance.* 按键图标,包含按键code、按键图标、按键弹出图标*/
class KeyIconRecord {int keyCode;Drawable icon;Drawable iconPopup;
}/*** Default definition for a certain key. It is defined in soft keyboard* template. A soft keyboard can refer to a default key in its xml file. Nothing* of the key can be overwritten, including the size.*/
class KeyRecord {int keyId;SoftKey softKey;
}/*** Soft keyboard template used by soft keyboards to share common resources. In* this way, memory cost is reduced.*/
public class SkbTemplate {private int mSkbTemplateId; // 模版的xml文件资源ID,也是模版在软键盘池中的IDprivate Drawable mSkbBg;private Drawable mBalloonBg;private Drawable mPopupBg;private float mXMargin = 0;private float mYMargin = 0;/** Key type list. */private Vector<SoftKeyType> mKeyTypeList = new Vector<SoftKeyType>();/*** Default key icon list. It is only for keys which do not have popup icons.*/private Vector<KeyIconRecord> mKeyIconRecords = new Vector<KeyIconRecord>();/*** Default key list.*/private Vector<KeyRecord> mKeyRecords = new Vector<KeyRecord>();public SkbTemplate(int skbTemplateId) {mSkbTemplateId = skbTemplateId;}public int getSkbTemplateId() {return mSkbTemplateId;}public void setBackgrounds(Drawable skbBg, Drawable balloonBg,Drawable popupBg) {mSkbBg = skbBg;mBalloonBg = balloonBg;mPopupBg = popupBg;}public Drawable getSkbBackground() {return mSkbBg;}public Drawable getBalloonBackground() {return mBalloonBg;}public Drawable getPopupBackground() {return mPopupBg;}public void setMargins(float xMargin, float yMargin) {mXMargin = xMargin;mYMargin = yMargin;}public float getXMargin() {return mXMargin;}public float getYMargin() {return mYMargin;}public SoftKeyType createKeyType(int id, Drawable bg, Drawable hlBg) {return new SoftKeyType(id, bg, hlBg);}public boolean addKeyType(SoftKeyType keyType) {// The newly added item should have the right id.if (mKeyTypeList.size() != keyType.mKeyTypeId)return false;mKeyTypeList.add(keyType);return true;}public SoftKeyType getKeyType(int typeId) {if (typeId < 0 || typeId > mKeyTypeList.size())return null;return mKeyTypeList.elementAt(typeId);}public void addDefaultKeyIcons(int keyCode, Drawable icon,Drawable iconPopup) {if (null == icon || null == iconPopup)return;KeyIconRecord iconRecord = new KeyIconRecord();iconRecord.icon = icon;iconRecord.iconPopup = iconPopup;iconRecord.keyCode = keyCode;int size = mKeyIconRecords.size();int pos = 0;while (pos < size) {if (mKeyIconRecords.get(pos).keyCode >= keyCode)break;pos++;}mKeyIconRecords.add(pos, iconRecord);}public Drawable getDefaultKeyIcon(int keyCode) {int size = mKeyIconRecords.size();int pos = 0;while (pos < size) {KeyIconRecord iconRecord = mKeyIconRecords.get(pos);if (iconRecord.keyCode < keyCode) {pos++;continue;}if (iconRecord.keyCode == keyCode) {return iconRecord.icon;}return null;}return null;}public Drawable getDefaultKeyIconPopup(int keyCode) {int size = mKeyIconRecords.size();int pos = 0;while (pos < size) {KeyIconRecord iconRecord = mKeyIconRecords.get(pos);if (iconRecord.keyCode < keyCode) {pos++;continue;}if (iconRecord.keyCode == keyCode) {return iconRecord.iconPopup;}return null;}return null;}public void addDefaultKey(int keyId, SoftKey softKey) {if (null == softKey)return;KeyRecord keyRecord = new KeyRecord();keyRecord.keyId = keyId;keyRecord.softKey = softKey;int size = mKeyRecords.size();int pos = 0;while (pos < size) {if (mKeyRecords.get(pos).keyId >= keyId)break;pos++;}mKeyRecords.add(pos, keyRecord);}public SoftKey getDefaultKey(int keyId) {int size = mKeyRecords.size();int pos = 0;while (pos < size) {KeyRecord keyRecord = mKeyRecords.get(pos);if (keyRecord.keyId < keyId) {pos++;continue;}if (keyRecord.keyId == keyId) {return keyRecord.softKey;}return null;}return null;}
}class SoftKeyType {public static final int KEYTYPE_ID_NORMAL_KEY = 0;public int mKeyTypeId;public Drawable mKeyBg;public Drawable mKeyHlBg;public int mColor;public int mColorHl;public int mColorBalloon;SoftKeyType(int id, Drawable bg, Drawable hlBg) {mKeyTypeId = id;mKeyBg = bg;mKeyHlBg = hlBg;}public void setColors(int color, int colorHl, int colorBalloon) {mColor = color;mColorHl = colorHl;mColorBalloon = colorBalloon;}
}

18、SoftKey.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import android.graphics.drawable.Drawable;/*** Class for soft keys which defined in the keyboard xml file. A soft key can be* a basic key or a toggling key. 按键* * @see com.android.inputmethod.pinyin.SoftKeyToggle*/
public class SoftKey {protected static final int KEYMASK_REPEAT = 0x10000000;protected static final int KEYMASK_BALLOON = 0x20000000;/*** For a finger touch device, after user presses a key, there will be some* consequent moving events because of the changing in touching pressure. If* the moving distance in x is within this threshold, the moving events will* be ignored. 触摸移动事件有效的x坐标差值,移动的x坐标差值小于有效的x坐标差值,移动事件被抛弃。*/public static final int MAX_MOVE_TOLERANCE_X = 0;/*** For a finger touch device, after user presses a key, there will be some* consequent moving events because of the changing in touching pressure. If* the moving distance in y is within this threshold, the moving events will* be ignored. 触摸移动事件有效的y坐标差值,移动的x坐标差值小于有效的y坐标差值,移动事件被抛弃。*/public static final int MAX_MOVE_TOLERANCE_Y = 0;/*** Used to indicate the type and attributes of this key. the lowest 8 bits* should be reserved for SoftkeyToggle. 按键的属性和类型,最低的8位留给软键盘变换状态。*/protected int mKeyMask;/** key的类型 */protected SoftKeyType mKeyType;/** key的图标 */protected Drawable mKeyIcon;/** key的弹出图标 */protected Drawable mKeyIconPopup;/** key的文本 */protected String mKeyLabel;/** key的code */protected int mKeyCode;/*** If this value is not 0, this key can be used to popup a sub soft keyboard* when user presses it for some time.* 软件盘弹出对话框的id。如果这个值不为空,那么当它被长按的时候,弹出一个副软键盘。*/public int mPopupSkbId;/** 键盘宽度的百分比 ,mLeft = (int) (mLeftF * skbWidth); */public float mLeftF;public float mRightF;/** 键盘高度的百分比 */public float mTopF;public float mBottomF;// TODO 以下的 区域坐标是相对于什么的?是全局还是相对于父视图的?public int mLeft;public int mRight;public int mTop;public int mBottom;/*** 设置按键的类型、图标、弹出图标* * @param keyType* @param keyIcon* @param keyIconPopup*/public void setKeyType(SoftKeyType keyType, Drawable keyIcon,Drawable keyIconPopup) {mKeyType = keyType;mKeyIcon = keyIcon;mKeyIconPopup = keyIconPopup;}// The caller guarantees that all parameters are in [0, 1]public void setKeyDimensions(float left, float top, float right,float bottom) {mLeftF = left;mTopF = top;mRightF = right;mBottomF = bottom;}public void setKeyAttribute(int keyCode, String label, boolean repeat,boolean balloon) {mKeyCode = keyCode;mKeyLabel = label;if (repeat) {mKeyMask |= KEYMASK_REPEAT;} else {mKeyMask &= (~KEYMASK_REPEAT);}if (balloon) {mKeyMask |= KEYMASK_BALLOON;} else {mKeyMask &= (~KEYMASK_BALLOON);}}/*** 设置副软键盘弹出框* * @param popupSkbId*/public void setPopupSkbId(int popupSkbId) {mPopupSkbId = popupSkbId;}// Call after setKeyDimensions(). The caller guarantees that the// keyboard with and height are valid./*** 设置按键的区域* * @param skbWidth*            键盘的宽度* @param skbHeight*            键盘的高度*/public void setSkbCoreSize(int skbWidth, int skbHeight) {mLeft = (int) (mLeftF * skbWidth);mRight = (int) (mRightF * skbWidth);mTop = (int) (mTopF * skbHeight);mBottom = (int) (mBottomF * skbHeight);}public Drawable getKeyIcon() {return mKeyIcon;}public Drawable getKeyIconPopup() {if (null != mKeyIconPopup) {return mKeyIconPopup;}return mKeyIcon;}/*** 获取按键的key code* * @return*/public int getKeyCode() {return mKeyCode;}/*** 获取按键的字符* * @return*/public String getKeyLabel() {return mKeyLabel;}/*** 大小写转换* * @param upperCase*/public void changeCase(boolean upperCase) {if (null != mKeyLabel) {if (upperCase)mKeyLabel = mKeyLabel.toUpperCase();elsemKeyLabel = mKeyLabel.toLowerCase();}}public Drawable getKeyBg() {return mKeyType.mKeyBg;}public Drawable getKeyHlBg() {return mKeyType.mKeyHlBg;}public int getColor() {return mKeyType.mColor;}public int getColorHl() {return mKeyType.mColorHl;}public int getColorBalloon() {return mKeyType.mColorBalloon;}/*** 是否是系统的keycode* * @return*/public boolean isKeyCodeKey() {if (mKeyCode > 0)return true;return false;}/*** 是否是用户定义的keycode* * @return*/public boolean isUserDefKey() {if (mKeyCode < 0)return true;return false;}/*** 是否是字符按键* * @return*/public boolean isUniStrKey() {if (null != mKeyLabel && mKeyCode == 0)return true;return false;}/*** 是否需要弹出气泡* * @return*/public boolean needBalloon() {return (mKeyMask & KEYMASK_BALLOON) != 0;}/*** 是否有重复按下功能,即连续按这个按键是否执行不同的操作。* * @return*/public boolean repeatable() {return (mKeyMask & KEYMASK_REPEAT) != 0;}public int getPopupResId() {return mPopupSkbId;}public int width() {return mRight - mLeft;}public int height() {return mBottom - mTop;}/*** 判断坐标是否在该按键的区域内* * @param x* @param y* @return*/public boolean moveWithinKey(int x, int y) {if (mLeft - MAX_MOVE_TOLERANCE_X <= x&& mTop - MAX_MOVE_TOLERANCE_Y <= y&& mRight + MAX_MOVE_TOLERANCE_X > x&& mBottom + MAX_MOVE_TOLERANCE_Y > y) {return true;}return false;}@Overridepublic String toString() {String str = "\n";str += "  keyCode: " + String.valueOf(mKeyCode) + "\n";str += "  keyMask: " + String.valueOf(mKeyMask) + "\n";str += "  keyLabel: " + (mKeyLabel == null ? "null" : mKeyLabel) + "\n";str += "  popupResId: " + String.valueOf(mPopupSkbId) + "\n";str += "  Position: " + String.valueOf(mLeftF) + ", "+ String.valueOf(mTopF) + ", " + String.valueOf(mRightF) + ", "+ String.valueOf(mBottomF) + "\n";return str;}
}

19、SoftKeyboard.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import java.util.ArrayList;
import java.util.List;import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.KeyEvent;import com.keanbin.pinyinime.InputModeSwitcher.ToggleStates;/*** Class used to represent a soft keyboard definition, including the height, the* background image, the image for high light, the keys, etc.* 一个软件盘的定义,包括按键的排列布局,宽度高度。*/
public class SoftKeyboard {/*** The XML resource id for this soft keyboard. 软键盘的xml文件ID* */private int mSkbXmlId;/*** Do we need to cache this soft keyboard? 是否缓存这个软件盘?*/private boolean mCacheFlag;/*** After user switches to this soft keyboard, if this flag is true, this* soft keyboard will be kept unless explicit switching operation is* performed, otherwise IME will switch back to the previous keyboard layout* whenever user clicks on any none-function key.**/private boolean mStickyFlag;/*** The cache id for this soft keyboard. It is used to identify it in the* soft keyboard pool. 缓存ID*/private int mCacheId;/*** Used to indicate whether this soft keyboard is newly loaded from an XML* file or is just gotten from the soft keyboard pool.* 是否是最近从xml文件导入的软键盘,或者刚刚从软键盘池中得到的软键盘?*/private boolean mNewlyLoadedFlag = true;/*** The width of the soft keyboard. 键盘的宽度*/private int mSkbCoreWidth;/*** The height of the soft keyboard. 键盘的高度*/private int mSkbCoreHeight;/*** The soft keyboard template for this soft keyboard.软键盘的模版*/private SkbTemplate mSkbTemplate;/*** Used to indicate whether this soft keyboard is a QWERTY keyboard.* 是否使用标准的软键盘*/private boolean mIsQwerty;/*** When {@link #mIsQwerty} is true, this member is Used to indicate that the* soft keyboard should be displayed in uppercase. 是否是标准键盘的大写*/private boolean mIsQwertyUpperCase;/*** The id of the rows which are enabled. Rows with id* {@link KeyRow#ALWAYS_SHOW_ROW_ID} are always enabled. 可用行的id*/private int mEnabledRowId;/*** Rows in this soft keyboard. Each row has a id. Only matched rows will be* enabled. 按键排列的行的链表,每个元素都是一行。*/private List<KeyRow> mKeyRows;/*** Background of the soft keyboard. If it is null, the one in the soft* keyboard template will be used. 软键盘的背景,如果为空,就会使用软键盘模版中的背景。**/public Drawable mSkbBg;/*** Background for key balloon. If it is null, the one in the soft keyboard* template will be used. 气泡的背景,如果为空,就会使用软键盘模版中的气泡背景。**/private Drawable mBalloonBg;/*** Background for popup mini soft keyboard. If it is null, the one in the* soft keyboard template will be used. 弹出框的背景,如果为空,就会使用软键盘模版中的弹出框背景。**/private Drawable mPopupBg;/** The left and right margin of a key. 一个按键的左右间隔 */private float mKeyXMargin = 0;/** The top and bottom margin of a key. 一个按键的上下间隔 */private float mKeyYMargin = 0;private Rect mTmpRect = new Rect();public SoftKeyboard(int skbXmlId, SkbTemplate skbTemplate, int skbWidth,int skbHeight) {mSkbXmlId = skbXmlId;mSkbTemplate = skbTemplate;mSkbCoreWidth = skbWidth;mSkbCoreHeight = skbHeight;}public void setFlags(boolean cacheFlag, boolean stickyFlag,boolean isQwerty, boolean isQwertyUpperCase) {mCacheFlag = cacheFlag;mStickyFlag = stickyFlag;mIsQwerty = isQwerty;mIsQwertyUpperCase = isQwertyUpperCase;}public boolean getCacheFlag() {return mCacheFlag;}public void setCacheId(int cacheId) {mCacheId = cacheId;}public boolean getStickyFlag() {return mStickyFlag;}public void setSkbBackground(Drawable skbBg) {mSkbBg = skbBg;}public void setPopupBackground(Drawable popupBg) {mPopupBg = popupBg;}public void setKeyBalloonBackground(Drawable balloonBg) {mBalloonBg = balloonBg;}public void setKeyMargins(float xMargin, float yMargin) {mKeyXMargin = xMargin;mKeyYMargin = yMargin;}public int getCacheId() {return mCacheId;}/*** 重置软键盘,只做了清除mKeyRows列表的操作。*/public void reset() {if (null != mKeyRows)mKeyRows.clear();}public void setNewlyLoadedFlag(boolean newlyLoadedFlag) {mNewlyLoadedFlag = newlyLoadedFlag;}public boolean getNewlyLoadedFlag() {return mNewlyLoadedFlag;}/*** 开始新的一行* * @param rowId* @param yStartingPos*/public void beginNewRow(int rowId, float yStartingPos) {if (null == mKeyRows)mKeyRows = new ArrayList<KeyRow>();KeyRow keyRow = new KeyRow();keyRow.mRowId = rowId;keyRow.mTopF = yStartingPos;keyRow.mBottomF = yStartingPos;keyRow.mSoftKeys = new ArrayList<SoftKey>();mKeyRows.add(keyRow);}/*** 添加一个按键,按键是添加在最后一行中。* * @param softKey* @return*/public boolean addSoftKey(SoftKey softKey) {if (mKeyRows.size() == 0)return false;KeyRow keyRow = mKeyRows.get(mKeyRows.size() - 1);if (null == keyRow)return false;List<SoftKey> softKeys = keyRow.mSoftKeys;softKey.setSkbCoreSize(mSkbCoreWidth, mSkbCoreHeight);softKeys.add(softKey);// 根据加入的按键的top和bottom,调整行的top和bottomif (softKey.mTopF < keyRow.mTopF) {keyRow.mTopF = softKey.mTopF;}if (softKey.mBottomF > keyRow.mBottomF) {keyRow.mBottomF = softKey.mBottomF;}return true;}public int getSkbXmlId() {return mSkbXmlId;}// Set the size of the soft keyboard core. In other words, the background's// padding are not counted./*** 设置键盘核心的宽度和高度(不包括padding),并根据新的宽度和高度,调整键盘中各行的top和bottom,调整行中的按键的尺寸。* * @param skbCoreWidth* @param skbCoreHeight*/public void setSkbCoreSize(int skbCoreWidth, int skbCoreHeight) {if (null == mKeyRows|| (skbCoreWidth == mSkbCoreWidth && skbCoreHeight == mSkbCoreHeight)) {return;}for (int row = 0; row < mKeyRows.size(); row++) {KeyRow keyRow = mKeyRows.get(row);keyRow.mBottom = (int) (skbCoreHeight * keyRow.mBottomF);keyRow.mTop = (int) (skbCoreHeight * keyRow.mTopF);List<SoftKey> softKeys = keyRow.mSoftKeys;for (int i = 0; i < softKeys.size(); i++) {SoftKey softKey = softKeys.get(i);softKey.setSkbCoreSize(skbCoreWidth, skbCoreHeight);}}mSkbCoreWidth = skbCoreWidth;mSkbCoreHeight = skbCoreHeight;}public int getSkbCoreWidth() {return mSkbCoreWidth;}public int getSkbCoreHeight() {return mSkbCoreHeight;}public int getSkbTotalWidth() {Rect padding = getPadding();return mSkbCoreWidth + padding.left + padding.right;}public int getSkbTotalHeight() {Rect padding = getPadding();return mSkbCoreHeight + padding.top + padding.bottom;}// TODO 不明白这个函数的作用public int getKeyXMargin() {Environment env = Environment.getInstance();return (int) (mKeyXMargin * mSkbCoreWidth * env.getKeyXMarginFactor());}// TODO 不明白这个函数的作用public int getKeyYMargin() {Environment env = Environment.getInstance();return (int) (mKeyYMargin * mSkbCoreHeight * env.getKeyYMarginFactor());}public Drawable getSkbBackground() {if (null != mSkbBg)return mSkbBg;return mSkbTemplate.getSkbBackground();}public Drawable getBalloonBackground() {if (null != mBalloonBg)return mBalloonBg;return mSkbTemplate.getBalloonBackground();}public Drawable getPopupBackground() {if (null != mPopupBg)return mPopupBg;return mSkbTemplate.getPopupBackground();}public int getRowNum() {if (null != mKeyRows) {return mKeyRows.size();}return 0;}public KeyRow getKeyRowForDisplay(int row) {if (null != mKeyRows && mKeyRows.size() > row) {KeyRow keyRow = mKeyRows.get(row);if (KeyRow.ALWAYS_SHOW_ROW_ID == keyRow.mRowId|| keyRow.mRowId == mEnabledRowId) {return keyRow;}}return null;}public SoftKey getKey(int row, int location) {if (null != mKeyRows && mKeyRows.size() > row) {List<SoftKey> softKeys = mKeyRows.get(row).mSoftKeys;if (softKeys.size() > location) {return softKeys.get(location);}}return null;}/*** 根据坐标查找按键,如果坐标在某个按键区域内,就返回这个按键,如果坐标不在所有的按键区域内,返回离它最近的按键。* * @优化 可以在判断坐标在某个按键区域内的时候,并且加上判断离它最近的按键,这样就只需要一次遍历就行了。* @param x* @param y* @return*/public SoftKey mapToKey(int x, int y) {if (null == mKeyRows) {return null;}// If the position is inside the rectangle of a certain key, return that// key.int rowNum = mKeyRows.size();for (int row = 0; row < rowNum; row++) {KeyRow keyRow = mKeyRows.get(row);if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId&& keyRow.mRowId != mEnabledRowId)continue;if (keyRow.mTop > y && keyRow.mBottom <= y)continue;List<SoftKey> softKeys = keyRow.mSoftKeys;int keyNum = softKeys.size();for (int i = 0; i < keyNum; i++) {SoftKey sKey = softKeys.get(i);if (sKey.mLeft <= x && sKey.mTop <= y && sKey.mRight > x&& sKey.mBottom > y) {return sKey;}}}// If the position is outside the rectangles of all keys, find the// nearest one.SoftKey nearestKey = null;float nearestDis = Float.MAX_VALUE;for (int row = 0; row < rowNum; row++) {KeyRow keyRow = mKeyRows.get(row);if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId&& keyRow.mRowId != mEnabledRowId)continue;if (keyRow.mTop > y && keyRow.mBottom <= y)continue;List<SoftKey> softKeys = keyRow.mSoftKeys;int keyNum = softKeys.size();for (int i = 0; i < keyNum; i++) {SoftKey sKey = softKeys.get(i);int disx = (sKey.mLeft + sKey.mRight) / 2 - x; // 按键x坐标的中心点到x之间的距离int disy = (sKey.mTop + sKey.mBottom) / 2 - y; // 按键y坐标的中心点到y之间的距离float dis = disx * disx + disy * disy;if (dis < nearestDis) {nearestDis = dis;nearestKey = sKey;}}}return nearestKey;}/*** 改变Qwerty键盘中每个按键的状态* * @param toggle_state_id*            按键的状态属性值* @param upperCase*            大小写*/public void switchQwertyMode(int toggle_state_id, boolean upperCase) {if (!mIsQwerty)return;int rowNum = mKeyRows.size();for (int row = 0; row < rowNum; row++) {KeyRow keyRow = mKeyRows.get(row);List<SoftKey> softKeys = keyRow.mSoftKeys;int keyNum = softKeys.size();for (int i = 0; i < keyNum; i++) {SoftKey sKey = softKeys.get(i);if (sKey instanceof SoftKeyToggle) {((SoftKeyToggle) sKey).enableToggleState(toggle_state_id,true);}if (sKey.mKeyCode >= KeyEvent.KEYCODE_A&& sKey.mKeyCode <= KeyEvent.KEYCODE_Z) {sKey.changeCase(upperCase);}}}}/*** 改变键盘中每个按键的状态* * @param toggleStateId* @param resetIfNotFound*/public void enableToggleState(int toggleStateId, boolean resetIfNotFound) {int rowNum = mKeyRows.size();for (int row = 0; row < rowNum; row++) {KeyRow keyRow = mKeyRows.get(row);List<SoftKey> softKeys = keyRow.mSoftKeys;int keyNum = softKeys.size();for (int i = 0; i < keyNum; i++) {SoftKey sKey = softKeys.get(i);if (sKey instanceof SoftKeyToggle) {((SoftKeyToggle) sKey).enableToggleState(toggleStateId,resetIfNotFound);}}}}/*** 消除键盘中按键的某一个状态* * @param toggleStateId* @param resetIfNotFound*/public void disableToggleState(int toggleStateId, boolean resetIfNotFound) {int rowNum = mKeyRows.size();for (int row = 0; row < rowNum; row++) {KeyRow keyRow = mKeyRows.get(row);List<SoftKey> softKeys = keyRow.mSoftKeys;int keyNum = softKeys.size();for (int i = 0; i < keyNum; i++) {SoftKey sKey = softKeys.get(i);if (sKey instanceof SoftKeyToggle) {((SoftKeyToggle) sKey).disableToggleState(toggleStateId,resetIfNotFound);}}}}/*** 改变键盘的状态,并且根据键盘状态中的mKeyStates[]来设置每个按键。* * @param toggleStates*            整个键盘的状态*/public void enableToggleStates(ToggleStates toggleStates) {if (null == toggleStates)return;enableRow(toggleStates.mRowIdToEnable);boolean isQwerty = toggleStates.mQwerty;boolean isQwertyUpperCase = toggleStates.mQwertyUpperCase;boolean needUpdateQwerty = (isQwerty && mIsQwerty && (mIsQwertyUpperCase != isQwertyUpperCase));int states[] = toggleStates.mKeyStates;int statesNum = toggleStates.mKeyStatesNum;int rowNum = mKeyRows.size();for (int row = 0; row < rowNum; row++) {KeyRow keyRow = mKeyRows.get(row);if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId&& keyRow.mRowId != mEnabledRowId) {continue;}List<SoftKey> softKeys = keyRow.mSoftKeys;int keyNum = softKeys.size();for (int keyPos = 0; keyPos < keyNum; keyPos++) {SoftKey sKey = softKeys.get(keyPos);if (sKey instanceof SoftKeyToggle) {for (int statePos = 0; statePos < statesNum; statePos++) {((SoftKeyToggle) sKey).enableToggleState(states[statePos], statePos == 0);}if (0 == statesNum) {((SoftKeyToggle) sKey).disableAllToggleStates();}}if (needUpdateQwerty) {if (sKey.mKeyCode >= KeyEvent.KEYCODE_A&& sKey.mKeyCode <= KeyEvent.KEYCODE_Z) {sKey.changeCase(isQwertyUpperCase);}}}}mIsQwertyUpperCase = isQwertyUpperCase;}private Rect getPadding() {mTmpRect.set(0, 0, 0, 0);Drawable skbBg = getSkbBackground();if (null == skbBg)return mTmpRect;skbBg.getPadding(mTmpRect);return mTmpRect;}/*** Enable a row with the give toggle Id. Rows with other toggle ids (except* the id {@link KeyRow#ALWAYS_SHOW_ROW_ID}) will be disabled.* * @param rowId*            The row id to enable.* @return True if the soft keyboard requires redrawing.*//*** 先查找与rowId相等的行的id,如果有,就设置mEnabledRowId = rowId。* * @param rowId* @return*/private boolean enableRow(int rowId) {if (KeyRow.ALWAYS_SHOW_ROW_ID == rowId)return false;boolean enabled = false;int rowNum = mKeyRows.size();for (int row = rowNum - 1; row >= 0; row--) {if (mKeyRows.get(row).mRowId == rowId) {enabled = true;break;}}if (enabled) {mEnabledRowId = rowId;}return enabled;}@Overridepublic String toString() {String str = "------------------SkbInfo----------------------\n";String endStr = "-----------------------------------------------\n";str += "Width: " + String.valueOf(mSkbCoreWidth) + "\n";str += "Height: " + String.valueOf(mSkbCoreHeight) + "\n";str += "KeyRowNum: " + mKeyRows == null ? "0" : String.valueOf(mKeyRows.size()) + "\n";if (null == mKeyRows)return str + endStr;int rowNum = mKeyRows.size();for (int row = 0; row < rowNum; row++) {KeyRow keyRow = mKeyRows.get(row);List<SoftKey> softKeys = keyRow.mSoftKeys;int keyNum = softKeys.size();for (int i = 0; i < softKeys.size(); i++) {str += "-key " + String.valueOf(i) + ":"+ softKeys.get(i).toString();}}return str + endStr;}public String toShortString() {return super.toString();}/*** 键盘的行* * @ClassName KeyRow* @author keanbin*/class KeyRow {static final int ALWAYS_SHOW_ROW_ID = -1;static final int DEFAULT_ROW_ID = 0;List<SoftKey> mSoftKeys;/*** If the row id is {@link #ALWAYS_SHOW_ROW_ID}, this row will always be* enabled.*/int mRowId;float mTopF;float mBottomF;int mTop;int mBottom;}
}

20、SoftKeyboardView.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import java.util.List;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.view.View;import com.keanbin.pinyinime.SoftKeyboard.KeyRow;/*** Class used to show a soft keyboard.* * A soft keyboard view should not handle touch event itself, because we do bias* correction, need a global strategy to map an event into a proper view to* achieve better user experience. 软件盘视图*/
public class SoftKeyboardView extends View {/*** The definition of the soft keyboard for the current this soft keyboard* view. 软件盘布局*/private SoftKeyboard mSoftKeyboard;/*** The popup balloon hint for key press/release.*/private BalloonHint mBalloonPopup;/*** The on-key balloon hint for key press/release. If it is null, on-key* highlight will be drawn on th soft keyboard view directly.*/private BalloonHint mBalloonOnKey;/** Used to play key sounds. 声音管理 */private SoundManager mSoundManager;/** The last key pressed. 最后按下的按键 */private SoftKey mSoftKeyDown;/** Used to indicate whether the user is holding on a key. 是否正在按住按键? */private boolean mKeyPressed = false;/*** The location offset of the view to the keyboard container.* 视图到键盘集装箱之间的位置偏移*/private int mOffsetToSkbContainer[] = new int[2];/*** The location of the desired hint view to the keyboard container.* 所需提示视图到键盘集装箱之间的位置偏移*/private int mHintLocationToSkbContainer[] = new int[2];/*** Text size for normal key. 正常按键的文本大小*/private int mNormalKeyTextSize;/*** Text size for function key. 功能按键的文本大小*/private int mFunctionKeyTextSize;/*** Long press timer used to response long-press. 长按的定时器*/private SkbContainer.LongPressTimer mLongPressTimer;/*** Repeated events for long press 长按是否为重复?*/private boolean mRepeatForLongPress = false;/*** If this parameter is true, the balloon will never be dismissed even if* user moves a lot from the pressed point. 这个参数如果为true,当用户移开点击点后,气泡不会被销毁。*/private boolean mMovingNeverHidePopupBalloon = false;/** Vibration for key press. 震动操作对象 */private Vibrator mVibrator;/** Vibration pattern for key press. 震动的参数 */protected long[] mVibratePattern = new long[] { 1, 20 };/*** The dirty rectangle used to mark the area to re-draw during key press and* release. Currently, whenever we can invalidate(Rect), view will call* onDraw() and we MUST draw the whole view. This dirty information is for* future use. 该区域用于标记按键按下和释放后需要重绘的区域,目前没有作用,是为了以后保留的。*/private Rect mDirtyRect = new Rect();private Paint mPaint;private FontMetricsInt mFmi;private boolean mDimSkb;Context mContext;public SoftKeyboardView(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;mSoundManager = SoundManager.getInstance(mContext);mPaint = new Paint();mPaint.setAntiAlias(true);mFmi = mPaint.getFontMetricsInt();}public boolean setSoftKeyboard(SoftKeyboard softSkb) {if (null == softSkb) {return false;}mSoftKeyboard = softSkb;Drawable bg = softSkb.getSkbBackground();if (null != bg)setBackgroundDrawable(bg);return true;}public SoftKeyboard getSoftKeyboard() {return mSoftKeyboard;}/*** 设置mSoftKeyboard的尺寸* * @param skbWidth* @param skbHeight*/public void resizeKeyboard(int skbWidth, int skbHeight) {mSoftKeyboard.setSkbCoreSize(skbWidth, skbHeight);}/*** 设置mBalloonOnKey气泡、mBalloonPopup气泡。* * @param balloonOnKey* @param balloonPopup* @param movingNeverHidePopup*/public void setBalloonHint(BalloonHint balloonOnKey,BalloonHint balloonPopup, boolean movingNeverHidePopup) {mBalloonOnKey = balloonOnKey;mBalloonPopup = balloonPopup;mMovingNeverHidePopupBalloon = movingNeverHidePopup;}/*** 设置视图到键盘集装箱之间的位置偏移* * @param offsetToSkbContainer*/public void setOffsetToSkbContainer(int offsetToSkbContainer[]) {mOffsetToSkbContainer[0] = offsetToSkbContainer[0];mOffsetToSkbContainer[1] = offsetToSkbContainer[1];}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int measuredWidth = 0;int measuredHeight = 0;if (null != mSoftKeyboard) {measuredWidth = mSoftKeyboard.getSkbCoreWidth();measuredHeight = mSoftKeyboard.getSkbCoreHeight();measuredWidth += getPaddingLeft() + getPaddingRight();measuredHeight += getPaddingTop() + getPaddingBottom();}// TODO 如果 measuredWidth 大于父视图可给予的最大宽度,会出现什么样的情况?// 设置view的尺寸setMeasuredDimension(measuredWidth, measuredHeight);}/*** 显示气泡。逻辑简介:如果该气泡需要强行销毁,那么就马上销毁。如果该气泡正在显示,就更新它,否则显示该气泡。* * @param balloon* @param balloonLocationToSkb* @param movePress*/private void showBalloon(BalloonHint balloon, int balloonLocationToSkb[],boolean movePress) {long delay = BalloonHint.TIME_DELAY_SHOW;if (movePress)delay = 0;if (balloon.needForceDismiss()) {balloon.delayedDismiss(0);}if (!balloon.isShowing()) {balloon.delayedShow(delay, balloonLocationToSkb);} else {balloon.delayedUpdate(delay, balloonLocationToSkb,balloon.getWidth(), balloon.getHeight());}long b = System.currentTimeMillis();}/*** 重置mKeyPressed为false,并关闭mBalloonOnKey气泡。* * @param balloonDelay*            关闭气泡的延时时间*/public void resetKeyPress(long balloonDelay) {if (!mKeyPressed)return;mKeyPressed = false;if (null != mBalloonOnKey) {mBalloonOnKey.delayedDismiss(balloonDelay);} else {if (null != mSoftKeyDown) {if (mDirtyRect.isEmpty()) {mDirtyRect.set(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,mSoftKeyDown.mRight, mSoftKeyDown.mBottom);}invalidate(mDirtyRect);} else {invalidate();}}mBalloonPopup.delayedDismiss(balloonDelay);}// If movePress is true, means that this function is called because user// moves his finger to this button. If movePress is false, means that this// function is called when user just presses this key./*** 按键按下处理函数* * @param x* @param y* @param longPressTimer* @param movePress*            如果为true,意味着是因为手指触摸移动到该按键。如果为false,意味着是因为手指按下给按键。* @return*/public SoftKey onKeyPress(int x, int y,SkbContainer.LongPressTimer longPressTimer, boolean movePress) {mKeyPressed = false;boolean moveWithinPreviousKey = false;if (movePress) {SoftKey newKey = mSoftKeyboard.mapToKey(x, y);if (newKey == mSoftKeyDown)moveWithinPreviousKey = true;mSoftKeyDown = newKey;} else {mSoftKeyDown = mSoftKeyboard.mapToKey(x, y);}if (moveWithinPreviousKey || null == mSoftKeyDown)return mSoftKeyDown;// TODO// 这句代码放在这里,那如果moveWithinPreviousKey是true的情况下,mKeyPressed不是还是false吗?mKeyPressed = true;// 播放按键声音和震动if (!movePress) {tryPlayKeyDown();tryVibrate();}mLongPressTimer = longPressTimer;// 判断是否是按下操作,如果是,就判读条件,启动长按定时器if (!movePress) {if (mSoftKeyDown.getPopupResId() > 0 || mSoftKeyDown.repeatable()) {mLongPressTimer.startTimer();}} else {mLongPressTimer.removeTimer();}int desired_width;int desired_height;float textSize;Environment env = Environment.getInstance();if (null != mBalloonOnKey) {// 设置气泡背景Drawable keyHlBg = mSoftKeyDown.getKeyHlBg();mBalloonOnKey.setBalloonBackground(keyHlBg);// Prepare the on-key balloon// 设置气泡内容和尺寸int keyXMargin = mSoftKeyboard.getKeyXMargin();int keyYMargin = mSoftKeyboard.getKeyYMargin();desired_width = mSoftKeyDown.width() - 2 * keyXMargin;desired_height = mSoftKeyDown.height() - 2 * keyYMargin;textSize = env.getKeyTextSize(SoftKeyType.KEYTYPE_ID_NORMAL_KEY != mSoftKeyDown.mKeyType.mKeyTypeId);Drawable icon = mSoftKeyDown.getKeyIcon();if (null != icon) {mBalloonOnKey.setBalloonConfig(icon, desired_width,desired_height);} else {mBalloonOnKey.setBalloonConfig(mSoftKeyDown.getKeyLabel(),textSize, true, mSoftKeyDown.getColorHl(),desired_width, desired_height);}// 设置气泡显示的位置mHintLocationToSkbContainer[0] = getPaddingLeft()+ mSoftKeyDown.mLeft- (mBalloonOnKey.getWidth() - mSoftKeyDown.width()) / 2;mHintLocationToSkbContainer[0] += mOffsetToSkbContainer[0];mHintLocationToSkbContainer[1] = getPaddingTop()+ (mSoftKeyDown.mBottom - keyYMargin)- mBalloonOnKey.getHeight();mHintLocationToSkbContainer[1] += mOffsetToSkbContainer[1];// 显示气泡showBalloon(mBalloonOnKey, mHintLocationToSkbContainer, movePress);} else {// 设置界面局部刷新,只刷新按键区域mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,mSoftKeyDown.mRight, mSoftKeyDown.mBottom);invalidate(mDirtyRect);}// TODO 下面这个气泡和上面的气泡有什么区别?// Prepare the popup balloonif (mSoftKeyDown.needBalloon()) {Drawable balloonBg = mSoftKeyboard.getBalloonBackground();mBalloonPopup.setBalloonBackground(balloonBg);desired_width = mSoftKeyDown.width() + env.getKeyBalloonWidthPlus();desired_height = mSoftKeyDown.height()+ env.getKeyBalloonHeightPlus();textSize = env.getBalloonTextSize(SoftKeyType.KEYTYPE_ID_NORMAL_KEY != mSoftKeyDown.mKeyType.mKeyTypeId);Drawable iconPopup = mSoftKeyDown.getKeyIconPopup();if (null != iconPopup) {mBalloonPopup.setBalloonConfig(iconPopup, desired_width,desired_height);} else {mBalloonPopup.setBalloonConfig(mSoftKeyDown.getKeyLabel(),textSize, mSoftKeyDown.needBalloon(),mSoftKeyDown.getColorBalloon(), desired_width,desired_height);}// The position to show.mHintLocationToSkbContainer[0] = getPaddingLeft()+ mSoftKeyDown.mLeft+ -(mBalloonPopup.getWidth() - mSoftKeyDown.width()) / 2;mHintLocationToSkbContainer[0] += mOffsetToSkbContainer[0];mHintLocationToSkbContainer[1] = getPaddingTop()+ mSoftKeyDown.mTop - mBalloonPopup.getHeight();mHintLocationToSkbContainer[1] += mOffsetToSkbContainer[1];showBalloon(mBalloonPopup, mHintLocationToSkbContainer, movePress);} else {mBalloonPopup.delayedDismiss(0);}// TODO 怎么这里还有一个长按定时器启动? 上面不是已经设置了吗?if (mRepeatForLongPress)longPressTimer.startTimer();return mSoftKeyDown;}/*** 按键释放的处理函数* * @param x* @param y* @return*/public SoftKey onKeyRelease(int x, int y) {mKeyPressed = false;if (null == mSoftKeyDown)return null;mLongPressTimer.removeTimer();if (null != mBalloonOnKey) {mBalloonOnKey.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);} else {mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,mSoftKeyDown.mRight, mSoftKeyDown.mBottom);invalidate(mDirtyRect);}if (mSoftKeyDown.needBalloon()) {mBalloonPopup.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);}// TODO 为什么在按下的处理函数中判断坐标点属于哪个按键的时候不需要减去padding,而在这里需要呢?if (mSoftKeyDown.moveWithinKey(x - getPaddingLeft(), y- getPaddingTop())) {return mSoftKeyDown;}return null;}/*** 按键的移动处理函数* * @param x* @param y* @return*/public SoftKey onKeyMove(int x, int y) {if (null == mSoftKeyDown)return null;if (mSoftKeyDown.moveWithinKey(x - getPaddingLeft(), y- getPaddingTop())) {return mSoftKeyDown;}// The current key needs to be updated.mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,mSoftKeyDown.mRight, mSoftKeyDown.mBottom);if (mRepeatForLongPress) {// 如果mMovingNeverHidePopupBalloon为true,那么就不销毁气泡,否则,先销毁在进入按键处理函数。if (mMovingNeverHidePopupBalloon) {return onKeyPress(x, y, mLongPressTimer, true);}if (null != mBalloonOnKey) {mBalloonOnKey.delayedDismiss(0);} else {invalidate(mDirtyRect);}if (mSoftKeyDown.needBalloon()) {mBalloonPopup.delayedDismiss(0);}if (null != mLongPressTimer) {mLongPressTimer.removeTimer();}return onKeyPress(x, y, mLongPressTimer, true);} else {// When user moves between keys, repeated response is disabled.return onKeyPress(x, y, mLongPressTimer, true);}}/*** 震动*/private void tryVibrate() {if (!Settings.getVibrate()) {return;}if (mVibrator == null) {mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);// = new Vibrator();}mVibrator.vibrate(mVibratePattern, -1);}/*** 播放按键按下的声音*/private void tryPlayKeyDown() {if (Settings.getKeySound()) {mSoundManager.playKeyDown();}}/*** 是否销毁SoftKeyboard视图* * @param dimSkb*            该标志会在onDraw()中使用。*/public void dimSoftKeyboard(boolean dimSkb) {mDimSkb = dimSkb;invalidate();}@Overrideprotected void onDraw(Canvas canvas) {if (null == mSoftKeyboard)return;// 画布水平移动padding,使得画布的原点(0,0)相对于父视图移动了(getPaddingLeft(),// getPaddingTop())。canvas.translate(getPaddingLeft(), getPaddingTop());Environment env = Environment.getInstance();mNormalKeyTextSize = env.getKeyTextSize(false);mFunctionKeyTextSize = env.getKeyTextSize(true);// Draw the last soft keyboardint rowNum = mSoftKeyboard.getRowNum();int keyXMargin = mSoftKeyboard.getKeyXMargin();int keyYMargin = mSoftKeyboard.getKeyYMargin();for (int row = 0; row < rowNum; row++) {KeyRow keyRow = mSoftKeyboard.getKeyRowForDisplay(row);if (null == keyRow)continue;List<SoftKey> softKeys = keyRow.mSoftKeys;int keyNum = softKeys.size();for (int i = 0; i < keyNum; i++) {SoftKey softKey = softKeys.get(i);if (SoftKeyType.KEYTYPE_ID_NORMAL_KEY == softKey.mKeyType.mKeyTypeId) {mPaint.setTextSize(mNormalKeyTextSize);} else {mPaint.setTextSize(mFunctionKeyTextSize);}drawSoftKey(canvas, softKey, keyXMargin, keyYMargin);}}// 清空画布if (mDimSkb) {mPaint.setColor(0xa0000000);canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);}mDirtyRect.setEmpty();}/*** 在画布上画一个按键* * @param canvas* @param softKey* @param keyXMargin* @param keyYMargin*/private void drawSoftKey(Canvas canvas, SoftKey softKey, int keyXMargin,int keyYMargin) {Drawable bg;int textColor;if (mKeyPressed && softKey == mSoftKeyDown) {bg = softKey.getKeyHlBg();textColor = softKey.getColorHl();} else {bg = softKey.getKeyBg();textColor = softKey.getColor();}if (null != bg) {bg.setBounds(softKey.mLeft + keyXMargin, softKey.mTop + keyYMargin,softKey.mRight - keyXMargin, softKey.mBottom - keyYMargin);bg.draw(canvas);}String keyLabel = softKey.getKeyLabel();Drawable keyIcon = softKey.getKeyIcon();if (null != keyIcon) {Drawable icon = keyIcon;int marginLeft = (softKey.width() - icon.getIntrinsicWidth()) / 2;int marginRight = softKey.width() - icon.getIntrinsicWidth()- marginLeft;int marginTop = (softKey.height() - icon.getIntrinsicHeight()) / 2;int marginBottom = softKey.height() - icon.getIntrinsicHeight()- marginTop;icon.setBounds(softKey.mLeft + marginLeft,softKey.mTop + marginTop, softKey.mRight - marginRight,softKey.mBottom - marginBottom);icon.draw(canvas);} else if (null != keyLabel) {mPaint.setColor(textColor);float x = softKey.mLeft+ (softKey.width() - mPaint.measureText(keyLabel)) / 2.0f;int fontHeight = mFmi.bottom - mFmi.top;float marginY = (softKey.height() - fontHeight) / 2.0f;float y = softKey.mTop + marginY - mFmi.top + mFmi.bottom / 1.5f;canvas.drawText(keyLabel, x, y + 1, mPaint);}}
}

21、SoftKeyToggle.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import android.graphics.drawable.Drawable;/*** Class for soft keys which defined in the keyboard xml file. A soft key can be* a basic key or a toggling key.* * @see com.android.inputmethod.pinyin.SoftKey*/
/*** 可以变换状态的按键* * @ClassName SoftKeyToggle* @author keanbin*/
public class SoftKeyToggle extends SoftKey {/*** The current state number is stored in the lowest 8 bits of mKeyMask, this* mask is used to get the state number. If the current state is 0, the* normal state is enabled; if the current state is more than 0, a toggle* state in the toggle state chain will be enabled.*/private static final int KEYMASK_TOGGLE_STATE = 0x000000ff;private ToggleState mToggleState;public int getToggleStateId() {return (mKeyMask & KEYMASK_TOGGLE_STATE);}// The state id should be valid, and less than 255.// If resetIfNotFound is true and there is no such toggle state with the// given id, the key state will be reset.// If the key state is newly changed (enabled to the given state, or// reseted) and needs re-draw, return true./*** 查找该按键是否有stateId的状态,如果有就切换到这个状态。* * @param stateId* @param resetIfNotFound*            如果没有找到与stateId的状态,是否重置mKeyMask的KEYMASK_TOGGLE_STATE位为0。* @return*/public boolean enableToggleState(int stateId, boolean resetIfNotFound) {int oldStateId = (mKeyMask & KEYMASK_TOGGLE_STATE);if (oldStateId == stateId)return false;mKeyMask &= (~KEYMASK_TOGGLE_STATE);if (stateId > 0) {/** 先设置判断的stateId为当前mKeyMask,然后在getToggleState()中根据mKeyMask进行查找,如果找到了,* 就返回true,如果没有找到* ,就重置mKeyMask中的KEYMASK_TOGGLE_STATE位为0,再判断resetIfNotFound是否为true,* 如果不是就还原以前的oldStateId。*/mKeyMask |= (KEYMASK_TOGGLE_STATE & stateId);if (getToggleState() == null) {mKeyMask &= (~KEYMASK_TOGGLE_STATE);if (!resetIfNotFound && oldStateId > 0) {mKeyMask |= (KEYMASK_TOGGLE_STATE & oldStateId);}return resetIfNotFound;} else {return true;}} else {return true;}}// The state id should be valid, and less than 255.// If resetIfNotFound is true and there is no such toggle state with the// given id, the key state will be reset.// If the key state is newly changed and needs re-draw, return true./*** 判断当前的状态是否是stateId,如果是,就重置mKeyMask的KEYMASK_TOGGLE_STATE位为0。* * @param stateId* @param resetIfNotFound*            如果没有找到与stateId的状态,是否重置mKeyMask的KEYMASK_TOGGLE_STATE位为0。* @return*/public boolean disableToggleState(int stateId, boolean resetIfNotFound) {int oldStateId = (mKeyMask & KEYMASK_TOGGLE_STATE);if (oldStateId == stateId) {mKeyMask &= (~KEYMASK_TOGGLE_STATE);return stateId != 0;}if (resetIfNotFound) {mKeyMask &= (~KEYMASK_TOGGLE_STATE);return oldStateId != 0;}return false;}// Clear any toggle state. If the key needs re-draw, return true./*** 重置mKeyMask的KEYMASK_TOGGLE_STATE位为0。* * @return*/public boolean disableAllToggleStates() {int oldStateId = (mKeyMask & KEYMASK_TOGGLE_STATE);mKeyMask &= (~KEYMASK_TOGGLE_STATE);return oldStateId != 0;}@Overridepublic Drawable getKeyIcon() {ToggleState state = getToggleState();if (null != state)return state.mKeyIcon;return super.getKeyIcon();}@Overridepublic Drawable getKeyIconPopup() {ToggleState state = getToggleState();if (null != state) {if (null != state.mKeyIconPopup) {return state.mKeyIconPopup;} else {return state.mKeyIcon;}}return super.getKeyIconPopup();}@Overridepublic int getKeyCode() {ToggleState state = getToggleState();if (null != state)return state.mKeyCode;return mKeyCode;}@Overridepublic String getKeyLabel() {ToggleState state = getToggleState();if (null != state)return state.mKeyLabel;return mKeyLabel;}@Overridepublic Drawable getKeyBg() {ToggleState state = getToggleState();if (null != state && null != state.mKeyType) {return state.mKeyType.mKeyBg;}return mKeyType.mKeyBg;}@Overridepublic Drawable getKeyHlBg() {ToggleState state = getToggleState();if (null != state && null != state.mKeyType) {return state.mKeyType.mKeyHlBg;}return mKeyType.mKeyHlBg;}@Overridepublic int getColor() {ToggleState state = getToggleState();if (null != state && null != state.mKeyType) {return state.mKeyType.mColor;}return mKeyType.mColor;}@Overridepublic int getColorHl() {ToggleState state = getToggleState();if (null != state && null != state.mKeyType) {return state.mKeyType.mColorHl;}return mKeyType.mColorHl;}@Overridepublic int getColorBalloon() {ToggleState state = getToggleState();if (null != state && null != state.mKeyType) {return state.mKeyType.mColorBalloon;}return mKeyType.mColorBalloon;}@Overridepublic boolean isKeyCodeKey() {ToggleState state = getToggleState();if (null != state) {if (state.mKeyCode > 0)return true;return false;}return super.isKeyCodeKey();}@Overridepublic boolean isUserDefKey() {ToggleState state = getToggleState();if (null != state) {if (state.mKeyCode < 0)return true;return false;}return super.isUserDefKey();}@Overridepublic boolean isUniStrKey() {ToggleState state = getToggleState();if (null != state) {if (null != state.mKeyLabel && state.mKeyCode == 0) {return true;}return false;}return super.isUniStrKey();}@Overridepublic boolean needBalloon() {ToggleState state = getToggleState();if (null != state) {return (state.mIdAndFlags & KEYMASK_BALLOON) != 0;}return super.needBalloon();}@Overridepublic boolean repeatable() {ToggleState state = getToggleState();if (null != state) {return (state.mIdAndFlags & KEYMASK_REPEAT) != 0;}return super.repeatable();}@Overridepublic void changeCase(boolean lowerCase) {ToggleState state = getToggleState();if (null != state && null != state.mKeyLabel) {if (lowerCase)state.mKeyLabel = state.mKeyLabel.toLowerCase();elsestate.mKeyLabel = state.mKeyLabel.toUpperCase();}}public ToggleState createToggleState() {return new ToggleState();}public boolean setToggleStates(ToggleState rootState) {if (null == rootState)return false;mToggleState = rootState;return true;}/*** 判断当前的ToggleState的mIdAndFlags &* KEYMASK_TOGGLE_STATE是否与stateId时相等的,如果不是就移动到下一个ToggleState再找* ,直到与stateId时相等或者没有下一个ToggleState为止。* * @return*/private ToggleState getToggleState() {int stateId = (mKeyMask & KEYMASK_TOGGLE_STATE);if (0 == stateId)return null;ToggleState state = mToggleState;while ((null != state)&& (state.mIdAndFlags & KEYMASK_TOGGLE_STATE) != stateId) {state = state.mNextState;}return state;}/*** 按键变化的状态,每一个按键的变化状态都是一个单向列表,mNextState指向一下个变化的状态。* * @ClassName ToggleState* @author keanbin*/public class ToggleState {// The id should be bigger than 0;private int mIdAndFlags;public SoftKeyType mKeyType;public int mKeyCode;public Drawable mKeyIcon;public Drawable mKeyIconPopup;public String mKeyLabel;public ToggleState mNextState;public void setStateId(int stateId) {mIdAndFlags |= (stateId & KEYMASK_TOGGLE_STATE);}public void setStateFlags(boolean repeat, boolean balloon) {if (repeat) {mIdAndFlags |= KEYMASK_REPEAT;} else {mIdAndFlags &= (~KEYMASK_REPEAT);}if (balloon) {mIdAndFlags |= KEYMASK_BALLOON;} else {mIdAndFlags &= (~KEYMASK_BALLOON);}}}
}

22、SoundManager.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import android.content.Context;
import android.media.AudioManager;/*** Class used to manage related sound resources.*/
/*** 声音管理类,该类采用单例模式。* * @ClassName SoundManager* @author keanbin*/
public class SoundManager {private static SoundManager mInstance = null;private Context mContext;private AudioManager mAudioManager;// Align sound effect volume on music volumeprivate final float FX_VOLUME = -1.0f;private boolean mSilentMode;private SoundManager(Context context) {mContext = context;updateRingerMode();}public void updateRingerMode() {if (mAudioManager == null) {mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);}mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);}public static SoundManager getInstance(Context context) {if (null == mInstance) {if (null != context) {mInstance = new SoundManager(context);}}return mInstance;}/*** 如果mSilentMode为false,就播放按下按键的声音。*/public void playKeyDown() {if (mAudioManager == null) {updateRingerMode();}if (!mSilentMode) {int sound = AudioManager.FX_KEYPRESS_STANDARD;mAudioManager.playSoundEffect(sound, FX_VOLUME);}}
}

23、XmlKeyboardLoader.java:

/** Copyright (C) 2009 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.keanbin.pinyinime;import java.io.IOException;
import java.util.regex.Pattern;import org.xmlpull.v1.XmlPullParserException;import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;import com.keanbin.pinyinime.SoftKeyboard.KeyRow;/*** Class used to load a soft keyboard or a soft keyboard template from xml* files.*/
/*** 解析软键盘模版和软键盘xml文件。* * @ClassName XmlKeyboardLoader* @author keanbin*/
public class XmlKeyboardLoader {/*** The tag used to define an xml-based soft keyboard template. xml标签*/private static final String XMLTAG_SKB_TEMPLATE = "skb_template";/*** The tag used to indicate the soft key type which is defined inside the* {@link #XMLTAG_SKB_TEMPLATE} element in the xml file. file. xml标签*/private static final String XMLTAG_KEYTYPE = "key_type";/*** The tag used to define a default key icon for enter/delete/space keys. It* is defined inside the {@link #XMLTAG_SKB_TEMPLATE} element in the xml* xml标签 file.*/private static final String XMLTAG_KEYICON = "key_icon";/*** Attribute tag of the left and right margin for a key. A key's width* should be larger than double of this value. Defined inside* {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}. xml标签中的属性*/private static final String XMLATTR_KEY_XMARGIN = "key_xmargin";/*** Attribute tag of the top and bottom margin for a key. A key's height* should be larger than double of this value. Defined inside* {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}. xml标签中的属性*/private static final String XMLATTR_KEY_YMARGIN = "key_ymargin";/*** Attribute tag of the keyboard background image. Defined inside* {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}. xml标签中的属性*/private static final String XMLATTR_SKB_BG = "skb_bg";/*** Attribute tag of the balloon background image for key press. Defined* inside {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}.* xml标签中的属性*/private static final String XMLATTR_BALLOON_BG = "balloon_bg";/*** Attribute tag of the popup balloon background image for key press or* popup mini keyboard. Defined inside {@link #XMLTAG_SKB_TEMPLATE} and* {@link #XMLTAG_KEYBOARD}. xml标签中的属性*/private static final String XMLATTR_POPUP_BG = "popup_bg";/*** Attribute tag of the color to draw key label. Defined inside* {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYTYPE}. xml标签中的属性*/private static final String XMLATTR_COLOR = "color";/*** Attribute tag of the color to draw key's highlighted label. Defined* inside {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYTYPE}.* xml标签中的属性*/private static final String XMLATTR_COLOR_HIGHLIGHT = "color_highlight";/*** Attribute tag of the color to draw key's label in the popup balloon.* Defined inside {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYTYPE}.* xml标签中的属性*/private static final String XMLATTR_COLOR_BALLOON = "color_balloon";/*** Attribute tag of the id of {@link #XMLTAG_KEYTYPE} and* {@link #XMLTAG_KEY}. Key types and keys defined in a soft keyboard* template should have id, because a soft keyboard needs the id to refer to* these default definitions. If a key defined in {@link #XMLTAG_KEYBOARD}* does not id, that means the key is newly defined; if it has id (and only* has id), the id is used to find the default definition from the soft* keyboard template. xml标签中的属性*/private static final String XMLATTR_ID = "id";/*** Attribute tag of the key background for a specified key type. Defined* inside {@link #XMLTAG_KEYTYPE}. xml标签中的属性*/private static final String XMLATTR_KEYTYPE_BG = "bg";/*** Attribute tag of the key high-light background for a specified key type.* Defined inside {@link #XMLTAG_KEYTYPE}. xml标签中的属性*/private static final String XMLATTR_KEYTYPE_HLBG = "hlbg";/*** Attribute tag of the starting x-position of an element. It can be defined* in {@link #XMLTAG_ROW} and {@link #XMLTAG_KEY} in {XMLTAG_SKB_TEMPLATE}.* If not defined, 0 will be used. For a key defined in* {@link #XMLTAG_KEYBOARD}, it always use its previous keys information to* calculate its own position. xml标签中的属性*/private static final String XMLATTR_START_POS_X = "start_pos_x";/*** Attribute tag of the starting y-position of an element. It can be defined* in {@link #XMLTAG_ROW} and {@link #XMLTAG_KEY} in {XMLTAG_SKB_TEMPLATE}.* If not defined, 0 will be used. For a key defined in* {@link #XMLTAG_KEYBOARD}, it always use its previous keys information to* calculate its own position. xml标签中的属性*/private static final String XMLATTR_START_POS_Y = "start_pos_y";/*** Attribute tag of a row's id. Defined {@link #XMLTAG_ROW}. If not defined,* -1 will be used. Rows with id -1 will be enabled always, rows with same* row id will be enabled when the id is the same to the activated id of the* soft keyboard. xml标签中的属性*/private static final String XMLATTR_ROW_ID = "row_id";/** The tag used to indicate the keyboard element in the xml file. xml标签 */private static final String XMLTAG_KEYBOARD = "keyboard";/** The tag used to indicate the row element in the xml file. xml标签 */private static final String XMLTAG_ROW = "row";/** The tag used to indicate key-array element in the xml file. xml标签 */private static final String XMLTAG_KEYS = "keys";/*** The tag used to indicate a key element in the xml file. If the element is* defined in a soft keyboard template, it should have an id. If it is* defined in a soft keyboard, id is not required. xml标签*/private static final String XMLTAG_KEY = "key";/** The tag used to indicate a key's toggle element in the xml file. xml标签 */private static final String XMLTAG_TOGGLE_STATE = "toggle_state";/*** Attribute tag of the toggle state id for toggle key. Defined inside* {@link #XMLTAG_TOGGLE_STATE} xml标签中的属性*/private static final String XMLATTR_TOGGLE_STATE_ID = "state_id";/** Attribute tag of key template for the soft keyboard. xml标签中的属性 */private static final String XMLATTR_SKB_TEMPLATE = "skb_template";/*** Attribute tag used to indicate whether this soft keyboard needs to be* cached in memory for future use. {@link #DEFAULT_SKB_CACHE_FLAG}* specifies the default value. xml标签中的属性*/private static final String XMLATTR_SKB_CACHE_FLAG = "skb_cache_flag";/*** Attribute tag used to indicate whether this soft keyboard is sticky. A* sticky soft keyboard will keep the current layout unless user makes a* switch explicitly. A none sticky soft keyboard will automatically goes* back to the previous keyboard after click a none-function key.* {@link #DEFAULT_SKB_STICKY_FLAG} specifies the default value. xml标签中的属性*/private static final String XMLATTR_SKB_STICKY_FLAG = "skb_sticky_flag";/*** Attribute tag to indicate whether it is a QWERTY soft keyboard. xml标签中的属性*/private static final String XMLATTR_QWERTY = "qwerty";/*** When the soft keyboard is a QWERTY one, this attribute tag to get the* information that whether it is defined in upper case. xml标签中的属性*/private static final String XMLATTR_QWERTY_UPPERCASE = "qwerty_uppercase";/** Attribute tag of key type. xml标签中的属性 */private static final String XMLATTR_KEY_TYPE = "key_type";/** Attribute tag of key width. xml标签中的属性 */private static final String XMLATTR_KEY_WIDTH = "width";/** Attribute tag of key height. xml标签中的属性 */private static final String XMLATTR_KEY_HEIGHT = "height";/** Attribute tag of the key's repeating ability. xml标签中的属性 */private static final String XMLATTR_KEY_REPEAT = "repeat";/** Attribute tag of the key's behavior for balloon. xml标签中的属性 */private static final String XMLATTR_KEY_BALLOON = "balloon";/** Attribute tag of the key splitter in a key array. xml标签中的属性 */private static final String XMLATTR_KEY_SPLITTER = "splitter";/** Attribute tag of the key labels in a key array. xml标签中的属性 */private static final String XMLATTR_KEY_LABELS = "labels";/** Attribute tag of the key codes in a key array. xml标签中的属性 */private static final String XMLATTR_KEY_CODES = "codes";/** Attribute tag of the key label in a key. xml标签中的属性 */private static final String XMLATTR_KEY_LABEL = "label";/** Attribute tag of the key code in a key. xml标签中的属性 */private static final String XMLATTR_KEY_CODE = "code";/** Attribute tag of the key icon in a key. xml标签中的属性 */private static final String XMLATTR_KEY_ICON = "icon";/** Attribute tag of the key's popup icon in a key. xml标签中的属性 */private static final String XMLATTR_KEY_ICON_POPUP = "icon_popup";/** The id for a mini popup soft keyboard. xml标签中的属性 */private static final String XMLATTR_KEY_POPUP_SKBID = "popup_skb";private static boolean DEFAULT_SKB_CACHE_FLAG = true;private static boolean DEFAULT_SKB_STICKY_FLAG = true;/*** The key type id for invalid key type. It is also used to generate next* valid key type id by adding 1. 无效key type id。最后的key type id。*/private static final int KEYTYPE_ID_LAST = -1;private Context mContext;private Resources mResources;/** The event type in parsing the xml file. */private int mXmlEventType;/*** The current soft keyboard template used by the current soft keyboard* under loading.**/private SkbTemplate mSkbTemplate;/** The x position for the next key. */float mKeyXPos;/** The y position for the next key. */float mKeyYPos;/** The width of the keyboard to load. */int mSkbWidth;/** The height of the keyboard to load. */int mSkbHeight;/** Key margin in x-way. */float mKeyXMargin = 0;/** Key margin in y-way. */float mKeyYMargin = 0;/*** Used to indicate whether next event has been fetched during processing* the the current event. 处理当前事件的时候,下一个事件是否已经到达。*/boolean mNextEventFetched = false;String mAttrTmp;/*** 标签的公共属性* * @ClassName KeyCommonAttributes* @author keanbin*/class KeyCommonAttributes {XmlResourceParser mXrp;int keyType;float keyWidth;float keyHeight;boolean repeat;boolean balloon;KeyCommonAttributes(XmlResourceParser xrp) {mXrp = xrp;balloon = true;}// Make sure the default object is not null./*** 确保默认的对象不是空。* * @param defAttr* @return*/boolean getAttributes(KeyCommonAttributes defAttr) {keyType = getInteger(mXrp, XMLATTR_KEY_TYPE, defAttr.keyType);keyWidth = getFloat(mXrp, XMLATTR_KEY_WIDTH, defAttr.keyWidth);keyHeight = getFloat(mXrp, XMLATTR_KEY_HEIGHT, defAttr.keyHeight);repeat = getBoolean(mXrp, XMLATTR_KEY_REPEAT, defAttr.repeat);balloon = getBoolean(mXrp, XMLATTR_KEY_BALLOON, defAttr.balloon);if (keyType < 0 || keyWidth <= 0 || keyHeight <= 0) {return false;}return true;}}public XmlKeyboardLoader(Context context) {mContext = context;mResources = mContext.getResources();}/*** 解析软键盘模版xml文件,生成一个软键盘模版对象。* * @param resourceId* @return*/public SkbTemplate loadSkbTemplate(int resourceId) {if (null == mContext || 0 == resourceId) {return null;}Resources r = mResources;XmlResourceParser xrp = r.getXml(resourceId);KeyCommonAttributes attrDef = new KeyCommonAttributes(xrp);KeyCommonAttributes attrKey = new KeyCommonAttributes(xrp);mSkbTemplate = new SkbTemplate(resourceId);int lastKeyTypeId = KEYTYPE_ID_LAST;int globalColor = 0;int globalColorHl = 0;int globalColorBalloon = 0;try {mXmlEventType = xrp.next();while (mXmlEventType != XmlResourceParser.END_DOCUMENT) {mNextEventFetched = false;if (mXmlEventType == XmlResourceParser.START_TAG) {String attribute = xrp.getName();if (XMLTAG_SKB_TEMPLATE.compareTo(attribute) == 0) {Drawable skbBg = getDrawable(xrp, XMLATTR_SKB_BG, null);Drawable balloonBg = getDrawable(xrp,XMLATTR_BALLOON_BG, null);Drawable popupBg = getDrawable(xrp, XMLATTR_POPUP_BG,null);if (null == skbBg || null == balloonBg|| null == popupBg) {return null;}mSkbTemplate.setBackgrounds(skbBg, balloonBg, popupBg);float xMargin = getFloat(xrp, XMLATTR_KEY_XMARGIN, 0);float yMargin = getFloat(xrp, XMLATTR_KEY_YMARGIN, 0);mSkbTemplate.setMargins(xMargin, yMargin);// Get default global colors.globalColor = getColor(xrp, XMLATTR_COLOR, 0);globalColorHl = getColor(xrp, XMLATTR_COLOR_HIGHLIGHT,0xffffffff);globalColorBalloon = getColor(xrp,XMLATTR_COLOR_BALLOON, 0xffffffff);} else if (XMLTAG_KEYTYPE.compareTo(attribute) == 0) {int id = getInteger(xrp, XMLATTR_ID, KEYTYPE_ID_LAST);Drawable bg = getDrawable(xrp, XMLATTR_KEYTYPE_BG, null);Drawable hlBg = getDrawable(xrp, XMLATTR_KEYTYPE_HLBG,null);int color = getColor(xrp, XMLATTR_COLOR, globalColor);int colorHl = getColor(xrp, XMLATTR_COLOR_HIGHLIGHT,globalColorHl);int colorBalloon = getColor(xrp, XMLATTR_COLOR_BALLOON,globalColorBalloon);if (id != lastKeyTypeId + 1) {return null;}SoftKeyType keyType = mSkbTemplate.createKeyType(id,bg, hlBg);keyType.setColors(color, colorHl, colorBalloon);if (!mSkbTemplate.addKeyType(keyType)) {return null;}lastKeyTypeId = id;} else if (XMLTAG_KEYICON.compareTo(attribute) == 0) {int keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);Drawable icon = getDrawable(xrp, XMLATTR_KEY_ICON, null);Drawable iconPopup = getDrawable(xrp,XMLATTR_KEY_ICON_POPUP, null);if (null != icon && null != iconPopup) {mSkbTemplate.addDefaultKeyIcons(keyCode, icon,iconPopup);}} else if (XMLTAG_KEY.compareTo(attribute) == 0) {int keyId = this.getInteger(xrp, XMLATTR_ID, -1);if (-1 == keyId)return null;if (!attrKey.getAttributes(attrDef)) {return null;}// Update the key position for the key.mKeyXPos = getFloat(xrp, XMLATTR_START_POS_X, 0);mKeyYPos = getFloat(xrp, XMLATTR_START_POS_Y, 0);SoftKey softKey = getSoftKey(xrp, attrKey);if (null == softKey)return null;mSkbTemplate.addDefaultKey(keyId, softKey);}}// Get the next tag.if (!mNextEventFetched)mXmlEventType = xrp.next();}xrp.close();return mSkbTemplate;} catch (XmlPullParserException e) {// Log.e(TAG, "Ill-formatted keyboard template resource file");} catch (IOException e) {// Log.e(TAG, "Unable to keyboard template resource file");}return null;}/*** 解析软键盘xml文件,生成一个软键盘。* * @param resourceId* @param skbWidth* @param skbHeight* @return*/public SoftKeyboard loadKeyboard(int resourceId, int skbWidth, int skbHeight) {if (null == mContext)return null;Resources r = mResources;SkbPool skbPool = SkbPool.getInstance();XmlResourceParser xrp = mContext.getResources().getXml(resourceId);mSkbTemplate = null;SoftKeyboard softKeyboard = null;Drawable skbBg;Drawable popupBg;Drawable balloonBg;SoftKey softKey = null;KeyCommonAttributes attrDef = new KeyCommonAttributes(xrp);KeyCommonAttributes attrSkb = new KeyCommonAttributes(xrp);KeyCommonAttributes attrRow = new KeyCommonAttributes(xrp);KeyCommonAttributes attrKeys = new KeyCommonAttributes(xrp);KeyCommonAttributes attrKey = new KeyCommonAttributes(xrp);mKeyXPos = 0;mKeyYPos = 0;mSkbWidth = skbWidth;mSkbHeight = skbHeight;try {mKeyXMargin = 0;mKeyYMargin = 0;mXmlEventType = xrp.next();while (mXmlEventType != XmlResourceParser.END_DOCUMENT) {mNextEventFetched = false;if (mXmlEventType == XmlResourceParser.START_TAG) {String attr = xrp.getName();// 1. Is it the root element, "keyboard"?if (XMLTAG_KEYBOARD.compareTo(attr) == 0) {// 1.1 Get the keyboard template id.int skbTemplateId = xrp.getAttributeResourceValue(null,XMLATTR_SKB_TEMPLATE, 0);// 1.2 Try to get the template from pool. If it is not// in, the pool will try to load it.mSkbTemplate = skbPool.getSkbTemplate(skbTemplateId,mContext);if (null == mSkbTemplate|| !attrSkb.getAttributes(attrDef)) {return null;}boolean cacheFlag = getBoolean(xrp,XMLATTR_SKB_CACHE_FLAG, DEFAULT_SKB_CACHE_FLAG);boolean stickyFlag = getBoolean(xrp,XMLATTR_SKB_STICKY_FLAG,DEFAULT_SKB_STICKY_FLAG);boolean isQwerty = getBoolean(xrp, XMLATTR_QWERTY,false);boolean isQwertyUpperCase = getBoolean(xrp,XMLATTR_QWERTY_UPPERCASE, false);softKeyboard = new SoftKeyboard(resourceId,mSkbTemplate, mSkbWidth, mSkbHeight);softKeyboard.setFlags(cacheFlag, stickyFlag, isQwerty,isQwertyUpperCase);mKeyXMargin = getFloat(xrp, XMLATTR_KEY_XMARGIN,mSkbTemplate.getXMargin());mKeyYMargin = getFloat(xrp, XMLATTR_KEY_YMARGIN,mSkbTemplate.getYMargin());skbBg = getDrawable(xrp, XMLATTR_SKB_BG, null);popupBg = getDrawable(xrp, XMLATTR_POPUP_BG, null);balloonBg = getDrawable(xrp, XMLATTR_BALLOON_BG, null);if (null != skbBg) {softKeyboard.setSkbBackground(skbBg);}if (null != popupBg) {softKeyboard.setPopupBackground(popupBg);}if (null != balloonBg) {softKeyboard.setKeyBalloonBackground(balloonBg);}softKeyboard.setKeyMargins(mKeyXMargin, mKeyYMargin);} else if (XMLTAG_ROW.compareTo(attr) == 0) {if (!attrRow.getAttributes(attrSkb)) {return null;}// Get the starting positions for the row.mKeyXPos = getFloat(xrp, XMLATTR_START_POS_X, 0);mKeyYPos = getFloat(xrp, XMLATTR_START_POS_Y, mKeyYPos);int rowId = getInteger(xrp, XMLATTR_ROW_ID,KeyRow.ALWAYS_SHOW_ROW_ID);softKeyboard.beginNewRow(rowId, mKeyYPos);} else if (XMLTAG_KEYS.compareTo(attr) == 0) {if (null == softKeyboard)return null;if (!attrKeys.getAttributes(attrRow)) {return null;}String splitter = xrp.getAttributeValue(null,XMLATTR_KEY_SPLITTER);splitter = Pattern.quote(splitter);String labels = xrp.getAttributeValue(null,XMLATTR_KEY_LABELS);String codes = xrp.getAttributeValue(null,XMLATTR_KEY_CODES);if (null == splitter || null == labels) {return null;}String labelArr[] = labels.split(splitter);String codeArr[] = null;if (null != codes) {codeArr = codes.split(splitter);if (labelArr.length != codeArr.length) {return null;}}for (int i = 0; i < labelArr.length; i++) {softKey = new SoftKey();int keyCode = 0;if (null != codeArr) {keyCode = Integer.valueOf(codeArr[i]);}softKey.setKeyAttribute(keyCode, labelArr[i],attrKeys.repeat, attrKeys.balloon);softKey.setKeyType(mSkbTemplate.getKeyType(attrKeys.keyType),null, null);float left, right, top, bottom;left = mKeyXPos;right = left + attrKeys.keyWidth;top = mKeyYPos;bottom = top + attrKeys.keyHeight;if (right - left < 2 * mKeyXMargin)return null;if (bottom - top < 2 * mKeyYMargin)return null;softKey.setKeyDimensions(left, top, right, bottom);softKeyboard.addSoftKey(softKey);mKeyXPos = right;if ((int) mKeyXPos * mSkbWidth > mSkbWidth) {return null;}}} else if (XMLTAG_KEY.compareTo(attr) == 0) {if (null == softKeyboard) {return null;}if (!attrKey.getAttributes(attrRow)) {return null;}int keyId = this.getInteger(xrp, XMLATTR_ID, -1);if (keyId >= 0) {softKey = mSkbTemplate.getDefaultKey(keyId);} else {softKey = getSoftKey(xrp, attrKey);}if (null == softKey)return null;// Update the position for next key.mKeyXPos = softKey.mRightF;if ((int) mKeyXPos * mSkbWidth > mSkbWidth) {return null;}// If the current xml event type becomes a starting tag,// it indicates that we have parsed too much to get// toggling states, and we started a new row. In this// case, the row starting position information should// be updated.if (mXmlEventType == XmlResourceParser.START_TAG) {attr = xrp.getName();if (XMLTAG_ROW.compareTo(attr) == 0) {mKeyYPos += attrRow.keyHeight;if ((int) mKeyYPos * mSkbHeight > mSkbHeight) {return null;}}}softKeyboard.addSoftKey(softKey);}} else if (mXmlEventType == XmlResourceParser.END_TAG) {String attr = xrp.getName();if (XMLTAG_ROW.compareTo(attr) == 0) {mKeyYPos += attrRow.keyHeight;if ((int) mKeyYPos * mSkbHeight > mSkbHeight) {return null;}}}// Get the next tag.if (!mNextEventFetched)mXmlEventType = xrp.next();}xrp.close();softKeyboard.setSkbCoreSize(mSkbWidth, mSkbHeight);return softKeyboard;} catch (XmlPullParserException e) {// Log.e(TAG, "Ill-formatted keybaord resource file");} catch (IOException e) {// Log.e(TAG, "Unable to read keyboard resource file");}return null;}// Caller makes sure xrp and r are valid./*** 解析一个按键,其中调用getToggleStates()解析ToggleState按键变换状态。* * @param xrp* @param attrKey* @return* @throws XmlPullParserException* @throws IOException*/private SoftKey getSoftKey(XmlResourceParser xrp,KeyCommonAttributes attrKey) throws XmlPullParserException,IOException {int keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);String keyLabel = getString(xrp, XMLATTR_KEY_LABEL, null);Drawable keyIcon = getDrawable(xrp, XMLATTR_KEY_ICON, null);Drawable keyIconPopup = getDrawable(xrp, XMLATTR_KEY_ICON_POPUP, null);int popupSkbId = xrp.getAttributeResourceValue(null,XMLATTR_KEY_POPUP_SKBID, 0);if (null == keyLabel && null == keyIcon) {keyIcon = mSkbTemplate.getDefaultKeyIcon(keyCode);keyIconPopup = mSkbTemplate.getDefaultKeyIconPopup(keyCode);if (null == keyIcon || null == keyIconPopup)return null;}// Dimension information must been initialized before// getting toggle state, because mKeyYPos may be changed// to next row when trying to get toggle state.float left, right, top, bottom;left = mKeyXPos;right = left + attrKey.keyWidth;top = mKeyYPos;bottom = top + attrKey.keyHeight;if (right - left < 2 * mKeyXMargin)return null;if (bottom - top < 2 * mKeyYMargin)return null;// Try to find if the next tag is// {@link #XMLTAG_TOGGLE_STATE_OF_KEY}, if yes, try to// create a toggle key.boolean toggleKey = false;mXmlEventType = xrp.next();mNextEventFetched = true;SoftKey softKey;if (mXmlEventType == XmlResourceParser.START_TAG) {mAttrTmp = xrp.getName();if (mAttrTmp.compareTo(XMLTAG_TOGGLE_STATE) == 0) {toggleKey = true;}}if (toggleKey) {softKey = new SoftKeyToggle();if (!((SoftKeyToggle) softKey).setToggleStates(getToggleStates(attrKey, (SoftKeyToggle) softKey, keyCode))) {return null;}} else {softKey = new SoftKey();}// Set the normal statesoftKey.setKeyAttribute(keyCode, keyLabel, attrKey.repeat,attrKey.balloon);softKey.setPopupSkbId(popupSkbId);softKey.setKeyType(mSkbTemplate.getKeyType(attrKey.keyType), keyIcon,keyIconPopup);softKey.setKeyDimensions(left, top, right, bottom);return softKey;}/*** 解析按键变换状态ToggleState。该函数是递归函数。* * @param attrKey* @param softKey* @param defKeyCode* @return* @throws XmlPullParserException* @throws IOException*/private SoftKeyToggle.ToggleState getToggleStates(KeyCommonAttributes attrKey, SoftKeyToggle softKey, int defKeyCode)throws XmlPullParserException, IOException {XmlResourceParser xrp = attrKey.mXrp;int stateId = getInteger(xrp, XMLATTR_TOGGLE_STATE_ID, 0);if (0 == stateId)return null;String keyLabel = getString(xrp, XMLATTR_KEY_LABEL, null);int keyTypeId = getInteger(xrp, XMLATTR_KEY_TYPE, KEYTYPE_ID_LAST);int keyCode;if (null == keyLabel) {keyCode = getInteger(xrp, XMLATTR_KEY_CODE, defKeyCode);} else {keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);}Drawable icon = getDrawable(xrp, XMLATTR_KEY_ICON, null);Drawable iconPopup = getDrawable(xrp, XMLATTR_KEY_ICON_POPUP, null);if (null == icon && null == keyLabel) {return null;}SoftKeyToggle.ToggleState rootState = softKey.createToggleState();rootState.setStateId(stateId);rootState.mKeyType = null;if (KEYTYPE_ID_LAST != keyTypeId) {rootState.mKeyType = mSkbTemplate.getKeyType(keyTypeId);}rootState.mKeyCode = keyCode;rootState.mKeyIcon = icon;rootState.mKeyIconPopup = iconPopup;rootState.mKeyLabel = keyLabel;boolean repeat = getBoolean(xrp, XMLATTR_KEY_REPEAT, attrKey.repeat);boolean balloon = getBoolean(xrp, XMLATTR_KEY_BALLOON, attrKey.balloon);rootState.setStateFlags(repeat, balloon);rootState.mNextState = null;// If there is another toggle state.mXmlEventType = xrp.next();while (mXmlEventType != XmlResourceParser.START_TAG&& mXmlEventType != XmlResourceParser.END_DOCUMENT) {mXmlEventType = xrp.next();}if (mXmlEventType == XmlResourceParser.START_TAG) {String attr = xrp.getName();if (attr.compareTo(XMLTAG_TOGGLE_STATE) == 0) {SoftKeyToggle.ToggleState nextState = getToggleStates(attrKey,softKey, defKeyCode);if (null == nextState)return null;rootState.mNextState = nextState;}}return rootState;}private int getInteger(XmlResourceParser xrp, String name, int defValue) {int resId = xrp.getAttributeResourceValue(null, name, 0);String s;if (resId == 0) {s = xrp.getAttributeValue(null, name);if (null == s)return defValue;try {int ret = Integer.valueOf(s);return ret;} catch (NumberFormatException e) {return defValue;}} else {return Integer.parseInt(mContext.getResources().getString(resId));}}private int getColor(XmlResourceParser xrp, String name, int defValue) {int resId = xrp.getAttributeResourceValue(null, name, 0);String s;if (resId == 0) {s = xrp.getAttributeValue(null, name);if (null == s)return defValue;try {int ret = Integer.valueOf(s);return ret;} catch (NumberFormatException e) {return defValue;}} else {return mContext.getResources().getColor(resId);}}private String getString(XmlResourceParser xrp, String name, String defValue) {int resId = xrp.getAttributeResourceValue(null, name, 0);if (resId == 0) {return xrp.getAttributeValue(null, name);} else {return mContext.getResources().getString(resId);}}private float getFloat(XmlResourceParser xrp, String name, float defValue) {int resId = xrp.getAttributeResourceValue(null, name, 0);if (resId == 0) {String s = xrp.getAttributeValue(null, name);if (null == s)return defValue;try {float ret;if (s.endsWith("%p")) {ret = Float.parseFloat(s.substring(0, s.length() - 2)) / 100;} else {ret = Float.parseFloat(s);}return ret;} catch (NumberFormatException e) {return defValue;}} else {return mContext.getResources().getDimension(resId);}}private boolean getBoolean(XmlResourceParser xrp, String name,boolean defValue) {String s = xrp.getAttributeValue(null, name);if (null == s)return defValue;try {boolean ret = Boolean.parseBoolean(s);return ret;} catch (NumberFormatException e) {return defValue;}}private Drawable getDrawable(XmlResourceParser xrp, String name,Drawable defValue) {int resId = xrp.getAttributeResourceValue(null, name, 0);if (0 == resId)return defValue;return mResources.getDrawable(resId);}
}

谷歌输入法PinyinIme 代码注释相关推荐

  1. 史上最良心程序员,在代码注释里,告诉这家公司有多坑

    程序员压力大,需要一个地方发泄,可又不能因此断了思路,于是代码注释成了绝佳的地方. 去年虾米音乐APP被爆出,代码注释中含有歧视侮辱性的词汇,将活动赠送的vip,标注为穷xvip.事件一曝光,就受到广 ...

  2. 谁的代码注释我都不服,就服你的!

    什么是代码注释,如何在代码中添加注释,相信每一位了解编程的人并不陌生.注释里往往有很多有趣的脑洞和「真心话」.今天我们一起去看看那些6到飞起,被玩坏了的幽默注释吧. 信息量太大的注释系列-- 01 你 ...

  3. 有趣的html代码_千万别惹程序员,否则会在代码注释里,告诉这家公司有多坑...

    Python现在非常火,语法简单而且功能强大,很多同学都想学Python!所以小的给各位看官们准备了高价值Python学习视频教程及相关电子版书籍,都放在了文章结尾,欢迎前来领取! 每个程序员敲代码都 ...

  4. 超有意思的代码注释_程序员搞笑的代码注释:谁的代码注释我都不服,就服你的...

    什么是代码注释,如何在代码中添加注释,相信每一位了解编程的人并不陌生.注释里往往有很多有趣的脑洞和「真心话」.今天我们一起去看看那些6到飞起,被玩坏了的幽默注释吧. 信息量太大的注释系列-- 01 你 ...

  5. 个性代码注释 大合集

    键盘 """┌───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┐│Esc│ ...

  6. 如来佛代码注释,保佑代码无BUG

    如来佛代码注释,保佑代码无BUG         /*                            _ooOoo_                           o8888888o   ...

  7. java 代码注释搞笑_笑哭丨谁的代码注释我都不服,就服你的!

    什么是代码注释,如何在代码中添加注释,相信每一位了解编程的人并不陌生.注释里往往有很多有趣的脑洞和「真心话」.今天我们一起去看看那些6到飞起,被玩坏了的幽默注释吧. 信息量太大的注释系列-- 01 你 ...

  8. 归并排序(代码注释超详细)

    归并排序: (复制粘贴百度百科没什么意思),简单来说,就是对数组进行分组,然后分组进行排序,排序完最后再整合起来排序! 我看了很多博客,都是写的8个数据呀什么的(2^4,分组方便),我就想着,要是10 ...

  9. 代码注释//_您应该停止编写//的五个代码注释,并且//应该开始的一个注释

    代码注释// 提供来自您最喜欢和最受欢迎的开源项目的示例-React,Angular,PHP,Pandas等! (With examples from your favorite and most p ...

最新文章

  1. Flex通信-Java服务端通信实例
  2. dns tunnel工具地址
  3. JS实现一行内多列DIV同高
  4. 成功解决D8016“/ZI”和“/Gy-”命令行选项不兼容
  5. python中operator.itemgetter函数
  6. java窗体设置最小宽度_flex web Application设置最小高度和宽度。
  7. python copy函数用法_Python深浅拷贝
  8. Java 集合 ArrayList 需要知道的几个问题
  9. python django 是啥_python的django做什么的
  10. VC/VS开发问题集锦
  11. 【Python】Matplotlib绘制带颜色标尺的彩色曲面
  12. 【配置git和github】github鉴权失败 git配置github 免密登录
  13. 如何让你的小刺猬顺刺
  14. char在python中什么意思_C语言-char 类型基本概念
  15. 适合普通人的108个短视频项目:不用出镜也能赚钱的手机摄影玩法(3)
  16. 股软分析系统源代码,股软开发,行情写库程序
  17. Sipeed MaixSense:Allwinner R329 (一)官方Debian系统--AIPU的基本使用--图像识别
  18. python定义整数_Python | 程序定义一个整数值并打印
  19. 解决Android在更新安装包时出现“未安装应用”的情况
  20. 国家教育部牵手曙光公司——“百校工程”助力教育行业大数据平台建设

热门文章

  1. java中调用js代码
  2. 【Android】下拉刷新上拉加载更多组件记录(81/100)
  3. “鱼类零食第一股”劲仔食品为何游不动了?
  4. 机器学习-Boosting(AdaBoost、GBDT)
  5. kubectl常用删除命令
  6. 全球与中国肥料定量机市场现状及未来发展趋势
  7. Ubuntu添加用户到docker组用户,免sudo执行
  8. filter函数的用法
  9. c语言题目 ppt怎么做,C语言学生题目.ppt
  10. web版svg制作工具