Android input method official SoftKeyboard sample analysis

1 Functional requirements

  • There is a text edit box in the main interface of the touch screen device, and a numeric keyboard is fixed at the bottom area. In addition to the number keys, the keyboard also has the functions of * and # keys;
  • Provide a custom digital input method, generate the apk installation package file, and embed it in the img image file.

2 Design ideas

  • Refer to the example of SoftKeyboard in the Sample in the Android SDK directory, refer to the demo demonstration, and imitate the implementation of IME (Input Method Editor);
  • After the platform version Android 1.5, SDK version 3, and version name CUPCAKE version, Android will open its IMF (Input Method Framework), allowing us to develop our own input method. The best starting example for developing an input method is SoftKeyboard. Although this example is just a simple input of English and numbers, it itself can be regarded as a very clear and complete input method implementation.

3 Source code analysis

3.1 Life cycle

inputmethod_lifecycle_image.png

inputmethod_lifecycle_image.png

Next, we introduce some of the more important life cycle methods
  
onCreate ()
input method creation process, first call this method for the main initialization of the input method components, this method will only be called once.
  
onCreateInputView()
This method is called when it is necessary to generate a view for creating an input. It will be called when your input method is displayed for the first time, and every time you need to recreate the input method due to configuration changes.
  
onCreateCandidateView()
This method is called when it needs to generate a view for displaying candidate keys, similar to the onCreateInputView() method.
  
onStartInputView()
is called when the input view is being displayed and input is started on the new edit box. This will always be called after the onStartInput() method, allowing you to make general settings here (set the space bar Icon, and refresh the entire keyboard view) and only the settings for a specific view (apply the selected keyboard to the input view). You should ensure that the onCreateInputView method is called before the method is called.
  
onCurrentInputMethodSubtypeChanged()
is called when the subtype changes. Set the space bar Icon and refresh the entire keyboard view.
  
onFinishInput() After the
user finishes editing the field, this method will be called. We can use it to reset our state. For example, clear the current input text and candidate content, and control the visibility of the candidate key display area. It is hidden by default.
  
onDestroy()
is called when the input method service ends. It is called only once, and the resource release is done in the method.

3.2 Implementation principle

  1. The classes related to the custom input method are all placed under the com.example.android.softkeyboard package
    Custom input method related classes
  2. Declare input method components in AndroidMainifest.xml
    Declare input method services in the manifest file and apply for binding input method permissions.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
        package="com.example.android.softkeyboard">
    <application android:label="@string/ime_name">
        <service android:name="SoftKeyboard"
                android:permission="android.permission.BIND_INPUT_METHOD">
            <intent-filter>
                <action android:name="android.view.InputMethod" />
            </intent-filter>
            <meta-data android:name="android.view.im" android:resource="@xml/method" />
        </service>

        <activity android:name=".ImePreferences" android:label="@string/settings_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
            </intent-filter>
        </activity>

    </application>
</manifest>
  1. CandidateView.java The
    CandidateView class is a view class for candidate keys, which can be directly inherited from the View class. When the user enters characters, it should be able to display certain prompts based on the characters, such as pinyin homophones, associative characters, and the like.
    The main methods in the class are as follows
    /**
     * 是设置宿主输入法,用于回到服务的连接以与文本字段进行通信。
     *
     * @param listener
     */
    public void setService(SoftKeyboard listener) {
    
    
        mService = listener;
    }
    /**
     * 设置候选词,之后进行绘制候选键视图。
     * @param suggestions
     * @param completions
     * @param typedWordValid
     */
    public void setSuggestions(List<String> suggestions, boolean completions,
                               boolean typedWordValid) {
    
    
        clear();
        if (suggestions != null) {
    
    
            mSuggestions = new ArrayList<String>(suggestions);
        }
        mTypedWordValid = typedWordValid;
        scrollTo(0, 0);
        mTargetScrollX = 0;
        // Compute the total width
        onDraw(null);
        invalidate();
        requestLayout();
    }
    /**
     * 移除高亮显示
     */
    private void removeHighlight() {
    
    
        mTouchX = OUT_OF_BOUNDS;
        invalidate();
    }
  1. The LatinKeyboard.java
    soft keyboard class directly inherits the Keyboard class to implement a keyboard for inputting Latin.
    The main private variables in the class are as follows
   /**
    * 存储更改模式键的当前状态。它的宽度将会动态更新,当变为不可见时来匹配这个区域。
    */
   private Key mModeChangeKey;
   /**
    * 存储语言切换键的当前状态(例如 地球键),当返回true时就应该可见。当这个key变得不可见时,它的宽度将会缩小为零。
    */
   private Key mLanguageSwitchKey;
   /**
    * 当mLanguageSwitchKey可见时,存储大小和mModeChangeKey的其他信息。
    * 这应该是不可变的,当改变mLanguageSwitchKey的可见性时,仅用作参考大小。
    */
   private Key mSavedModeChangeKey;
   /**
    * 当mLanguageSwitchKey可见时,存储大小和mLanguageSwitchKey的其他信息。
    * 这应该是不可变的,当改变mLanguageSwitchKey的可见性时,仅用作参考大小。
    */
   private Key mSavedLanguageSwitchKey;

The main methods in the class are as follows

  /**
   * 动态改变语言切换键的可见性(例如 全球键)
   *
   * @param visible
   */
  void setLanguageSwitchKeyVisibility(boolean visible) {
    
    
      if (visible) {
    
    
          // 语言切换键应该显示。使用保存的布局来恢复模式变化键和语言切换键的大小。
          mModeChangeKey.width = mSavedModeChangeKey.width;
          mModeChangeKey.x = mSavedModeChangeKey.x;
          mLanguageSwitchKey.width = mSavedLanguageSwitchKey.width;
          mLanguageSwitchKey.icon = mSavedLanguageSwitchKey.icon;
          mLanguageSwitchKey.iconPreview = mSavedLanguageSwitchKey.iconPreview;
      } else {
    
    
          // 语言切换键应该隐藏。改变模式变化键的宽度去填充语言键的空间,用户不会看到任何奇怪的间隙。
          mModeChangeKey.width = mSavedModeChangeKey.width + mSavedLanguageSwitchKey.width;
          mLanguageSwitchKey.width = 0;
          mLanguageSwitchKey.icon = null;
          mLanguageSwitchKey.iconPreview = null;
      }
  }
    /**
     * 查看当前编辑器提供的IME选项,在键盘Enter键设置合适的标签(如果有一个)。
     *
     * @param res
     * @param options
     */
    void setImeOptions(Resources res, int options) {
    
    
        if (mEnterKey == null) {
    
    
            return;
        }

        switch (options & (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION)) {
    
    
            case EditorInfo.IME_ACTION_GO:
                mEnterKey.iconPreview = null;
                mEnterKey.icon = null;
                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:
                // 设置回车键Icon
                mEnterKey.icon = res.getDrawable(R.drawable.sym_keyboard_return);
                mEnterKey.label = null;
                break;
        }
    }
    /**
     * 它还定义了一个内部类,叫做LatinKey,它直接继承了Key,来定义一个单独的键,
     * 它唯一重载的函数是isInside(int x , int y ),用来判断一个坐标是否在该键内。
     * 它重载为判断该键是否是CANCEL键,如果是则把Y坐标减少10px,
     * 按照他的解释是用来还原这个可以关掉键盘的键的目标区域。
     */
    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);
        }

        /**
         * 重写此方法,以便我们可以减少关闭键盘键的目标区域。
         *
         * @param x
         * @param y
         */
        @Override
        public boolean isInside(int x, int y) {
    
    
            return super.isInside(x, codes[0] == KEYCODE_CANCEL ? y - 10 : y);
        }
    }
  1. LatinKeyboardView.java
    is just a concept because the LatinKeyboard class defined earlier is just a concept, and a UI cannot be instantiated, so it needs to be drawn with the help of a View class. This class simply inherits the KeyboardView class, and then overloads a method, which is the onLongPress method.
  2. SoftKeyboard.java
    is an example of writing a soft keyboard input method. This code focuses on simplicity rather than completeness, so it should never be regarded as a complete soft keyboard. Its purpose is to provide a basic example of how to start writing input methods , And enrich it when appropriate. IME is actually a Service. When the user clicks the text edit box to input characters, it will be executed in the order of the input method life cycle. The entire input method includes when to build, when to display the input method, and how to communicate with the text box, and so on. The above several .java files are all for this class. In general, an input method needs an input view, a candidate word view, and a link to the application.
    The main methods in the class are as follows, executed in order from top to bottom
    /**
     * 输入法组件的主要初始化。一定要调用父类InputMethodService的onCreate()方法。
     * 用来创建输入法管理对象和字符串分隔符的初始化。
     */
    @Override
    public void onCreate() {
    
    
        super.onCreate();
        mInputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
        mWordSeparators = getResources().getString(R.string.word_separators);
    }
    /**
     * 这是你可以进行所有UI初始化的地方。在创建和任何配置更改后将调用这个方法。
     * 在这里对Keyboard进行了初始化,从XML文件中读取软键盘信息,封装进Keyboard对象。
     */
    @Override
    public void onInitializeInterface() {
    
    
        if (mQwertyKeyboard != null) {
    
    
            // 重新创建键盘后,可能会发生配置更改,因此,如果可用空间已更改,我们需要能够重新构建键盘。
            int displayWidth = getMaxWidth();
            if (displayWidth == mLastDisplayWidth) return;
            mLastDisplayWidth = displayWidth;
        }
        mQwertyKeyboard = new LatinKeyboard(this, R.xml.qwerty);
        mSymbolsKeyboard = new LatinKeyboard(this, R.xml.symbols);
        mSymbolsShiftedKeyboard = new LatinKeyboard(this, R.xml.symbols_shift);
    }
    /**
     * 这是我们初始化输入方法以开始在应用程序上进行操作的要点。
     * 在这里,我们已经绑定到客户端,并且现在正在接收有关我们的编辑对象的所有详细信息。
     *
     * @param attribute
     * @param restarting
     */
    @Override
    public void onStartInput(EditorInfo attribute, boolean restarting) {
    
    
        super.onStartInput(attribute, restarting);

        // 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.
        mComposing.setLength(0);
        updateCandidates();

        if (!restarting) {
    
    
            // Clear shift states.
            mMetaState = 0;
        }

        mPredictionOn = false;
        mCompletionOn = false;
        mCompletions = null;

        // We are now going to initialize our state based on the type of
        // text being edited.
        switch (attribute.inputType & InputType.TYPE_MASK_CLASS) {
    
    
            case InputType.TYPE_CLASS_NUMBER:
            case InputType.TYPE_CLASS_DATETIME:
                // Numbers and dates default to the symbols keyboard, with
                // no extra features.
                mCurKeyboard = mSymbolsKeyboard;
                break;

            case InputType.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 InputType.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 & InputType.TYPE_MASK_VARIATION;
                if (variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
                        variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
    
    
                    // Do not display predictions / what the user is typing
                    // when they are entering a password.
                    mPredictionOn = false;
                }

                if (variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
                        || variation == InputType.TYPE_TEXT_VARIATION_URI
                        || variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
    
    
                    // Our predictions are not useful for e-mail addresses
                    // or URIs.
                    mPredictionOn = false;
                }

                if ((attribute.inputType & InputType.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;
                    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);
    }
    /**
     * 在用户输入的区域要显示时,调用这个方法,输入法首次显示时,或者配置信息改变时,该方法就会被执行。
     * 在该方法中,对InputView进行初始化:读取布局文件信息,设置onKeyboardActionListener,并初始设置 keyboard。
     */
    @Override
    public View onCreateInputView() {
    
    
        mInputView = (LatinKeyboardView) getLayoutInflater().inflate(
                R.layout.input, null);
        mInputView.setOnKeyboardActionListener(this);
        setLatinKeyboard(mQwertyKeyboard);
        return mInputView;
    }
    /**
     * 在需要显示候选词汇的视图时,调用这个方法。和onCreateInputView()这个方法类似。
     * 在这个方法中,对CandidateView进行初始化。
     */
    @Override
    public View onCreateCandidatesView() {
    
    
        mCandidateView = new CandidateView(this);
        mCandidateView.setService(this);
        return mCandidateView;
    }
    /**
     * 当显示输入视图并且在新的文本编辑框上开始输入时调用,将InputView和当前Keyboard重新关联起来。
     * 这将始终在onStartInput()之后调用,允许你在此处进行常规设置,而在此处仅进行特定视图的设置。
     * 你可以保证在调用此函数之前一定时间会调用onCreateInputView()。
     *
     * @param attribute  对要编辑的文本类型的描述
     * @param restarting 如果我们要像以前一样在相同的文本字段上重新开始输入,请设置为true。
     */
    @Override
    public void onStartInputView(EditorInfo attribute, boolean restarting) {
    
    
        super.onStartInputView(attribute, restarting);
        // Apply the selected keyboard to the input view.
        setLatinKeyboard(mCurKeyboard);
        mInputView.closing();
        final InputMethodSubtype subtype = mInputMethodManager.getCurrentInputMethodSubtype();
        mInputView.setSubtypeOnSpaceKey(subtype);
    }

Among the above six methods, onCreateInputView() and onCreateCandidatesView() are executed only once during initialization, unless there is a change in configuration information.

4 Sample screenshot

Sample screenshot

Guess you like

Origin blog.csdn.net/discode/article/details/115031229
Recommended