本文討論以下幾個方面的問題以及解決辦法
1,Edittext,獲取焦點但是禁止軟鍵盤的彈出,
2,光標的產生原理
3,activity是如何在輸入法彈出後調整窗口大小的
1.Edittext,獲取焦點但是禁止軟鍵盤的彈出
Android api21 放開了TextView的setShowSoftInputOnFocus方法可以設置在獲取焦點的時候不顯示軟鍵盤,但是在Android api21以下該方法是隱藏的
api 21以上
/**
* Sets whether the soft input method will be made visible when this
* TextView gets focused. The default is true.
*/
@android.view.RemotableViewMethod
public final void setShowSoftInputOnFocus(boolean show) {
createEditorIfNeeded();
mEditor.mShowSoftInputOnFocus = show;
}
api21以下
/**
* Sets whether the soft input method will be made visible when this
* TextView gets focused. The default is true.
* @hide
*/
@android.view.RemotableViewMethod
public final void setShowSoftInputOnFocus(boolean show) {
createEditorIfNeeded();
mEditor.mShowSoftInputOnFocus = show;
}
因此解決辦法是當api小於21的時候使用反射調用setShowSoftInputOnFocus方法
如下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
editText.setShowSoftInputOnFocus(false);
}else {
//這裏採用反射調用被隱藏方法
setShowSoftInputOnFocus(editText,false);
}
private void setShowSoftInputOnFocus(EditText editText,boolean show) {
try {
Class<EditText> cls = EditText.class;
Method setShowSoftInputOnFocus;
setShowSoftInputOnFocus = cls.getMethod("setShowSoftInputOnFocus",
boolean.class);
setShowSoftInputOnFocus.setAccessible(true);
setShowSoftInputOnFocus.invoke(editText, show);
} catch (Exception e) {
e.printStackTrace();
}
}
2.光標的產生原理
是在textView的繪製過程中,如果當前textView是Edittext多繪製一個drawable 再每500ms刷新一次繪製流程 造成光標閃爍的效果
在textView的ondraw()方法中有如下代碼
Path highlight = getUpdatedHighlightPath();
if (mEditor != null) {
mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
} else {
layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
}
判斷當前的mEditor是否爲空 不爲空則調用他的ondraw方法,
這裏先看下 Editor類的介紹
/**
* Helper class used by TextView to handle editable text views.
*
* @hide
*/
public class Editor {
這個是TextView操縱可編輯Text視圖的工具類(可編輯text視圖可不就是Edittext)
再看看這個mEditor初始化
/**
* An Editor should be created as soon as any of the editable-specific fields (grouped
* inside the Editor object) is assigned to a non-default value.
* This method will create the Editor if needed.
*
* A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
* have a null Editor, unlike an EditText. Inconsistent in-between states will have an
* Editor for backward compatibility, as soon as one of these fields is assigned.
*
* Also note that for performance reasons, the mEditor is created when needed, but not
* reset when no more edit-specific fields are needed.
*/
private void createEditorIfNeeded() {
if (mEditor == null) {
mEditor = new Editor(this);
}
}
也就是說只有當可編輯的特殊的字段需要的時候但是未初始化的時候 mEditor便會被創建,但是對於標準的textview 如buttons不會被調用 只有Edittext纔會被調用
Edittext的調用流程如下:
TextView初始化調用->setText(text, bufferType);->Edittext重寫setText如下:
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, BufferType.EDITABLE);
}
->調用createEditorIfNeeded()方法
setText()方法中有如下代碼
...
if (type == BufferType.EDITABLE || getKeyListener() != null
|| needEditableForNotification) {
//此處type == BufferType.EDITABLE所以會調用 createEditorIfNeeded();
createEditorIfNeeded();
mEditor.forgetUndoRedo();
Editable t = mEditableFactory.newEditable(text);
text = t;
setFilters(t, mFilters);
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) imm.restartInput(this);
} else if (type == BufferType.SPANNABLE || mMovement != null) {
text = mSpannableFactory.newSpannable(text);
} else if (!(text instanceof CharWrapper)) {
text = TextUtils.stringOrSpannedString(text);
}
...
好了接下來再繼續光標的繪製流程
Editor的onDraw()方法如下:
....
if (highlight != null && selectionStart == selectionEnd && mCursorCount > 0) {
drawCursor(canvas, cursorOffsetVertical);
// Rely on the drawable entirely, do not draw the cursor line.
// Has to be done after the IMM related code above which relies on the highlight.
//highlight置空不再繪製光標線就是選中字體顏色蒙層
highlight = null;
}
....
//此處爲繪製光標的詳細方法
private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
final boolean translate = cursorOffsetVertical != 0;
if (translate) canvas.translate(0, cursorOffsetVertical);
for (int i = 0; i < mCursorCount; i++) {
mCursorDrawable[i].draw(canvas);
}
if (translate) canvas.translate(0, -cursorOffsetVertical);
}
//此時光標是完全繪製完成,但是如何實現閃爍效果的,請注意TextView中這個方法
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (isTemporarilyDetached()) {
// If we are temporarily in the detach state, then do nothing.
super.onFocusChanged(focused, direction, previouslyFocusedRect);
return;
}
//如果當前TextView是Edittext的話則需要調用mEditor的onFocusChanged()方法
if (mEditor != null) mEditor.onFocusChanged(focused, direction);
....
}
如下mEditor的onFocusChanged()方法
void onFocusChanged(boolean focused, int direction) {
mShowCursor = SystemClock.uptimeMillis();
ensureEndedBatchEdit();
if (focused) {
...
makeBlink();
...
}else{
...
}
...
void makeBlink() {
if (shouldBlink()) {
mShowCursor = SystemClock.uptimeMillis();
if (mBlink == null) mBlink = new Blink();
mTextView.removeCallbacks(mBlink);
//注意這個行代碼 textView延遲BLINK時間執行一個runnable任務
mTextView.postDelayed(mBlink, BLINK);
} else {
if (mBlink != null) mTextView.removeCallbacks(mBlink);
}
}
Editor中
static final int BLINK = 500;
private class Blink implements Runnable {
private boolean mCancelled;
public void run() {
if (mCancelled) {
return;
}
mTextView.removeCallbacks(this);
if (shouldBlink()) {
if (mTextView.getLayout() != null) {
//此處刷新光標
mTextView.invalidateCursorPath();
}
//循環調用Blink
mTextView.postDelayed(this, BLINK);
}
}
//此處爲TextView中刷新光標的邏輯
void invalidateCursorPath() {
if (mHighlightPathBogus) {
invalidateCursor();
} else {
final int horizontalPadding = getCompoundPaddingLeft();
final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
if (mEditor.mCursorCount == 0) {
synchronized (TEMP_RECTF) {
/*
* The reason for this concern about the thickness of the
* cursor and doing the floor/ceil on the coordinates is that
* some EditTexts (notably textfields in the Browser) have
* anti-aliased text where not all the characters are
* necessarily at integer-multiple locations. This should
* make sure the entire cursor gets invalidated instead of
* sometimes missing half a pixel.
*/
float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
if (thick < 1.0f) {
thick = 1.0f;
}
thick /= 2.0f;
// mHighlightPath is guaranteed to be non null at that point.
mHighlightPath.computeBounds(TEMP_RECTF, false);
invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
(int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
(int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
(int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
}
} else {
for (int i = 0; i < mEditor.mCursorCount; i++) {
Rect bounds = mEditor.mCursorDrawable[i].getBounds();
invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
}
}
}
}
小結:通過以上代碼分析 我們瞭解到了光標繪製流程和刷新機制,因此次我們可以根據以上的分析,實現自定義的光標定製(xml設置光標的資源drawableandroid:textCursorDrawable=”@drawable/text_cursor_holo_light”),禁止光標的閃爍(editor類中反射調用suspendBlink()方法實現禁止閃爍),以及上面提到的獲取焦點但是禁止系統軟件盤的彈出
3.activity是如何在輸入法彈出後調整窗口大小的
此處僅討論當軟鍵盤彈起的時候,當前activity是如何實現界面變換(關於輸入法的整體框架和通信的實現在下次闡述),此處我們只關心繫統在activity在系統輸入法彈出後窗口的調整
首先列出一下軟鍵盤和app當前窗口也的的交互模式
【A】stateUnspecified:軟鍵盤的狀態並沒有指定,系統將選擇一個合適的狀態或依賴於主題的設置
【B】stateUnchanged:當這個activity出現時,軟鍵盤將一直保持在上一個activity裏的狀態,無論是隱藏還是顯示
【C】stateHidden:用戶選擇activity時,軟鍵盤總是被隱藏
【D】stateAlwaysHidden:當該Activity主窗口獲取焦點時,軟鍵盤也總是被隱藏的
【E】stateVisible:軟鍵盤通常是可見的
【F】stateAlwaysVisible:用戶選擇activity時,軟鍵盤總是顯示的狀態
【G】adjustUnspecified:默認設置,通常由系統自行決定是隱藏還是顯示
【H】adjustResize:該Activity總是調整屏幕的大小以便留出軟鍵盤的空間
【I】adjustPan:當前窗口的內容將自動移動以便當前焦點從不被鍵盤覆蓋和用戶能總是看到輸入內容的部分
我們都知道當每一個window初始化的時候都會初始化一個inputmethodmanager 當window獲取焦點或者變換焦點的時候,inputmethodmanager便會綁定View,如果當前View是Edittext的話變化通過系統的ims吊起系統的輸入法,這時當前的activity根據softinputmode來變換當前的窗口大小
這個是如何實現的呢,首先我們需要了解wms對window的窗口計算,
應用程序進程是從ViewRoot類的成員函數performTraversals開始,向WindowManagerService服務請求計算一個Activity窗口的大小的,在performTraversals方法中
private void performTraversals() {
....
if (mLayoutRequested) {
......
if (mFirst) {
host.fitSystemWindows(mAttachInfo.mContentInsets);
......
} else {
//是否存在邊界
if (!mAttachInfo.mContentInsets.equals(mPendingContentInsets)) {
mAttachInfo.mContentInsets.set(mPendingContentInsets);
host.fitSystemWindows(mAttachInfo.mContentInsets);
insetsChanged = true;
......
}
if (!mAttachInfo.mVisibleInsets.equals(mPendingVisibleInsets)) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
......
}
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowResizesToFitContent = true;
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
}
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
......
host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
....
boolean windowShouldResize = mLayoutRequested && windowResizesToFitContent
&& ((mWidth != host.mMeasuredWidth || mHeight != host.mMeasuredHeight)
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
//檢查Activity窗口是否需要指定有額外的內容邊襯區域和可見邊襯區域。如果有的話,那麼變量attachInfo所指向的一個AttachInfo對象的成員變量mTreeObserver所描述的一個TreeObserver對象的成員函數hasComputeInternalInsetsListerner的返回值ComputeInternalInsets就會等於true。Activity窗口指定額外的內容邊襯區域和可見邊襯區域是爲了放置一些額外的東西。
//這個是獲取inputservice設置的值
final boolean computesInternalInsets =
attachInfo.mTreeObserver.hasComputeInternalInsetsListeners();
}
relayoutWindow(){
//如果有邊襯區域 則賦值給wms
if (computesInternalInsets) {
ViewTreeObserver.InternalInsetsInfo insets = attachInfo.mGivenInternalInsets;
final Rect givenContent = attachInfo.mGivenInternalInsets.contentInsets;
final Rect givenVisible = attachInfo.mGivenInternalInsets.visibleInsets;
givenContent.left = givenContent.top = givenContent.right
= givenContent.bottom = givenVisible.left = givenVisible.top
= givenVisible.right = givenVisible.bottom = 0;
attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
Rect contentInsets = insets.contentInsets;
Rect visibleInsets = insets.visibleInsets;
if (mTranslator != null) {
contentInsets = mTranslator.getTranslatedContentInsets(contentInsets);
visibleInsets = mTranslator.getTranslatedVisbileInsets(visibleInsets);
}
if (insetsPending || !mLastGivenInsets.equals(insets)) {
mLastGivenInsets.set(insets);
try {
sWindowSession.setInsets(mWindow, insets.mTouchableInsets,
contentInsets, visibleInsets);
} catch (RemoteException e) {
}
}
} }
//具體窗口計算實現 在計算一個窗口的大小的時候,我們需要四個參數。第一個參數是父窗口的大小pf,第二個參數是屏幕的大小df,第三個參數是內容區域邊襯大小cf,第四個參數是可見區域邊襯大小vf
public class PhoneWindowManager implements WindowManagerPolicy {
......
//開始佈局初始化
public void beginLayoutLw(int displayWidth, int displayHeight) {
mW = displayWidth;
mH = displayHeight;
mDockLeft = mContentLeft = mCurLeft = 0;
mDockTop = mContentTop = mCurTop = 0;
mDockRight = mContentRight = mCurRight = displayWidth;
mDockBottom = mContentBottom = mCurBottom = displayHeight;
mDockLayer = 0x10000000;
//(mDockLeft, mDockTop, mDockRight, mDockBottom),用來描述當前這輪窗口大小計算過程中的輸入法窗口所佔據的位置,//後一個成員變量mDockLayer用來描述輸入法窗品的Z軸位置。
//它們組成一個四元組(mCurLeft, mCurTop, mCurRight, mCurBottom),用來描述當前這輪窗口大小計算過程的屏幕裝飾區,它對應於前面所提到的Activity窗口的可見區域邊襯
//(mContentLeft, mContentTop, mContentRight, mContentBottom),也是用來描述當前這輪窗口大小計算過程的屏幕裝飾區,不過它對應的是前面所提到的Activity窗口的內容區域邊襯
//計算狀態欄的大小。狀態欄的大小一經確定,並且它是可見的,那麼就會修改成員變量mCurLeft、mContentLeft和mDockLeft的值爲狀態欄的所佔據的區域的下邊界位置,這樣就可以將(mCurLeft, mCurTop, mCurRight, mCurBottom)、(mContentLeft, mContentTop, mContentRight, mContentBottom)和(mDockLeft, mDockTop, mDockRight, mDockBottom)這三個區域限制爲剔除狀態欄區域之後所得到的屏幕區域
// decide where the status bar goes ahead of time
if (mStatusBar != null) {
final Rect pf = mTmpParentFrame;
final Rect df = mTmpDisplayFrame;
final Rect vf = mTmpVisibleFrame;
pf.left = df.left = vf.left = 0;
pf.top = df.top = vf.top = 0;
pf.right = df.right = vf.right = displayWidth;
pf.bottom = df.bottom = vf.bottom = displayHeight;
mStatusBar.computeFrameLw(pf, df, vf, vf);
if (mStatusBar.isVisibleLw()) {
// If the status bar is hidden, we don't want to cause
// windows behind it to scroll.
mDockTop = mContentTop = mCurTop = mStatusBar.getFrameLw().bottom;
......
}
}
}
......
}
//真正計算窗口大小
public void layoutWindowLw(WindowState win, WindowManager.LayoutParams attrs,
WindowState attached) {
// we've already done the status bar
if (win == mStatusBar) {
return;
}
......
final int fl = attrs.flags;
final int sim = attrs.softInputMode;
final Rect pf = mTmpParentFrame;
final Rect df = mTmpDisplayFrame;
final Rect cf = mTmpContentFrame;
final Rect vf = mTmpVisibleFrame;
if (attrs.type == TYPE_INPUT_METHOD) {
pf.left = df.left = cf.left = vf.left = mDockLeft;
pf.top = df.top = cf.top = vf.top = mDockTop;
pf.right = df.right = cf.right = vf.right = mDockRight;
pf.bottom = df.bottom = cf.bottom = vf.bottom = mDockBottom;
// IM dock windows always go to the bottom of the screen.
attrs.gravity = Gravity.BOTTOM;
mDockLayer = win.getSurfaceLayer();
} else {
if ((fl &
(FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR))
== (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
// This is the case for a normal activity window: we want it
// to cover all of the screen space, and it can take care of
// moving its contents to account for screen decorations that
// intrude into that space.
if (attached != null) {
// If this window is attached to another, our display
// frame is the same as the one we are attached to.
setAttachedWindowFrames(win, fl, sim, attached, true, pf, df, cf, vf);
} else {
pf.left = df.left = 0;
pf.top = df.top = 0;
pf.right = df.right = mW;
pf.bottom = df.bottom = mH;
//內容區域邊襯大小cf,可見區域邊襯大小vf
//注意此處的代碼 如果變量sim的SOFT_INPUT_ADJUST_RESIZE位等於1,那麼就意味着窗口win在出向輸入法窗口的時候,它的內/容要重新進行排布,避免被輸入法窗口擋住,因此,這時候窗口win的內容區域大小就會等於PhoneWindowManager類的成員變量mContentLeft、mContentTop、mContentRight和mContentBottom所組成的區域的大小。另一方面,如果變量sim的SOFT_INPUT_ADJUST_RESIZE位等於0,那麼就意味着窗口win在出向輸入法窗口的時候,它的內容不需要重新進行排布
if ((sim & SOFT_INPUT_MASK_ADJUST) != SOFT_INPUT_ADJUST_RESIZE) {
cf.left = mDockLeft; //可見壓縮
cf.top = mDockTop;
cf.right = mDockRight;
cf.bottom = mDockBottom;
} else {
cf.left = mContentLeft; //內容不壓縮
cf.top = mContentTop;
cf.right = mContentRight;
cf.bottom = mContentBottom;
}
vf.left = mCurLeft;
vf.top = mCurTop;
vf.right = mCurRight;
vf.bottom = mCurBottom;
}
}
......
}
win.computeFrameLw(pf, df, cf, vf);
// Dock windows carve out the bottom of the screen, so normal windows
// can't appear underneath them.
if (attrs.type == TYPE_INPUT_METHOD && !win.getGivenInsetsPendingLw()) {
int top = win.getContentFrameLw().top;
top += win.getGivenContentInsetsLw().top;
if (mContentBottom > top) {
mContentBottom = top;
}
top = win.getVisibleFrameLw().top;
top += win.getGivenVisibleInsetsLw().top;
if (mCurBottom > top) {
mCurBottom = top;
}
......
}
}
......
}
在InputMethodService類中在初始化Softwindow時
void initViews() {
mInitialized = false;
mWindowCreated = false;
mShowInputRequested = false;
mShowInputFlags = 0;
mThemeAttrs = obtainStyledAttributes(android.R.styleable.InputMethodService);
mRootView = mInflater.inflate(
com.android.internal.R.layout.input_method, null);
mRootView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
mWindow.setContentView(mRootView);
//注意此處的代碼設置View監聽,也就是ViewRootImpl獲取的內容邊間的連接處
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 = (ViewGroup)mRootView.findViewById(com.android.internal.R.id.fullscreenArea);
mExtractViewHidden = false;
mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea);
mExtractView = null;
mExtractEditText = null;
mExtractAccessories = null;
mExtractAction = null;
mFullscreenApplied = false;
mCandidatesFrame = (FrameLayout)mRootView.findViewById(android.R.id.candidatesArea);
mInputFrame = (FrameLayout)mRootView.findViewById(android.R.id.inputArea);
mInputView = null;
mIsInputViewShown = false;
mExtractFrame.setVisibility(View.GONE);
mCandidatesVisibility = getCandidatesHiddenVisibility();
mCandidatesFrame.setVisibility(mCandidatesVisibility);
mInputFrame.setVisibility(View.GONE);
}
//此處是監聽具體的實現
final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
new ViewTreeObserver.OnComputeInternalInsetsListener() {
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
//是否顯示額外的區域,設置內容邊界
if (isExtractViewShown()) {
// In true fullscreen mode, we just say the window isn't covering
// any content so we don't impact whatever is behind.
View decor = getWindow().getWindow().getDecorView();
info.contentInsets.top = info.visibleInsets.top
= decor.getHeight();
info.touchableRegion.setEmpty();
info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
} else {
onComputeInsets(mTmpInsets);
info.contentInsets.top = mTmpInsets.contentTopInsets;
info.visibleInsets.top = mTmpInsets.visibleTopInsets;
info.touchableRegion.set(mTmpInsets.touchableRegion);
info.setTouchableInsets(mTmpInsets.touchableInsets);
}
}
};
接下來我們來分析下final boolean computesInternalInsets =
attachInfo.mTreeObserver.hasComputeInternalInsetsListeners();
}
注意我們看下attachinfo 類中mTreeObserver
final ViewTreeObserver mTreeObserver;
而ViewTreeObserver中
/**
* Returns whether there are listeners for computing internal insets.
*/
final boolean hasComputeInternalInsetsListeners() {
final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
mOnComputeInternalInsetsListeners;
return (listeners != null && listeners.size() > 0);
}
所以以上我們將系統輸入的dialog和當前activity的窗口變換講述完了
//此處OnComputeInternalInsetsListener爲其簡介
/**
* Interface definition for a callback to be invoked when layout has
* completed and the client can compute its interior insets.
*
* We are not yet ready to commit to this API and support it, so
* @hide
*/
public interface OnComputeInternalInsetsListener {
/**
* Callback method to be invoked when layout has completed and the
* client can compute its interior insets.
*
* @param inoutInfo Should be filled in by the implementation with
* the information about the insets of the window. This is called
* with whatever values the previous OnComputeInternalInsetsListener
* returned, if there are multiple such listeners in the window.
*/
public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
}
Android framwork源碼 https://pan.baidu.com/s/1bqKNx3x bthv
此處總結下
app當前窗口會在焦點變換並且內容邊間不爲空的時候從新計算窗口大小,在這個件帶下的過程中會根據設置的軟件盤模式決定當前窗口的計算大小,而內容邊間又是根據
InputMethodService的mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsComputer);
mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
設置內容邊間而計算activity窗口的大小,