本篇爲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
- }
- }
- }
- 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);
- }
- }
- }
- 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();
- }
- }