本篇为SoftKeyboard源代码注释。
1、LatinKeyboard
public class LatinKeyboard extends Keyboard { private Key mEnterKey; public LatinKeyboard(Context context, int xmlLayoutResId) { super(context, xmlLayoutResId); Log.i("mytest", "LatinKeyboard_LatinKeyboard"); } public LatinKeyboard(Context context, int layoutTemplateResId, CharSequence characters, int columns, int horizontalPadding) { super(context, layoutTemplateResId, characters, columns, horizontalPadding); Log.i("mytest", "LatinKeyboard_LatinKeyboard"); } /* * 描绘键盘时候(由构造函数 )自动调用 * */ @Override protected Key createKeyFromXml(Resources res, Row parent, int x, int y, XmlResourceParser parser) { Log.i("mytest", "LatinKeyboard_createKeyFromXml"); Key key = new LatinKey(res, parent, x, y, parser); //重载的目的,好像仅仅是为了记录回车键的值而已(以Key型记录) //无非就是想对回车键做改观 if (key.codes[0] == 10) { mEnterKey = key; } return key; } /** * This looks at the ime options given by the current editor, to set the * appropriate label on the keyboard's enter key (if it has one). */ void setImeOptions(Resources res, int options) { //在SoftKeyboard的StartInput函数最后用到了 //传入了EditorInfo.imeOptions类型的options参数。此变量地位与EditorInfo.inputType类似。但作用截然不同 Log.i("mytest", "LatinKeyboard_setImeOptions"); if (mEnterKey == null) { return; } //惊爆:只要加载了EditorInfo的包,就可以使用其中的常量,所熟知的TextView类中的常量,经过试验也是可以任意使用的,猜测这些都是静态变量 switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) { case EditorInfo.IME_ACTION_GO: mEnterKey.iconPreview = null; mEnterKey.icon = null;//把图片设为空,并不代表就是空,只是下面的Lable可以代替之 mEnterKey.label = res.getText(R.string.label_go_key); break; case EditorInfo.IME_ACTION_NEXT: mEnterKey.iconPreview = null; mEnterKey.icon = null; mEnterKey.label = res.getText(R.string.label_next_key); break; case EditorInfo.IME_ACTION_SEARCH: mEnterKey.icon = res.getDrawable( R.drawable.sym_keyboard_search); mEnterKey.label = null; break; case EditorInfo.IME_ACTION_SEND: mEnterKey.iconPreview = null; mEnterKey.icon = null; mEnterKey.label = res.getText(R.string.label_send_key); break; default: mEnterKey.icon = res.getDrawable( R.drawable.sym_keyboard_return); mEnterKey.label = null; break; } } static class LatinKey extends Keyboard.Key { public LatinKey(Resources res, Keyboard.Row parent, int x, int y, XmlResourceParser parser) { super(res, parent, x, y, parser); Log.i("mytest", "LatinKeyboard_LatinKey"); } /** * Overriding this method so that we can reduce the target area for the key that * closes the keyboard. */ @Override public boolean isInside(int x, int y) { Log.i("mytest", "LatinKeyboard_isInside"); return super.isInside(x, codes[0] == KEYCODE_CANCEL ? y - 10 : y); //只有一个左下角cancel键跟super的此函数不一样,其余相同 //仅仅为了防止错误的点击?将cancel键的作用范围减小了10,其余的,如果作用到位,都返回true } } } 2、LatinKeyboardView
public class LatinKeyboardView extends KeyboardView { //网上说:当继承View的时候,会有个一个含有AttributeSet参数的构造方法, //通过此类就可以得到自己定义的xml属性,也可以是android的内置的属性 //就好像TextView这东西也有个 View的基类 //干什么用的?好像是设了一个无用的键值,等到后面调用 static final int KEYCODE_OPTIONS = -100; public LatinKeyboardView(Context context, AttributeSet attrs) { super(context, attrs); Log.i("mytest", "LatinKeyboardView_LatinKeyboardView(Context context, AttributeSet attrs) "); } public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); Log.i("mytest", "LatinKeyboardView_LatinKeyboardView(Context context, AttributeSet attrs, int defStyle)"); } @Override protected boolean onLongPress(Key key) { Log.i("mytest", "LatinKeyboardView_onLongPress"); //codes[0]代表当前按的值.按时间长了就失去了效果(cancel) if (key.codes[0] == Keyboard.KEYCODE_CANCEL) { getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS, null); return true; } else { return super.onLongPress(key); } } } 3、CandidateView
public class CandidateView extends View { private static final int OUT_OF_BOUNDS = -1; //这个是这个candidateView的宿主类,也就是该view是为什么输入法服务的。 private SoftKeyboard mService; //这个是建议。比如说当我们输入一些字母之后输入法希望根据输入来进行联想建议。 private List<String> mSuggestions; //这个是用户选择的词的索引。 private int mSelectedIndex; private int mTouchX = OUT_OF_BOUNDS; //这个是用来描绘选择区域高亮的一个类 private Drawable mSelectionHighlight; //键入的word是否合法正确。 private boolean mTypedWordValid; //背景填充区域,决定将要在那个部分显示? private Rect mBgPadding; private static final int MAX_SUGGESTIONS = 32; private static final int SCROLL_PIXELS = 20; //这个是对于候选词的每个词的宽度 private int[] mWordWidth = new int[MAX_SUGGESTIONS]; //这个是每个候选词的X坐标。 private int[] mWordX = new int[MAX_SUGGESTIONS]; //难道是两个词语之间的间隙?对了! private static final int X_GAP = 10; private static final List<String> EMPTY_LIST = new ArrayList<String>(); private int mColorNormal; private int mColorRecommended; private int mColorOther; private int mVerticalPadding; //所有关于绘制的信息,比如线条的颜色等 private Paint mPaint; private boolean mScrolled; private int mTargetScrollX; private int mTotalWidth; private GestureDetector mGestureDetector; /** * Construct a CandidateView for showing suggested words for completion. * @param context * @param attrs */ public CandidateView(Context context) { //activity,inputmethodservice,这都是context的派生类 super(context); //getResouces这个函数用来得到这个应用程序的所有资源,就连android自带的资源也要如此 mSelectionHighlight = context.getResources().getDrawable( android.R.drawable.list_selector_background); //mSelectionHighlight类型是Drawable,而Drawable设置状态就是这样 mSelectionHighlight.setState(new int[] { android.R.attr.state_enabled, //这行如果去掉,点击候选词的时候是灰色,但是也可以用 android.R.attr.state_focused, //用处不明。。。。 android.R.attr.state_window_focused, //这行如果去掉,当点击候选词的时候背景不会变成橙色 android.R.attr.state_pressed //点击候选词语时候背景颜色深浅的变化,不知深层意义是什么? }); Resources r = context.getResources(); setBackgroundColor(r.getColor(R.color.candidate_background)); //设置高亮区域的背景颜色,还是透明的,很美,很美,但为什么是透明的还有待考证? //这个颜色,是非首选词的颜色 mColorNormal = r.getColor(R.color.candidate_normal); //找到了,这个是显示字体的颜色 mColorRecommended = r.getColor(R.color.candidate_recommended); //这个是候选词语分割线的颜色 mColorOther = r.getColor(R.color.candidate_other); //这是系统定义的一个整型变量。用就可以了 mVerticalPadding = r.getDimensionPixelSize(R.dimen.candidate_vertical_padding); mPaint = new Paint(); mPaint.setColor(mColorNormal); //这行如果没有,那么字体的线条就不一样 mPaint.setAntiAlias(true); mPaint.setTextSize(r.getDimensionPixelSize(R.dimen.candidate_font_height)); mPaint.setStrokeWidth(0); //用手可以滑动,这是在构造函数里面对滑动监听的重载,猜测,这个函数与onTouchEvent函数应该是同时起作用? mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { mScrolled = true; //得到滑动开始的横坐标 int sx = getScrollX(); //加上滑动的距离,这个滑动距离是最后一次call滑动之间的距离,很小,应该 sx += distanceX; if (sx < 0) { sx = 0; } if (sx + getWidth() > mTotalWidth) { sx -= distanceX; } //记录将要移动到的位置,后面会用到 mTargetScrollX = sx; //这是处理滑动的函数,view类的函数。后面一个参数,说明Y轴永远不变,如果你尝试去改变一下,经测试,太好玩了 scrollTo(sx, getScrollY()); //文档中说的是使得整个VIew作废,但是如果不用这句,会发生什么? invalidate(); return true; } }); //这后三行语句不是在GestureDetector函数中的,而是在构造函数中的,当候选View建立成功的时候就已经是下面的状态了 //拖动时刻左右两边的淡出效果 setHorizontalFadingEdgeEnabled(true); //当拖动的时候,依旧可以输入并显示 setWillNotDraw(false); //作用暂时不明? setHorizontalScrollBarEnabled(false); setVerticalScrollBarEnabled(false); Log.i("mytest", "CandidateView_CandidateView"); } /** * A connection back to the service to communicate with the text field * @param listener */ public void setService(SoftKeyboard listener) { //自己定义的废柴函数,使得私有变量mService的值得以改变 mService = listener; Log.i("mytest", "CandidateView_setService"); } @Override public int computeHorizontalScrollRange() { Log.i("mytest", "CandidateView_computeHorizontalScrollRange"); return mTotalWidth; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //猜测:如果第二参数是从onMeasure的参数中来的,就用第二变量 //这个函数是个调和宽度函数,一般情况下用参数1值,除非第二个参数给其限制 int measuredWidth = resolveSize(50, widthMeasureSpec); Log.i("mytest", "CandidateView_onMeasure"); // Get the desired height of the icon menu view (last row of items does // not have a divider below) Rect padding = new Rect(); //吴:高亮区域除了字以外,剩下的空隙,用getPadding得到,或许,这是由list_selector_background决定的。 mSelectionHighlight.getPadding(padding); final int desiredHeight = ((int)mPaint.getTextSize()) + mVerticalPadding + padding.top + padding.bottom; // Maximum possible width and desired height setMeasuredDimension(measuredWidth, resolveSize(desiredHeight, heightMeasureSpec)); } /** * If the canvas is null, then only touch calculations are performed to pick the target * candidate. */ @Override protected void onDraw(Canvas canvas) { //这是每个View对象绘制自己的函数,重载之。经测试:没有这个函数的重载,则显示不出字来,这个就是用来显示字条 Log.i("mytest", "CandidateView_onDraw"); if (canvas != null) { super.onDraw(canvas); } mTotalWidth = 0; if (mSuggestions == null) return; if (mBgPadding == null) { mBgPadding = new Rect(0, 0, 0, 0); if (getBackground() != null) { getBackground().getPadding(mBgPadding); } } //第一个词左侧为0,测试知道:这个地方能改变文字的左侧开端 int x = 0; final int count = mSuggestions.size(); final int height = getHeight(); final Rect bgPadding = mBgPadding; final Paint paint = mPaint; final int touchX = mTouchX; //取得被点击词语的横坐标 final int scrollX = getScrollX(); final boolean scrolled = mScrolled; final boolean typedWordValid = mTypedWordValid; final int y = (int) (((height - mPaint.getTextSize()) / 2) - mPaint.ascent()); for (int i = 0; i < count; i++) { //开始一个一个地添置候选词,但是本例中,候选词只能有1个? String suggestion = mSuggestions.get(i); //获取词语宽度,但是词语的字号又是怎么设定的呢? float textWidth = paint.measureText(suggestion); //整体宽度是词语宽度加上两倍间隙 final int wordWidth = (int) textWidth + X_GAP * 2; mWordX[i] = x; mWordWidth[i] = wordWidth; paint.setColor(mColorNormal); //保持正常输出而不受触摸影响的复杂条件 if (touchX + scrollX >= x && touchX + scrollX < x + wordWidth && !scrolled) { if (canvas != null) { //画布转变位置,按下候选词后,看到的黄色区域是画布处理的位置 canvas.translate(x, 0); mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height); mSelectionHighlight.draw(canvas); //上面两句是密不可分的,第一步给框,第二步画画,不知与canvas.translate(x, 0);什么关系。画布与词的显示位置好像没有什么关系 //词的位置的改变在下面处理 canvas.translate(-x, 0); } mSelectedIndex = i; } if (canvas != null) { if ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid)) { //第一个候选词,设置不同的显示式样,粗体 paint.setFakeBoldText(true); paint.setColor(mColorRecommended); } else if (i != 0) { paint.setColor(mColorOther); } //测试得:这里才能决定词语出现的位置 canvas.drawText(suggestion, x + X_GAP, y, paint); paint.setColor(mColorOther); canvas.drawLine(x + wordWidth + 0.5f, bgPadding.top, x + wordWidth + 0.5f, height + 1, paint); paint.setFakeBoldText(false); } x += wordWidth; } mTotalWidth = x; //每个滑动,都会造成mTargetScrollX改变,因为他在动作监听函数里面赋值 if (mTargetScrollX != getScrollX()) { //意思是说:只要移动了。难道是说如果在移动完成之后进行的输入,则进行下面操作? //如果在移动完成之后输入,那么mTargetScrollX记录的也是移动最终目标的水平坐标 scrollToTarget(); } } //这个地方,应该和下面的setSuggestions函数一起看,对于滑动之后一输入就归零的问题,有两个原因,源头 //都在setSuggestions函数中,一个是scrollTo(0, 0);这句话,每当输入一个字母,就有了一个新词语,这个新词语 //会致使scrollTo(0, 0);的发生。但是就算把这句话注释掉,下面的一句mTargetScrollX = 0;也会使得Ondraw() //这个函数的调用到最后的时候,执行scrollToTarget();产生作用,回复到0位置。 private void scrollToTarget() { Log.i("mytest", "CandidateView_scrollToTarget"); int sx = getScrollX(); if (mTargetScrollX > sx) { sx += SCROLL_PIXELS; if (sx >= mTargetScrollX) { sx = mTargetScrollX; requestLayout(); } } else { sx -= SCROLL_PIXELS; if (sx <= mTargetScrollX) { sx = mTargetScrollX; requestLayout(); } } //移动之 。 p.s不要把高亮区与候选栏相混,移动的,是候选栏,高亮区自从生成就亘古不变,直到消失 scrollTo(sx, getScrollY()); invalidate(); } public void setSuggestions(List<String> suggestions, boolean completions, boolean typedWordValid) { //此函数本类中出现就一次,会在别的类中调用,没有内部调用 Log.i("mytest", "CandidateView_setSuggestions"); clear(); if (suggestions != null) { //新的建议集合字串就是传过来的这个参数字串。 mSuggestions = new ArrayList<String>(suggestions); } //确定此词是否可用? mTypedWordValid = typedWordValid; //每当有新的候选词出现,view就会滑动到初始的位置 scrollTo(0, 0); mTargetScrollX = 0; // Compute the total width //onDraw的参数为null的时候,他不再执行super里面的onDraw onDraw(null); invalidate(); requestLayout();//文档:当View作废时候使用 } public void clear() { Log.i("mytest", "CandidateView_clear"); //前面定义了,这是一个空数组,将候选词库弄为空数组 mSuggestions = EMPTY_LIST; mTouchX = OUT_OF_BOUNDS; //把被触摸的横坐标定为一个负数,这样的话就等于没触摸 mSelectedIndex = -1; invalidate(); } @Override public boolean onTouchEvent(MotionEvent me) { //这是触屏选词工作 Log.i("mytest", "CandidateView_onTouchEvent"); //猜测,如果前面那个滑动监听函数起了作用,就不用再乎这个函数后面的了,这是对的! //文档中这样解释:GestureDetector.OnGestureListener使用的时候,这里会返回 //true,后面又说,前面定义的GestureDetector.SimpleOnGestureListener, //是GestureDetector.OnGestureListener的派生类 if (mGestureDetector.onTouchEvent(me)) { return true; }//p.s.经注解忽略测试发现:所有的触摸效果源自这里。如果注解掉,则不会发生滑动 int action = me.getAction(); int x = (int) me.getX(); int y = (int) me.getY(); mTouchX = x; //被点击词语的横坐标 //如果后续出现滑动,又会被前面那个监听到的 switch (action) { case MotionEvent.ACTION_DOWN: mScrolled = false; invalidate(); break; case MotionEvent.ACTION_MOVE: //选词,经过测试,当向上滑动的时候也是可以选词的 if (y <= 0) { // Fling up!? if (mSelectedIndex >= 0) { mService.pickSuggestionManually(mSelectedIndex); mSelectedIndex = -1; } } invalidate(); break; case MotionEvent.ACTION_UP: if (!mScrolled) { if (mSelectedIndex >= 0) { mService.pickSuggestionManually(mSelectedIndex); //点击选词经测试合格 } } mSelectedIndex = -1; removeHighlight();//消除高亮区域 requestLayout(); //文档:当View作废时候使用 break; } return true; } /** * For flick through from keyboard, call this method with the x coordinate of the flick * gesture. * @param x */ public void takeSuggestionAt(float x) { //本类中只出现了一次,在别的类中有调用 Log.i("mytest", "CandidateView_takeSuggestionAt"); //此处也给mTouchX赋了非负值 mTouchX = (int) x; // To detect candidate onDraw(null); if (mSelectedIndex >= 0) { mService.pickSuggestionManually(mSelectedIndex); } invalidate(); } private void removeHighlight() {//取消高亮区域的显示,等待下次生成 Log.i("mytest", "CandidateView_removeHighlight"); //把被触摸的横坐标定为一个负数,这样的话就等于没触摸 mTouchX = OUT_OF_BOUNDS; invalidate(); } } 4、SoftKeyboard
/** * Example of writing an input method for a soft keyboard. This code is * focused on simplicity over completeness, so it should in no way be considered * to be a complete soft keyboard implementation. Its purpose is to provide * a basic example for how you would get started writing an input method, to * be fleshed out as appropriate. */ public class SoftKeyboard extends InputMethodService implements KeyboardView.OnKeyboardActionListener { static final boolean DEBUG = false; /** * This boolean indicates the optional example code for performing * processing of hard keys in addition to regular text generation * from on-screen interaction. It would be used for input methods that * perform language translations (such as converting text entered on * a QWERTY keyboard to Chinese), but may not be used for input methods * that are primarily intended to be used for on-screen text entry. */ //是否在用硬键盘,这里默认的是总可以使用,费柴变量 static final boolean PROCESS_HARD_KEYS = true; //键盘view对象,但不是自己定义的类latinkeyboardview.... private KeyboardView mInputView; //候选栏对象 private CandidateView mCandidateView; //候选串之串 private CompletionInfo[] mCompletions; private StringBuilder mComposing = new StringBuilder(); //这东西是决定能不能有候选条 private boolean mPredictionOn; //决定auto是否需要显示在候选栏 private boolean mCompletionOn; private int mLastDisplayWidth; private boolean mCapsLock; private long mLastShiftTime; //matakey的按下状态,猜测是每种组合对应一个此值? private long mMetaState; private LatinKeyboard mSymbolsKeyboard; private LatinKeyboard mSymbolsShiftedKeyboard; private LatinKeyboard mQwertyKeyboard; //当前键盘 private LatinKeyboard mCurKeyboard; //默认的使得输入中断的字符 private String mWordSeparators; /** * Main initialization of the input method component. Be sure to call * to super class. */ @Override public void onCreate() { super.onCreate(); //对resource这个东西有了一些了解:getResources是contextWrapper类的函数,contextWrapper而是inputmethodservice //的间接基类 mWordSeparators = getResources().getString(R.string.word_separators); Log.i("mytest", "SoftKeyboard_onCreate"); } /** * This is the point where you can do all of your UI initialization. It * is called after creation and any configuration change. */ @Override public void onInitializeInterface() { Log.i("mytest", "SoftKeyboard_onInitializeInterface"); //这只加载键盘,类似于findViewById,离真正生成界面还早 if (mQwertyKeyboard != null) { // Configuration changes can happen after the keyboard gets recreated, // so we need to be able to re-build the keyboards if the available // space has changed. //可用的,最大屏幕宽度,好像也没什么用 int displayWidth = getMaxWidth(); if (displayWidth == mLastDisplayWidth) return; //难道就是为了记录最大宽度于mLastDisplayWidth? mLastDisplayWidth = displayWidth; } mQwertyKeyboard = new LatinKeyboard(this, R.xml.qwerty); mSymbolsKeyboard = new LatinKeyboard(this, R.xml.symbols); mSymbolsShiftedKeyboard = new LatinKeyboard(this, R.xml.symbols_shift); } /** * Called by the framework when your view for creating input needs to * be generated. This will be called the first time your input method * is displayed, and every time it needs to be re-created such as due to * a configuration change. */ @Override public View onCreateInputView() { Log.i("mytest", "SoftKeyboard_onCreateInputView"); mInputView = (KeyboardView) getLayoutInflater().inflate( R.layout.input, null); //上边的函数findViewById对于keyboardView是不能用的 //只对TextView等可以用 mInputView.setOnKeyboardActionListener(this); mInputView.setKeyboard(mQwertyKeyboard); //通过这个return,自己定义的keyboardview类对象就与这个类绑定了 return mInputView; } /** * Called by the framework when your view for showing candidates needs to * be generated, like {@link #onCreateInputView}. */ @Override public View onCreateCandidatesView() { Log.i("mytest", "SoftKeyboard_onCreateCandidatesView"); mCandidateView = new CandidateView(this); //为什么参数是this??因为activity,inputmethodservice,这都是context的派生类 mCandidateView.setService(this);//在CandidateView类里面对这个类的描述中,参数就是个 return mCandidateView; //这一步很重要,后面的setCandidatesViewShown(false);就是个返回的结果造成的? } /** * This is the main point where we do our initialization of the input method * to begin operating on an application. At this point we have been * bound to the client, and are now receiving all of the detailed information * about the target of our edits. */ @Override public void onStartInput(EditorInfo attribute, boolean restarting) { super.onStartInput(attribute, restarting); Log.i("mytest", "SoftKeyboard_onStartInput"); // Reset our state. We want to do this even if restarting, because // the underlying state of the text editor could have changed in any way. //一个StringBuilder,前面定义的 mComposing.setLength(0); updateCandidates();//可知此处的candidateview注定还不显示 if (!restarting) { // Clear shift states. mMetaState = 0; } mPredictionOn = false; //猜测:是否需要显示候选词条,证实确实如此 mCompletionOn = false; //允许auto的内容显示在后选栏中 mCompletions = null; // We are now going to initialize our state based on the type of // text being edited. //一个靠谱的猜测:inputtype的给定值里面有那么几个掩码,但是从参数传来的具体inputtype值里面包含了所有的信息,不同的掩码能够得出不同的信息 //例如TYPE_MASK_CLASS就能得出下面四种,这四种属于同一类期望信息,这个信息叫做CLASS,下面一个掩码TYPE_MASK_VARIATION按位与出来的是一类 //叫做VARIATION的信息 switch (attribute.inputType&EditorInfo.TYPE_MASK_CLASS) { case EditorInfo.TYPE_CLASS_NUMBER: case EditorInfo.TYPE_CLASS_DATETIME: // Numbers and dates default to the symbols keyboard, with // no extra features. mCurKeyboard = mSymbolsKeyboard; break; case EditorInfo.TYPE_CLASS_PHONE: // Phones will also default to the symbols keyboard, though // often you will want to have a dedicated phone keyboard. mCurKeyboard = mSymbolsKeyboard; break; case EditorInfo.TYPE_CLASS_TEXT: // This is general text editing. We will default to the // normal alphabetic keyboard, and assume that we should // be doing predictive text (showing candidates as the // user types). mCurKeyboard = mQwertyKeyboard; mPredictionOn = true; // We now look for a few special variations of text that will // modify our behavior. int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION; if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { // Do not display predictions / what the user is typing // when they are entering a password. mPredictionOn = false; //密码框的输入是不需要候选词条的 } if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || variation == EditorInfo.TYPE_TEXT_VARIATION_URI || variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) { // Our predictions are not useful for e-mail addresses // or URIs. mPredictionOn = false; //如果是网站或者是邮箱地址,不用候选词条 } if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { //开始界面的那个输入框,就是自动生成的 // If this is an auto-complete text view, then our predictions // will not be shown and instead we will allow the editor // to supply their own. We only show the editor's // candidates when in fullscreen mode, otherwise relying // own it displaying its own UI. mPredictionOn = false; //经过测试,当输入法处在全屏模式的时候,原本auto的候选词会显示在输入法的候选栏中 //这是mCompletiOn的作用,这个值初始化设为false. //如果把这里的两个值都设置为true则可以发现再输入任意auto的时候都会在候选栏中显示auto的词语 //所以,变量mCompletionOn的后续作用需要监视 //这两行做后续测试: 真值:false,isFullscreenMode() mCompletionOn = isFullscreenMode(); } // We also want to look at the current state of the editor // to decide whether our alphabetic keyboard should start out // shifted. updateShiftKeyState(attribute); break; default: // For all unknown input types, default to the alphabetic // keyboard with no special features. mCurKeyboard = mQwertyKeyboard; updateShiftKeyState(attribute);//决定是否需要初始大写状态 } // Update the label on the enter key, depending on what the application // says it will do. mCurKeyboard.setImeOptions(getResources(), attribute.imeOptions);//根据输入目标设置回车键 } /** * This is called when the user is done editing a field. We can use * this to reset our state. */ @Override public void onFinishInput() { super.onFinishInput(); Log.i("mytest", "SoftKeyboard_onFinishInput"); //经测试,终于发现,start与finish,在输入框切换的时候,平时这俩结束函数并不调用,或许输入框只是隐藏。 //测试语句 mInputView=null; // Clear current composing text and candidates. mComposing.setLength(0); updateCandidates(); // We only hide the candidates window when finishing input on // a particular editor, to avoid popping the underlying application // up and down if the user is entering text into the bottom of // its window. setCandidatesViewShown(false);//默认的就是不可见的 mCurKeyboard = mQwertyKeyboard; if (mInputView != null) { mInputView.closing(); //据分析,关闭输入界面和收起输入界面还不是一回事? } } @Override public void onStartInputView(EditorInfo attribute, boolean restarting) { super.onStartInputView(attribute, restarting); //如果没有这个函数的作用,在切换输入目标的时候不会发生键盘的变化 //而且经过测试,这个函数执行的时间是开始输入的时候 // Apply the selected keyboard to the input view. Log.i("mytest", "SoftKeyboard_onStartInputView"); mInputView.setKeyboard(mCurKeyboard); //这个是转换键盘的关键 //mInputView是自己定义的一个键盘 mInputView.closing(); //这个语句能让整个需要输入的目标关闭?到底是干什么用的??疑问? } /** * Deal with the editor reporting movement of its cursor. */ @Override public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { //光标! super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd); Log.i("mytest", "SoftKeyboard_onUpdateSelection"); // If the current selection in the text view changes, we should // clear whatever candidate text we have. //当输入框向输入法报告用户移动了光标时调用。,当用户移动输入框中的光标的时候,它就默认的表示本次输入完成了, //然后将候选词以及正在输入的文本复位,并且向编辑器报告输入法已经完成了一个输入。 //四个整形都是坐标? if (mComposing.length() > 0 && (newSelStart != candidatesEnd || newSelEnd != candidatesEnd)) { mComposing.setLength(0);//这才是候选栏置空的精义所在 updateCandidates();//候选栏置空 InputConnection ic = getCurrentInputConnection(); //这个语句和下面if里面那个,决定了结束输入的全过程 if (ic != null) { ic.finishComposingText();//这个语句的作用是,让输入目标内的下划线去掉,完成一次编辑 } } } /** * This tells us about completions that the editor has determined based * on the current text in it. We want to use this in fullscreen mode * to show the completions ourself, since the editor can not be seen * in that situation. */ @Override public void onDisplayCompletions(CompletionInfo[] completions) { //当需要在候选栏里面显示auto的内容 //此函数作用,猜测:当全屏幕模式的时候,mCompletionOn置true,可以通过候选栏来显示auto Log.i("mytest", "SoftKeyboard_onDisplayCompletions"); if (mCompletionOn) { //必须这个变量允许 mCompletions = completions; //赋值给本来里面专门记录候选值的变量 if (completions == null) { setSuggestions(null, false, false); //如果没有候选词,就这样处置 return; } List<String> stringList = new ArrayList<String>(); for (int i=0; i<(completions != null ? completions.length : 0); i++) { CompletionInfo ci = completions[i]; if (ci != null) stringList.add(ci.getText().toString()); } setSuggestions(stringList, true, true); } } /** * This translates incoming hard key events in to edit operations on an * InputConnection. It is only needed when using the * PROCESS_HARD_KEYS option. */ private boolean translateKeyDown(int keyCode, KeyEvent event) { //这个函数在OnKeyDown中用到了 //这个是当组合键时候用,shift+A或者别的Alt+A之类 Log.i("mytest", "SoftKeyboard_translateKeyDown"); mMetaState = MetaKeyKeyListener.handleKeyDown(mMetaState, keyCode, event); //处理matakey的按下,猜测:每一个long型的mMetaState值都代表着一个meta键组合值。8成是对的 int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(mMetaState)); //如果没这套组合键,就返回0 //这又是在干什么?猜测:每一个mMetaState值,对应着一个unicode值,这一步就是为了得到它,此猜测正确 //重置这个元状态。当取得了C值之后,完全可以重置元状态了,后面的语句不会出现任何问题。 //上面这三行有点疑问 mMetaState = MetaKeyKeyListener.adjustMetaAfterKeypress(mMetaState); //后边这函数是inputmethodservice自己的,获得当前的链接 InputConnection ic = getCurrentInputConnection(); if (c == 0 || ic == null) { return false; } //一个dead=true意味着是一个有定义的组合键 boolean dead = false; //看看c所昭示的这个键能不能被允许组合键 if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) { //定义下来看能否使用这个组合键 dead = true; //这样就得到了真正的码值 c = c & KeyCharacterMap.COMBINING_ACCENT_MASK; } //这是处理“编辑中最后字符越变”的情况 if (mComposing.length() > 0) { char accent = mComposing.charAt(mComposing.length() -1 );//返回正在编辑的字串的最后一个字符 //这种情况下最后是返回了新的阿斯课码。composed最终还是要还给c.作为onKey的参数。 int composed = KeyEvent.getDeadChar(accent, c); if (composed != 0) { c = composed; mComposing.setLength(mComposing.length()-1); // 要把最后一个字符去掉,才能够在下一步中越变成为新的字符 } } onKey(c, null); //强制输入C,这样就实现了组合键的功效 return true; } /** * Use this to monitor key events being delivered to the application. * We get first crack at them, and can either resume them or let them * continue to the app. */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { Log.i("mytest", "SoftKeyboard_onKeyDown"); //这是重载了基类的,经测试确定,只有在硬件盘被敲击时候才调用,除了那个键本身的功效,还有这里定义的这些 //是对输入法的影响 switch (keyCode) { case KeyEvent.KEYCODE_BACK: //这就是那个破箭头,扭曲的 // The InputMethodService already takes care of the back // key for us, to dismiss the input method if it is shown. // However, our keyboard could be showing a pop-up window // that back should dismiss, so we first allow it to do that. //mInputView类是自己定义的keyBoardView类 if (event.getRepeatCount() == 0 && mInputView != null) { if (mInputView.handleBack()) {//通过弯钩键来关闭键盘的元凶在这里 //这函数干吗呢?猜测:如果成功地荡掉了键盘,就返回真 return true; } } break; case KeyEvent.KEYCODE_DEL: // Special handling of the delete key: if we currently are // composing text for the user, we want to modify that instead // of let the application to the delete itself. if (mComposing.length() > 0) { onKey(Keyboard.KEYCODE_DELETE, null); //所以,onkey定义中的事情才是软键盘的事件 return true; } break; case KeyEvent.KEYCODE_ENTER: // Let the underlying text editor always handle these. return false; default: // For all other keys, if we want to do transformations on // text being entered with a hard keyboard, we need to process // it and do the appropriate action. if (PROCESS_HARD_KEYS) { //这个是个废柴变量,因为在前面赋值了,永远是true if (keyCode == KeyEvent.KEYCODE_SPACE && (event.getMetaState()&KeyEvent.META_ALT_ON) != 0) { //为什么有这个按位与?因为这个META_ALT_ON就是用来按位与来判断是否按下alt //条件:alt+空格 // A silly example: in our input method, Alt+Space // is a shortcut for 'android' in lower case. InputConnection ic = getCurrentInputConnection(); if (ic != null) { // First, tell the editor that it is no longer in the // shift state, since we are consuming this. ic.clearMetaKeyStates(KeyEvent.META_ALT_ON);// 清除组合键状态,如果不清除,出来的字符就不是Android //由此可知,这些函数才是控制显示字符的,但貌似没那么简单 keyDownUp(KeyEvent.KEYCODE_A); keyDownUp(KeyEvent.KEYCODE_N); keyDownUp(KeyEvent.KEYCODE_D); keyDownUp(KeyEvent.KEYCODE_R); keyDownUp(KeyEvent.KEYCODE_O); keyDownUp(KeyEvent.KEYCODE_I); keyDownUp(KeyEvent.KEYCODE_D); // And we consume this event. return true; } } if (mPredictionOn && translateKeyDown(keyCode, event)) { return true; } } } return super.onKeyDown(keyCode, event); } /** * Use this to monitor key events being delivered to the application. * We get first crack at them, and can either resume them or let them * continue to the app. */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { // If we want to do transformations on text being entered with a hard // keyboard, we need to process the up events to update the meta key // state we are tracking. Log.i("mytest", "SoftKeyboard_onKeyUp"); if (PROCESS_HARD_KEYS) { //哈哈,判断是不在使用硬件输入 //要懂得,mete keys意味着shift和alt这类的键 if (mPredictionOn) { //处理matakey的释放 mMetaState = MetaKeyKeyListener.handleKeyUp(mMetaState, keyCode, event); } } //只有在一个键被放起时候执行,但经过测试,他不是执行输入的,仅仅是再输入之前做些事务, return super.onKeyUp(keyCode, event); } /** * Helper function to commit any text being composed in to the editor. */ private void commitTyped(InputConnection inputConnection) { Log.i("mytest", "SoftKeyboard_commitTyped"); if (mComposing.length() > 0) { inputConnection.commitText(mComposing, mComposing.length()); //后边的参数决定了光标的应有位置 mComposing.setLength(0); updateCandidates();//这两行联手,一般能造成候选栏置空与候选词条串置空的效果 } } /** * Helper to update the shift state of our keyboard based on the initial * editor state. */ private void updateShiftKeyState(EditorInfo attr) { //但是,这个函数每次输入一个字母都要执行 //用于在开始输入前切换大写 //它首先是判断是否输入视图存在,并且输入框要求有输入法,然后根据输入框的输入类型来获得是否需要大小写,最后定义在输入视图上。 //经测试,每当键盘刚出来的时候会有,每输入一个字符都会有这个函数的作用 Log.i("mytest", "SoftKeyboard_updateShiftKeyState"); //getKeyboard又是个可得私有变量的公有函数 //条件的含义是:当有字母键盘存在的时候 if (attr != null && mInputView != null && mQwertyKeyboard == mInputView.getKeyboard()) { int caps = 0; EditorInfo ei = getCurrentInputEditorInfo(); //获得当前输入框的信息?本.java中,大多数的attr参数于这个东西等同 //这个破inputtype类型是全0,一般不会有这种破类型 if (ei != null && ei.inputType != EditorInfo.TYPE_NULL) { caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType);//返回的东西不是光标位置,得到的是 //是否需要大写的判断,但是返回值是怎么弄的?? } mInputView.setShifted(mCapsLock || caps != 0); } } /** * Helper to determine if a given character code is alphabetic. */ private boolean isAlphabet(int code) { //看看是不是字母 Log.i("mytest", "SoftKeyboard_isAlphabet"); if (Character.isLetter(code)) { return true; } else { return false; } } /** * Helper to send a key down / key up pair to the current editor. */ private void keyDownUp(int keyEventCode) { Log.i("mytest", "SoftKeyboard_keyDownUp"); getCurrentInputConnection().sendKeyEvent( new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode)); //参见文档中KeyEvent getCurrentInputConnection().sendKeyEvent( new KeyEvent(KeyEvent.ACTION_UP, keyEventCode)); //明白了,这个函数是用来特殊输出的,就好像前面定义的“android”输出,但如果简单地从键盘输入字符,是不会经过这一步的 //一点都没错,强制输出,特殊输出,就这里 // keyDownUp(KeyEvent.KEYCODE_N); // keyDownUp(KeyEvent.KEYCODE_B); } /** * Helper to send a character to the editor as raw key events. */ private void sendKey(int keyCode) { //传入的参数是阿斯课码 //处理中断符的时候使用到了 Log.i("mytest", "SoftKeyboard_sendKey"); switch (keyCode) { case '\n': keyDownUp(KeyEvent.KEYCODE_ENTER); break; default: if (keyCode >= '0' && keyCode <= '9') { keyDownUp(keyCode - '0' + KeyEvent.KEYCODE_0); } else { getCurrentInputConnection().commitText(String.valueOf((char) keyCode), 1); } break; } } // Implementation of KeyboardViewListener // Implementation of KeyboardViewListener //你难道没看见这个类定义时候的接口吗?那个接口定义的监听函数就是为了监听这种On事件的,这就是软键盘按压事件 public void onKey(int primaryCode, int[] keyCodes) { Log.i("mytest", "SoftKeyboard_onKey"); //后面定义的函数 //当输入被中断符号中断 if (isWordSeparator(primaryCode)) { // Handle separator if (mComposing.length() > 0) { commitTyped(getCurrentInputConnection()); } sendKey(primaryCode); //提交完了输出之后,还必须要把这个特殊字符写上 updateShiftKeyState(getCurrentInputEditorInfo());//看看是否到了特殊的位置,需要改变大小写状态 } else if (primaryCode == Keyboard.KEYCODE_DELETE) { handleBackspace(); } else if (primaryCode == Keyboard.KEYCODE_SHIFT) { handleShift(); } else if (primaryCode == Keyboard.KEYCODE_CANCEL) { //左下角那个键,关闭 handleClose(); return; } else if (primaryCode == LatinKeyboardView.KEYCODE_OPTIONS) { //这个键,是这样的,前面的LatinKeyboardView这个类里面定义了KEYCODE_OPTIONS //用来描述长按左下角关闭键的代替。经测试,千真万确 // Show a menu or somethin' } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE && mInputView != null) { //就是显示着“abc”或者"123"的那个键 Keyboard current = mInputView.getKeyboard(); if (current == mSymbolsKeyboard || current == mSymbolsShiftedKeyboard) { current = mQwertyKeyboard; } else { current = mSymbolsKeyboard; } //改变键盘的根本操作,但是对于具体输入的是大写字母这件事情,还要等按下了之后在做定论 mInputView.setKeyboard(current); if (current == mSymbolsKeyboard) { current.setShifted(false); //测试,这里要是设置为true,打开之后只是shift键的绿点变亮,但是并没有变成另一个符号键盘 } } else { handleCharacter(primaryCode, keyCodes); //这就是处理真正的字符处理函数,不是那些其他的控制键 } } public void onText(CharSequence text) { //这也是接口类的触发的函数。什么时候响应,有待考证 Log.i("mytest", "SoftKeyboard_onText"); InputConnection ic = getCurrentInputConnection(); if (ic == null) return; ic.beginBatchEdit(); if (mComposing.length() > 0) { commitTyped(ic); } ic.commitText(text, 0); ic.endBatchEdit(); updateShiftKeyState(getCurrentInputEditorInfo());//看是否需要切换大小写 } /** * Update the list of available candidates from the current composing * text. This will need to be filled in by however you are determining * candidates. */ private void updateCandidates() {//此函数处理的是不允许从auto获取的情况,应该是大多数情况 Log.i("mytest", "SoftKeyboard_updateCandidates"); if (!mCompletionOn) { if (mComposing.length() > 0) { //mComposing记录着候选字符串之串,待考证 ArrayList<String> list = new ArrayList<String>(); list.add(mComposing.toString()); setSuggestions(list, true, true); } else { setSuggestions(null, false, false); } } } public void setSuggestions(List<String> suggestions, boolean completions, boolean typedWordValid) { //这第三个参数是前面函数调用的时候人为给的,没什么玄妙 Log.i("mytest", "SoftKeyboard_setSuggestions"); if (suggestions != null && suggestions.size() > 0) { setCandidatesViewShown(true); } else if (isExtractViewShown()) { setCandidatesViewShown(true); } if (mCandidateView != null) { mCandidateView.setSuggestions(suggestions, completions, typedWordValid); //就是改变了一下suggestion,在candidateView里面真正靠的是onDraw } } //删除一个字,用的就是他 private void handleBackspace() { Log.i("mytest", "SoftKeyboard_handleBackspace"); final int length = mComposing.length(); if (length > 1) {//就是在说等于1的时候 mComposing.delete(length - 1, length); getCurrentInputConnection().setComposingText(mComposing, 1); updateCandidates(); } else if (length > 0) { mComposing.setLength(0); getCurrentInputConnection().commitText("", 0); updateCandidates(); } else { keyDownUp(KeyEvent.KEYCODE_DEL); } updateShiftKeyState(getCurrentInputEditorInfo()); } private void handleShift() {//这才是大小写的切换,是正常切换(通过转换键) Log.i("mytest", "SoftKeyboard_handleShift"); if (mInputView == null) { return; } Keyboard currentKeyboard = mInputView.getKeyboard(); if (mQwertyKeyboard == currentKeyboard) { // Alphabet keyboard checkToggleCapsLock(); //只有当键盘是字母键盘的时候,需要检验锁(控制变幻频率,不能过快) mInputView.setShifted(mCapsLock || !mInputView.isShifted()); } else if (currentKeyboard == mSymbolsKeyboard) { mSymbolsKeyboard.setShifted(true); //所谓的setShift,仅仅指的是那个键盘的大小写键变化,经测试,只要android:code=-1就有这种绿点效果 mInputView.setKeyboard(mSymbolsShiftedKeyboard); mSymbolsShiftedKeyboard.setShifted(true); } else if (currentKeyboard == mSymbolsShiftedKeyboard) { mSymbolsShiftedKeyboard.setShifted(false); mInputView.setKeyboard(mSymbolsKeyboard); mSymbolsKeyboard.setShifted(false); } } private void handleCharacter(int primaryCode, int[] keyCodes) { //primayCode是键的阿斯课码值 Log.i("mytest", "SoftKeyboard_handleCharacter"); if (isInputViewShown()) { if (mInputView.isShifted()) { primaryCode = Character.toUpperCase(primaryCode); //这才真正把这个字符变成了大写的效果,经测试,没有就不行 //把键盘换成大写的了还不够,那只是从View上解决了问题,一定要这样一句才行 } } if (isAlphabet(primaryCode) && mPredictionOn) { //输入的是个字母,而且允许候选栏显示 mComposing.append((char) primaryCode); //append(添加)就是把当前的输入的一个字符放到mComposing里面来 getCurrentInputConnection().setComposingText(mComposing, 1);//在输入目标中也显示最新得到的mComposing. updateShiftKeyState(getCurrentInputEditorInfo()); //每当输入完结,都要检验是否需要变到大写 updateCandidates(); } else { //比如说当输入的是“‘”这个符号的时候,就会掉用这个 //结果就是remove掉所有编辑中的字符,第二个参数的正负,决定着 //光标位置的不同 getCurrentInputConnection().commitText( String.valueOf((char) primaryCode), 1); } } private void handleClose() { Log.i("mytest", "SoftKeyboard_handleClose"); //关闭键盘件的作用就在这里,左下角那个.,记住!!!!!左下角那个,不是弯钩键!!!! commitTyped(getCurrentInputConnection()); requestHideSelf(0); //关掉输入法的区域,这才是关闭的王道.似乎这句包含了上面那句的作用(测试结果) mInputView.closing(); //这个函数不懂什么意思待问?? 哪里都测试,哪里都没有用处?? } private void checkToggleCapsLock() { Log.i("mytest", "SoftKeyboard_checkToggleCapsLock"); long now = System.currentTimeMillis();//记录上次变幻的时间 if (mLastShiftTime + 800 > now) {//不允许频繁地换大小写? mCapsLock = !mCapsLock; mLastShiftTime = 0; } else { mLastShiftTime = now; } } private String getWordSeparators() { Log.i("mytest", "SoftKeyboard_getWordSeparators"); return mWordSeparators; } public boolean isWordSeparator(int code) { Log.i("mytest", "SoftKeyboard_isWordSeparator"); String separators = getWordSeparators();//检查所属入的字符有没有在这些字符里面 return separators.contains(String.valueOf((char)code)); } public void pickDefaultCandidate() { Log.i("mytest", "SoftKeyboard_pickDefaultCandidate"); pickSuggestionManually(0); } public void pickSuggestionManually(int index) { Log.i("mytest", "SoftKeyboard_pickSuggestionManually"); if (mCompletionOn && mCompletions != null && index >= 0 && index < mCompletions.length) { CompletionInfo ci = mCompletions[index]; getCurrentInputConnection().commitCompletion(ci); if (mCandidateView != null) { mCandidateView.clear(); } updateShiftKeyState(getCurrentInputEditorInfo()); } else if (mComposing.length() > 0) { // If we were generating candidate suggestions for the current // text, we would commit one of them here. But for this sample, // we will just commit the current text. commitTyped(getCurrentInputConnection()); } } //着下面6个函数,完全是因为声明了那个接口类,所以必须要包含这几个函数,还有上面的几个函数,但是实际上这些函数可以没有意义 public void swipeRight() { Log.i("mytest", "SoftKeyboard_swipeRight"); if (mCompletionOn) { pickDefaultCandidate(); } } public void swipeLeft() { Log.i("mytest", "SoftKeyboard_swipeLeft"); handleBackspace(); } public void swipeDown() { Log.i("mytest", "SoftKeyboard_swipeDown"); handleClose(); } public void swipeUp() { Log.i("mytest", "SoftKeyboard_swipeUp"); } public void onPress(int primaryCode) { Log.i("mytest", "SoftKeyboard_onPress"); } public void onRelease(int primaryCode) { Log.i("mytest", "SoftKeyboard_onRelease"); } }
参考文献:
android sdk中 softkeyboard的自己解析(1)
android sdk中 softkeyboard的自己解析(2)
android sdk中 softkeyboard的自己解析(3)
android sdk中 softkeyboard的自己解析(4)