输入法是全屏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,依然显示在底部,比较费解。
那么尝试上移mInputFrame,
怎么移?mInputFrame本身是Framelayout,而且位置在android资源文件中写死了,通过代码动态移动?
想过WindowManager的updateLayout,可是布局参数呢?
一般三方自定义输入法时,需要继承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移动一点距离。
-------------------------------------------------------------------------------------------------------------------------
接下来,分析下输入法调用栈,
首先,窗口获得焦距后,才会调用输入法,这是起点,位于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 /* missingMethodFlags */,
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过程,不在此赘述;