Android沉浸式PopupWindow响应物理按键踩坑
一、需求:
全屏视频播放器,点击右上角更多按钮,弹出设置菜单,效果参考优酷、西瓜等视频应用。
功能细节:
1.弹窗显示和消失动画
2.沉浸式效果,窗口显示和消失过程中,状态栏不会出现
3.弹窗能够响应物理back按键
二、实现方案
实现方式1
直接上结论:
通过PopupWindow.setAnimationStyles实现动画,
显示时focusable=false,由于View没有焦点,物理按键事件不会自动分发给PopupWindow,Activity重写dispatchKeyEvent或者onBackPressed,判断弹窗显示的时候,拦截back事件,并让弹窗消失。
分析:问题的关键点在于,如果focusable=true,PopupWindow就可以自动响应物理按键,但是此时会破坏沉浸模式,出状态栏,原因是把Activity的焦点给抢走了。所以,只能设置focusable=false,通过Activity来分发事件给弹窗。
总结:正规做法,没有用到任何奇技淫巧,不会有坑,分发事件稍微麻烦,如果多个Activity同时用到该弹窗,都需要向该弹窗分发back事件
其它实现方式
这几种方式大部分都有坑
1. 显示时无焦点,显示完再update设置需要获取焦点(有坑)
参考实现:https://relex.me/show-popupwindow-in-immersive-mode/
原理:显示PopupWindow前,focusable=false,调用show后再focusable=true,update,
问题点:大部分手机ok,三星手机(SM-G9350 android 7.0)会出现动画异常,update方法会触发第二次enter动画,菜单会从右侧一直滑动到屏幕最左侧。
2.监听焦点改变,触发隐藏状态栏(有坑)
设置设置focusable=true,通过onWindowFocusChanged或者view.getViewTreeObserver().addOnWindowFocusChangeListener,
监听到焦点丢失时,重新隐藏状态栏,要求api >= 18
问题点:点击出弹窗时会闪现状态栏,不能保证状态栏一直不出来
3.Dialog方式实现
参考:https://www.jianshu.com/p/d10dd0c1a344
原理:也同其他方式1,显示时无焦点,显示完再update设置View需要获取焦点,
问题点:暂时未验证,如果有问题的话,可能也应该是状态栏闪现的问题。
三、具体实现
1.弹窗关键代码
public class PoppupWindowLandActivity extends AppCompatActivity implements View.OnClickListener {
private ViewGroup mViewGroup;
private ViewGroup mContentView;
private ViewGroup mMainContent;
private PopupWindow mPopupWindow;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_poppup_window_land);
mViewGroup = findViewById(R.id.layout_main);
// 沉浸式
StatusBarUtils.fullScreenImmersive(this);
findViewById(R.id.btn_popup1).setOnClickListener(this);
findViewById(R.id.btn_popup2).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_popup1:
showMoreMenuWindow1(this, mViewGroup);
break;
case R.id.btn_popup2:
showMoreMenuDialog(this, mViewGroup);
break;
default:
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent ke) {
if (ke.getAction() == MotionEvent.ACTION_DOWN && ke.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (isMoreMenuShow()) {
dismissMoreMenu();
return true;
}
}
return super.dispatchKeyEvent(ke);
}
public void showMoreMenuWindow1(final Context context, final ViewGroup viewGroup) {
if (isMoreMenuShow()) {
return;
}
mContentView = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.video_full_more_popup_layout, viewGroup, false);
initView(mContentView);
mPopupWindow = new PopupWindow(mContentView, ScreenUtil.dp2px(context, 375), FrameLayout.LayoutParams.MATCH_PARENT, false);
mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
LogUtil.d("onDismiss");
onMoreMenuDismiss();
}
});
mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
mPopupWindow.setOutsideTouchable(true);
mPopupWindow.setTouchable(true);
mPopupWindow.setClippingEnabled(false);
mPopupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
mPopupWindow.setAnimationStyle(R.style.animate_fullscreen_dialog);
mPopupWindow.showAtLocation(viewGroup, Gravity.RIGHT, 0, 0); // 相对于屏幕去显示
// mPopupWindow.showAsDropDown(viewGroup, Gravity.RIGHT, 0, 0); // 相对于控件去显示
// mPopupWindow.setFocusable(true);
// mPopupWindow.update(); // 大部分手机ok,目前发现三星手机(SM-G9350 android 7.0)有坑,会触发二次enter动画
// StatusBarUtils.fullScreenImmersive(mPopupWindow.getContentView());
}
private boolean isMoreMenuShow() {
return mPopupWindow != null && mPopupWindow.isShowing();
}
public void dismissMoreMenu() {
if (mPopupWindow != null) {
if (mPopupWindow.isShowing()) {
mPopupWindow.dismiss();
}
}
}
private void onMoreMenuDismiss() {}
private void initView(View view) {
mMainContent = view.findViewById(R.id.main_content);
}
private void showMoreMenuDialog(Context context, ViewGroup viewGroup) {
// TODO: 2019-10-21
}
}
2.转场动画
从右侧进入
styles.xml
<style name="animate_fullscreen_dialog">
<item name="android:windowEnterAnimation">@anim/fullscreen_anim_enter</item>
<item name="android:windowExitAnimation">@anim/fullscreen_anim_out</item>
</style>
fullscreen_anim_enter.xml
<?xml version="1.0" encoding="utf-8"?>
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:fromXDelta="100%p"
android:toXDelta="0%p"/>
fullscreen_anim_out.xml
<?xml version="1.0" encoding="utf-8"?>
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:fromXDelta="0%p"
android:toXDelta="100%p"/>
3. 沉浸式工具类
public class StatusBarUtils {
public static void fullScreenImmersive(Context mContext) {
if (mContext != null && mContext instanceof Activity) {
Window window = ((Activity) mContext).getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
StatusBarUtils.setStatusBarTranslucent(window, 0);
StatusBarUtils.setStatusBar(window, false, false, true);
} else {
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
StatusBarUtils.setActionBarAndNavKeyVisibility(mContext, false);
}
}
}
public static void setStatusBarTranslucent(Window window, int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // 5.0及以上
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
if ((color & 0xFF000000) == 0) {
window.setStatusBarColor(Color.TRANSPARENT);
} else if ((color ^ 0xFFFFFFFF) == 0) {
if (isSupportWhiteBackground()) {
window.setStatusBarColor(color);
} else {
window.setStatusBarColor(STATUS_BAR_BACKGROUND_COLOR_GRAY);
}
} else {
window.setStatusBarColor(color);
}
} else {
if ((color & 0xFF000000) == 0) {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
}
public static void setStatusBar(Window window, boolean show, int color, boolean fullscreen) {
setStatusBarTranslucent(window, color);
boolean light = getBright(color) * 2 > 255;
setStatusBar(window, show, light, fullscreen);
}
public static void setStatusBar(Window window, boolean show, int color, boolean light, boolean fullscreen) {
setStatusBarTranslucent(window, color);
setStatusBar(window, show, light, fullscreen);
}
public static void setStatusBar(Window window, boolean show, boolean light, boolean fullscreen) {
Context context = window.getContext();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (show) {
int opt = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
if (fullscreen) {
opt |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
} else {
opt |= View.SYSTEM_UI_FLAG_VISIBLE;
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
if (light) {
opt |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else {
opt &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
// 机型适配
window.getDecorView().setSystemUiVisibility(opt);
} else {
int opt = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
if (fullscreen) {
opt |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
}
window.getDecorView().setSystemUiVisibility(opt);
}
} else {
if (fullscreen) {
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
setActionBarAndNavKeyVisibility(context, show);
}
}
public static void setActionBarAndNavKeyVisibility(Context context, boolean show) {
if (show) {
showSupportActionBar(context, true, true);
if (context instanceof Activity) {
showNavKey((Activity) context, View.SYSTEM_UI_FLAG_VISIBLE);
}
} else {
hideSupportActionBar(context, true, true);
if (context instanceof Activity) {
hideNavKey((Activity) context);
}
}
}
public static void hideSupportActionBar(Context context, boolean actionBar, boolean statusBar) {
if (actionBar) {
AppCompatActivity appCompatActivity = getAppCompActivity(context);
if (appCompatActivity != null) {
ActionBar ab = appCompatActivity.getSupportActionBar();
if (ab != null) {
ab.setShowHideAnimationEnabled(false);
ab.hide();
}
}
}
}
public static void showSupportActionBar(Context context, boolean actionBar, boolean statusBar) {
if (actionBar) {
AppCompatActivity appCompatActivity = getAppCompActivity(context);
if (appCompatActivity != null) {
ActionBar ab = appCompatActivity.getSupportActionBar();
if (ab != null) {
ab.setShowHideAnimationEnabled(false);
ab.show();
}
}
}
}
/**
* Get AppCompatActivity from context
*
* @param context
* @return AppCompatActivity if it's not null
*/
public static AppCompatActivity getAppCompActivity(Context context) {
if (context == null) {
return null;
}
if (context instanceof AppCompatActivity) {
return (AppCompatActivity) context;
} else if (context instanceof ContextThemeWrapper) {
return getAppCompActivity(((ContextThemeWrapper) context).getBaseContext());
}
return null;
}
public static void showNavKey(Activity activity, int systemUiVisibility) {
activity.getWindow().getDecorView().setSystemUiVisibility(systemUiVisibility);
}
public static void hideNavKey(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 设置屏幕始终在前面,不然点击鼠标,重新出现虚拟按键
activity.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav
// bar
| View.SYSTEM_UI_FLAG_IMMERSIVE);
} else {
activity.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav
);
}
}
public static boolean isSupportWhiteBackground() {
if (((DeviceUtils.getDeviceType() == DeviceUtils.DEVICE_VIVO)
|| (DeviceUtils.getDeviceType() == DeviceUtils.DEVICE_SAMSUNG))
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false;
}
return true;
}
}
参考文章:
Android 全局沉浸式显示效果:https://www.jianshu.com/p/d10dd0c1a344
沉浸式中显示 PopupWindow:https://relex.me/show-popupwindow-in-immersive-mode/
上一篇: 在Python中操作字典之clear()方法的使用
下一篇: 走过内卷的To C,走向实体的To B