由于业务需要将底部的NavigationBar给隐藏了,期间用不获取焦点的方式处理了dialog弹出navigationBar又会出现的问题,本以为万事ok了,谁曾想,换popupWindow弹出,navigationBar又出现了。
第一想法,按dialog的套路来处理,结果刚下手就发现,两实现方式不同,popupWindow是用windowManager来添加view的,不能用这个套路来玩。
那就换一种,获取到DecorView,去设置它的flags(网上基本上的解决方案都是用这种)
public static void setFullScreenWindowLayout(Window window, boolean withNavigationBar) {
int flags = withNavigationBar ? View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE :
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
window.getDecorView().setSystemUiVisibility(flags);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.setStatusBarColor(Color.TRANSPARENT);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
//设置页面全屏显示
WindowManager.LayoutParams lp = window.getAttributes();
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
//设置页面延伸到刘海区显示
window.setAttributes(lp);
}
}
结果,navigationBar一会出现一会隐藏,不光闪还不按预期出牌,这个方案out。
为什么设置DecorView的flags没有出现预期效果,网上的方案不行,那只能自己动手丰衣足食了。
看下showAtLocation的实现
public void showAtLocation(IBinder token, int gravity, int x, int y) {
if (isShowing() || mContentView == null) {
return;
}
TransitionManager.endTransitions(mDecorView);
detachFromAnchor();
mIsShowing = true;
mIsDropdown = false;
mGravity = gravity;
final WindowManager.LayoutParams p = createPopupLayoutParams(token);
preparePopup(p);
p.x = x;
p.y = y;
invokePopup(p);
}
很简单的一个实现,看方法名也差不多猜到与createPopupLayoutParams(token)有关,接着往下跟
protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
// These gravity settings put the view at the top left corner of the
// screen. The view is then positioned to the appropriate location by
// setting the x and y offsets to match the anchor's bottom-left
// corner.
p.gravity = computeGravity();
p.flags = computeFlags(p.flags);
p.type = mWindowLayoutType;
p.token = token;
p.softInputMode = mSoftInputMode;
p.windowAnimations = computeAnimationResource();
if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
}
if (mHeightMode < 0) {
p.height = mLastHeight = mHeightMode;
} else {
p.height = mLastHeight = mHeight;
}
if (mWidthMode < 0) {
p.width = mLastWidth = mWidthMode;
} else {
p.width = mLastWidth = mWidth;
}
p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
| PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
// Used for debugging.
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
return p;
}
嗯。。看到computeFlags(p.flags),我们应该离结果不远了
private int computeFlags(int curFlags) {
curFlags &= ~(
WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
if(mIgnoreCheekPress) {
curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
}
if (!mFocusable) {
curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
if (mInputMethodMode == INPUT_METHOD_NEEDED) {
curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
}
} else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
}
if (!mTouchable) {
curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
}
if (mOutsideTouchable) {
curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
}
if (!mClippingEnabled || mClipToScreen) {
curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
}
if (isSplitTouchEnabled()) {
curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
}
if (mLayoutInScreen) {
curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
}
if (mLayoutInsetDecor) {
curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
}
if (mNotTouchModal) {
curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
if (mAttachedInDecor) {
curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
}
return curFlags;
}
果然,看成到了熟悉的flags设置。理论上来说,变更这个flags就能实现我们想要的结果了,实践下理论,
嗯。。涉及到flags的方法,还是private就是final的,这问题先放一放,我们自己用WindowManager设置flags来添加contentView,。
实践结果,完美达到预期。
接下来就是怎么优雅的处理了,按刚测试的自己来add? 那还有popupWindow什么事,这方法太low。重写popupWindow?前边就有提供,涉及到flags的,不是private就是final,该如何去干涉add过程?
popupWindow无从下手,那我们就转眼看看WindowManager,最终的view是通过WindowManager添加上的。WindowManager是个接口,WindowManagerImpl是具体的,我们要干涉addView。
这么一分析,其实解决方案也就呼之欲出了,很典型的,可以使用代理来拦截addView,所以我们的最终解决方案:
try {
final WindowManager window = (WindowManager) getContentView().getContext().getSystemService(Context.WINDOW_SERVICE);
WindowManager mWindowManager = (WindowManager) Proxy.newProxyInstance(window.getClass().getClassLoader(),
window.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("addView".equalsIgnoreCase(method.getName())) {
WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) args[1];
layoutParams.flags = (
WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
);
args[1] = layoutParams;
}
return method.invoke(window, args);
}
});
Field field = PopupWindow.class.getDeclaredField("mWindowManager");
field.setAccessible(true);
field.set(this, mWindowManager);
} catch (Exception e) {
e.printStackTrace();
}
只要重写popupWindow,在各种show之前添加这串代理设置,就可以完美解决popupWindow弹出又把navigationBar给显示出来的问题了