欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android沉浸式PopupWindow响应物理按键踩坑

程序员文章站 2022-05-26 16:58:05
...

一、需求:

全屏视频播放器,点击右上角更多按钮,弹出设置菜单,效果参考优酷、西瓜等视频应用。

功能细节:

1.弹窗显示和消失动画

2.沉浸式效果,窗口显示和消失过程中,状态栏不会出现

3.弹窗能够响应物理back按键

Android沉浸式PopupWindow响应物理按键踩坑

 

二、实现方案

实现方式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/