Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android編程入門 >> 寫一個Android輸入法02——候選窗、轉換

寫一個Android輸入法02——候選窗、轉換

編輯:Android編程入門

上一篇

上一篇介紹了完成Android輸入法的最小化步驟,它只能將按鍵對應的字符上屏。一般的東亞語言都有一個轉換的過程,比如漢語輸入拼音,需要由拼音轉成漢字再上屏。本文將在前文基礎上加入完成轉換過程所必需的候選窗。本文代碼可參見https://github.com/palanceli/AndroidXXIME/tree/v2。

如下圖所示,用紅框框出來的窗體是候選窗,其內的字符創叫做候選串,點擊候選窗使之進入輸入控件叫做上屏。沒有輸入的時候隱藏候選窗,當輸入字串還未上屏時顯示候選窗:

引入候選窗需要完成兩個步驟:

一、創建CandidateView,該窗口需要覆蓋如下兩個方法,已完成自繪:

  • onDraw(Canvas canvas);
  • onMeasure(int widthMeasureSpec, int heightMeasureSpec);

二、覆蓋AndroidXXIME類的如下兩個方法:

  • onCreateCandidateView();

  在該方法中創建CandidateView。

  • onKey(int  primaryCode, int [] keyCodes);

  在該方法中響應按鍵消息,如:當按下字母鍵,則展現候選窗以及候選字串;當按下空格,則上屏候選字串,等等。    

 


 

創建CandidateView

    public CandidateView(Context context) {
        super(context);
        Log.d(this.getClass().toString(), "CandidateView: ");

        // 設置前景、背景色、字體、字號
        Resources r = context.getResources();

        setBackgroundColor(getResources().getColor(R.color.candidate_background, null));

        mColorNormal = r.getColor(R.color.candidate_normal, null);
        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);

        setWillNotDraw(false);  // 覆蓋了onDraw函數應清除該標記
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d(this.getClass().toString(), "onMeasure: ");
        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSize = MeasureSpec.getSize(widthMeasureSpec);

        int measuredWidth = resolveSize(50, widthMeasureSpec);

        final int desiredHeight = ((int)mPaint.getTextSize()) + mVerticalPadding;

        // 系統會根據返回值確定窗體的大小
        setMeasuredDimension(measuredWidth, resolveSize(desiredHeight, heightMeasureSpec));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Log.d(this.getClass().toString(), "onDraw: ");
        super.onDraw(canvas);

        if (mSuggestions == null)
            return;

        // 依次繪制每組候選字串
        int x = 0;
        final int count = mSuggestions.size();
        final int height = getHeight();
        final int y = (int) (((height - mPaint.getTextSize()) / 2) - mPaint.ascent());

        for (int i = 0; i < count; i++) {
            String suggestion = mSuggestions.get(i);
            float textWidth = mPaint.measureText(suggestion);
            final int wordWidth = (int) textWidth + X_GAP * 2;

            canvas.drawText(suggestion, x + X_GAP, y, mPaint);
            x += wordWidth;
        }
    }

    public void setSuggestions(List<String> suggestions) {
        // 設置候選字串列表
        if (suggestions != null) {
            mSuggestions = new ArrayList<String>(suggestions);
        }
        invalidate();
        requestLayout();
    }

}

覆蓋onCreateCandidateView()方法

該方法會在每次輸入法被呼出的時候調用,如函數名所示,在這裡創建候選窗口。

public class AndroidXXIME extends InputMethodService
        implements KeyboardView.OnKeyboardActionListener {
……

    @Override public View onCreateCandidatesView(){
        Log.d(this.getClass().toString(), "onCreateCandidatesView: ");
        candidateView = new CandidateView(this);
        return candidateView;
    }
……
}

覆蓋onKey(int primaryCode, int [] keyCodes)方法

public class AndroidXXIME extends InputMethodService
        implements KeyboardView.OnKeyboardActionListener {
……
    @Override
    public void onKey(int primaryCode, int[] keyCodes) {
        InputConnection ic = getCurrentInputConnection();
        playClick(primaryCode);
        switch(primaryCode){
            case Keyboard.KEYCODE_DELETE :
                // 如果收到的是DELETE鍵,則刪除光標前的一個字符
                ic.deleteSurroundingText(1, 0);
                break;
            case Keyboard.KEYCODE_DONE:
                // 如果收到的是DONE鍵,則執行回車
                ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
                break;
            default:
                char code = (char)primaryCode;
                if(code == ' '){ // 如果收到的是空格
                    if(m_composeString.length() > 0) {  // 如果有寫作串,則將首個候選提交上屏
                        ic.commitText(m_composeString, m_composeString.length());
                        m_composeString.setLength(0);
                    }else{                              // 如果沒有寫作串,則直接將空格上屏
                        ic.commitText(" ", 1);
                    }
                }else {          // 否則,將字符計入寫作串
                    m_composeString.append(code);
                    ic.setComposingText(m_composeString, 1);
                }
                updateCandidates();
        }
    }
}

在updateCandidates()函數中向CandidateView塞入候選字串列表,並觸發該窗口更新。


 

當在系統“語言和輸入法”-“更改鍵盤”中選擇輸入法時,

系統會調用該輸入法InputMethodService的如下方法:

  • onCreate()
  • onInitializeInterface()  可以在該方法中完成與輸入法相關的初始化操作,比如加載詞庫。
  • onStartInput()    每次切換輸入焦點的時候,都會調用該方法,在這裡可以完成和會話相關的初始化操作,後面還會介紹。

 

當一個輸入控件獲得焦點,呼出輸入法,到它失去焦點,這期間成為一次會話。當一個會話開始時,系統會調用輸入法InputMethodService的如下方法:

  • onStartInput()    負責會話相關的初始化工作。輸入法要負責在會話切換時,清除上次會話的中間數據,以防止前一個會話的中間數據竄入下個會話。這和Windows平台下的輸入法有很大區別,在Windows下,輸入法一個DLL,它依附在切出輸入法的進程中,因此,每個輸入法進程保存各自的輸入法上下文,如果需要在進程間共享數據(比如詞庫),則需要采用共享內存機制;而在Android平台下,輸入法是一個獨立的進程,所有的數據僅在該進程中保存一份,此時則需要考慮如何隔離不同進程間的私有數據,比如前一個進程輸入一半但未上屏的數據,切到另一個進程或輸入控件後,就應該清除掉。該回調函數用來做這類工作。
  • onCreateInputView()    創建輸入法鍵盤布局。
  • onCreateCandidatesView()   創建候選窗。

完成以上步驟之後,輸入法就多出了候選窗口,下圖中淺藍色窗體既是:

在處理上還是很簡陋,比如退格還不支持刪除輸入串,還不支持點擊上屏,等等。這些屬於業務邏輯的細節了,可以慢慢精耕細作。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved