本文讨论以下几个方面的问题以及解决办法
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窗口的大小,