【Android】记录一次输入法位置修改思路

【Android】记录一次输入法位置修改思路,第1张

【Android】记录一次输入法位置修改思路

输入法是dialog,还是全屏dialog,且不会被dismiss,显示与隐藏系统调用方法为showWindow(true)和hide(),以下是输入法Window创建,SoftInputWindow继承Dialog,这个不必多说,

mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
                WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
        mWindow.getWindow().getAttributes().setFitInsetsTypes(statusBars() | navigationBars());
        mWindow.getWindow().getAttributes().setFitInsetsSides(Side.all() & ~Side.BOTTOM);
        mWindow.getWindow().getAttributes().setFitInsetsIgnoringVisibility(true);

至于全屏的依据,可以看SoftInputWindow initDockWindow方法,

看,写入了WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;

这个Flag啥意思呢?就是将窗口放置在整个屏幕内,忽略父容器的限制,

private void initDockWindow() {

       WindowManager.LayoutParams lp = getWindow().getAttributes();

       lp.type = mWindowType;
       lp.setTitle(mName);

       lp.gravity = mGravity;
       updateWidthHeight(lp);

       getWindow().setAttributes(lp);

       int windowSetFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
       int windowModFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
               WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
               WindowManager.LayoutParams.FLAG_DIM_BEHIND;

       if (!mTakesFocus) {
           windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
       } else {
           windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
           windowModFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
       }
       getWindow().setFlags(windowSetFlags, windowModFlags);
   }


如果要输入法上移,必须调整dialog中的布局,

在InputMethodService方法中,initView如下,

mWindow.setContentView(mRootView),关键,

mWindow是SoftInputWindow,继承Dialog,因此这是设置视角;

如何,一些成员的初始化在此进行,如mInputframe和mCandidatesframe,这是啥呢?在setInputView中回调onCreateView方法,拿到三方输入法自定义的View,

所以显示在我们面前的视野就是mInputframe;

void initViews() {

       mInitialized = false;
       mViewsCreated = false;
       mShowInputRequested = false;
       mShowInputFlags =

 0;
       mThemeAttrs = obtainStyledAttributes(android.R.styleable.InputMethodService);
       mRootView = mInflater.inflate(
               com.android.internal.R.layout.input_method, null);
       mWindow.setContentView(mRootView);
       mRootView.getViewTreeObserver().removeonComputeInternalInsetsListener(mInsetsComputer);
       mRootView.getViewTreeObserver().addonComputeInternalInsetsListener(mInsetsComputer);
       if (Settings.Global.getInt(getContentResolver(),
               Settings.Global.FANCY_IME_ANIMATIONS, 0) != 0) {
           mWindow.getWindow().setWindowAnimations(
                   com.android.internal.R.style.Animation_InputMethodFancy);
       }
       mFullscreenArea = mRootView.findViewById(com.android.internal.R.id.fullscreenArea);
       mExtractViewHidden = false;
       mExtractframe = mRootView.findViewById(android.R.id.extractArea);
       mExtractView = null;
       mExtractEditText = null;
       mExtractAccessories = null;
       mExtractAction = null;
       mFullscreenApplied = false;
       mCandidatesframe = mRootView.findViewById(android.R.id.candidatesArea);
       mInputframe = mRootView.findViewById(android.R.id.inputArea);
       mInputView = null;
       mIsInputViewShown = false;
       mExtractframe.setVisibility(View.GONE);
       mCandidatesVisibility = getCandidatesHiddenVisibility();
       mCandidatesframe.setVisibility(mCandidatesVisibility);
       mInputframe.setVisibility(View.GONE);
   }

因为要上移输入法,尝试对window的attribute参数下手,失败告终,因为设置y参数对全屏输入法无效,由于service的onCreate方法new SoftInputWindow时,传入Gravity和Bottom,输入法就默认显示底部?调试时改为Top,依然显示在底部,比较费解。

FullscreenArea = mRootView.findViewById(com.android.internal.R.id.fullscreenArea);具体资源看不到,这个应该覆盖了整个屏幕

综上所述,输入法类似以下布局,暂不讨论屏幕旋转之内,这个在SoftInputWindow会更新Gravity,

处理上移问题,就要上移mInputframe,

怎么移?mInputframe本身是framelayout,而且位置在android资源文件中写死了,通过代码动态移动?

想过WindowManager的updateLayout,可是布局参数呢?

或者直接调用setY,可问题是这样的效果类似动画,原View并没有移动,也就导致点击事件不可用,

或者view的startMoveTask方法?感觉都不行,

一般三方自定义输入法时,需要继承InputMethodService类,这是个标准,

重写其中onCreateVIew方法,也就传入自定义输入法布局,系统通过如下方法回调,

public void updateInputViewShown() {
       boolean isShown = mShowInputRequested && onevaluateInputViewShown();
       if (mIsInputViewShown != isShown && mDecorViewVisible) {
           mIsInputViewShown = isShown;
           mInputframe.setVisibility(isShown ? View.VISIBLE : View.GONE);
           if (mInputView == null) {
               initialize();
               View v = onCreateInputView();
               if (v != null) {
                   setInputView(v);
               }
           }
       }
   }

之后,通过setInputView方法设置显示在用户面前的视野,可以看到,视野添加到mInputframe中,对于另外一个方法setCandidatesView,就是候选视图,暂不多说,候选嘛。

public void setInputView(View view) {
       mInputframe.removeAllViews();
       mInputframe.addView(view, new frameLayout.LayoutParams(
               ViewGroup.LayoutParams.MATCH_PARENT,
               ViewGroup.LayoutParams.WRAP_CONTENT));
       mInputView = view;
   }

所以我们要挪动输入法,可以在这里做文章,即添加一点参数,让mInputView上移一点,

frameLayout.LayoutParams提供了bottomMargin参数,即让咱们的mInputView相对bottom移动一点距离,单位是像素不是dp,

-------------------------------------------------------------------------------------------------------------------------

接下来,分析下输入法调用栈,

首先,窗口获得焦距后,才会调用输入法,这是起点,位于ViewRootImpl.handleWindowFocusChanged方法,

跟进其中,

// Note: must be done after the focus change callbacks,
            // so all of the view state is set up correctly.
            mImeFocusController.onPostWindowFocus(mView.findFocus(), hasWindowFocus,
                    mWindowAttributes);

跟进,

immDelegate.startInputAsynconWindowFocusGain(viewForWindowFocus,
                windowAttribute.softInputMode, windowAttribute.flags, forceFocus);

跟进,

                    mService.startInputOrWindowGainedFocus(
                            startInputReason, mClient,
                            focusedView.getWindowToken(), startInputFlags, softInputMode,
                            windowFlags,
                            null,
                            null,
                            0 ,
                            mCurRootView.mContext.getApplicationInfo().targetSdkVersion);

跟进,

 result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client,
                        windowToken, startInputFlags, softInputMode, windowFlags, attribute,
                        inputContext, missingMethods, unverifiedTargetSdkVersion, userId);

跟进,

showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
                            SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);

发Message到IInputMethodWrapper,

case DO_SHOW_SOFT_INPUT: {
                final SomeArgs args = (SomeArgs)msg.obj;
                inputMethod.showSoftInputWithToken(
                        msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
                args.recycle();
                return;
            }

跟进,

public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
                IBinder showInputToken) {
            mSystemCallingShowSoftInput = true;
            mCurShowInputToken = showInputToken;
            showSoftInput(flags, resultReceiver);
            mCurShowInputToken = null;
            mSystemCallingShowSoftInput = false;
        }

跟进showSoftInput,

 try {
                        showWindow(true);
                    } catch (BadTokenException e) {

跟进showWinodw,

先准备InputVIew,也就是在onCreateInputVIew中返回的View

startViews(prepareWindow(showInput));

跟进prepareWindow,

private boolean prepareWindow(boolean showInput) {
        boolean doShowInput = false;
        mDecorViewVisible = true;
        if (!mShowInputRequested && mInputStarted && showInput) {
            doShowInput = true;
            mShowInputRequested = true;
        }

        if (DEBUG) Log.v(TAG, "showWindow: updating UI");
        initialize();
        updateFullscreenMode();
        updateInputViewShown();

        if (!mViewsCreated) {
            mViewsCreated = true;
            initialize();
            if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView");
            View v = onCreateCandidatesView();
            if (DEBUG) Log.v(TAG, "showWindow: candidates=" + v);
            if (v != null) {
                setCandidatesView(v);
            }
        }
        return doShowInput;
    }

跟进updateInputViewShow,

 public void updateInputViewShown() {
        boolean isShown = mShowInputRequested && onevaluateInputViewShown();
        if (mIsInputViewShown != isShown && mDecorViewVisible) {
            mIsInputViewShown = isShown;
            mInputframe.setVisibility(isShown ? View.VISIBLE : View.GONE);
            if (mInputView == null) {
                initialize();
                View v = onCreateInputView();
                if (v != null) {
                    setInputView(v);
                }
            }
        }
    }
    

ok,这里回调了onCreateInputVIew,接下来就是dialog的show过程,不在此赘述;

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5434565.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-11
下一篇 2022-12-11

发表评论

登录后才能评论

评论列表(0条)

保存