Android轮子

高级UI<第五十篇>:沉浸式导航栏

2020-04-06  本文已影响0人  NoBugException

导航栏分为顶部状态栏底部虚拟键盘,导航栏沉浸式在Android 4.4之后被推出,它的适配问题一直都是Android开发者比较关注的问题,现在,我将重新整理一下有关沉浸式导航栏的适配。

[一] 在某些场景下,直接全屏就可以满足客户的需求:比如拍照界面

   requestWindowFeature(Window.FEATURE_NO_TITLE);
   getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

代码也比较简单。

[二] Android 4.4机型适配

Android 4.4能够做到的只有移除状态栏移除底部虚拟键盘,代码如下:

//移除状态栏
mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);               

//移除底部虚拟键盘    
mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
图片.png 图片.png 图片.png 图片.png

以上四张图片是Android 4.4模拟器上的运行效果,前面两张好的不得了,第三张Toolbar的返回键按钮和状态栏挡住,第四张Button组件被虚拟键盘挡住。遇到这种情况有两种方案解决:

[方案一]

加一个判断,如果是4.4~5.0以下的机型不做沉浸式

[方案二]

方案一是一个很好的建议,但是不排除一些客户强制要求做沉浸式,那么,可以在顶部padding一个状态栏高度的空隙,在底部padding一个虚拟键盘高度的空隙,最终效果如下:

图片.png 图片.png

[三] Android 5.0机型适配

适配Android5.0以上的手机,首先需要去除状态栏的灰色背景以及移除隐藏状态栏和底部导航栏的Flag

        //去除灰色背影
        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        //移除隐藏状态栏的Flag
        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        //移除隐藏底部虚拟键盘的Flag
        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);

这是必须要做的一步。

在Android5.0之后,开始支持修改状态栏和底部导航栏的颜色

            //设置状态栏颜色
            mActivity.getWindow().setStatusBarColor(mStatusBarColor);
            //设置虚拟键盘颜色
            mActivity.getWindow().setNavigationBarColor(mNavigationBarColor);

如果需要修改状态栏和底部导航栏颜色,直接使用上面两句代码即可,如果需要全屏,那么需要setSystemUiVisibility方法的支持

      mActivity.getWindow().getDecorView().setSystemUiVisibility(systemUiFlag);

systemUiFlag支持的状态有:

        //View.SYSTEM_UI_FLAG_LOW_PROFILE:弱化状态栏和导航栏的图标
        //View.SYSTEM_UI_FLAG_FULLSCREEN:全屏,隐藏状态栏,需要从状态栏位置下拉才会出现
        //View.SYSTEM_UI_FLAG_HIDE_NAVIGATION:隐藏导航栏,用户点击屏幕会显示导航栏
        //View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION:拓展布局到导航栏后面
        //View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN:拓展布局到状态栏后面
        //View.SYSTEM_UI_FLAG_LAYOUT_STABLE:稳定的布局,不会随系统栏的隐藏、显示而变化
        //View.SYSTEM_UI_FLAG_IMMERSIVE:沉浸模式,用户可以交互的界面
        //View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY:沉浸模式,用户可以交互的界面。同时,用户上下拉系统栏时,会自动隐藏系统栏

使用不同的状态,实现想要的沉浸式效果。

[四] Android 6.0机型适配

从Android6.0开始,Android开始支持修改状态栏图标的颜色,默认是白色,View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR属性可以让白色小图标变成黑色,代码如下:

      mActivity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);

[五] 刘海屏

在Android 7.0开始,很多厂商制作的手机设备开始出现刘海屏和滴水屏,这两种屏幕的顶部可能会遮挡控件,像这种情况的适配一般都是阅读某厂商的官方文档就可以解决。

从Android 9.0(Android P)之后,Google提供了适配刘海屏的API。

一般情况下,刘海部分不会超过状态栏的高度,所以,为了防止刘海挡住底部控件,可以在顶部padding一个状态栏的高度即可。

假如,有一款手机的刘海超过状态栏的高度,那么可以直接找一下对应厂商的官方文档,肯定能找到适配方案。

[六] 封装

接下来,分享一下整体适配代码

public class SystemBar {

    private static Activity mActivity;
    //状态栏颜色,默认透明色
    private static int mStatusBarColor = Color.TRANSPARENT;
    //底部虚拟键盘颜色,默认透明色
    private static int mNavigationBarColor = Color.TRANSPARENT;

    //不做任何动作
    public static final int NORMAL = 1 << 0;
    //修改状态栏颜色
    public static final int STATUS_BAR_COLOR = 1 << 1;
    //修改底部虚拟键盘颜色
    public static final int NAVIGATION_BAR_COLOR = 1 << 2;
    //去除状态栏
    public static final int REMOVE_STATUS_BAR = 1 << 3;
    //去除底部虚拟键盘
    public static final int REMOVE_NAVIGATION_BAR = 1 << 4;
    //状态栏小图标置灰
    public static final int DARK_STATUS_ICON = 1 << 5;

    //策略
    private @Model static int mStrategy;

    private SystemBar(){
        mStrategy = NORMAL;
    }

    static class SystemBarHolder{
        public static SystemBar instance = new SystemBar();
    }

    //flag=true时,参数可以“|”传递多个参数,flag=false时,只能传递一个参数
    @IntDef(flag = true, value = {NORMAL,STATUS_BAR_COLOR,NAVIGATION_BAR_COLOR,REMOVE_STATUS_BAR,REMOVE_NAVIGATION_BAR,DARK_STATUS_ICON})
    //注解作用域参数、方法、成员变量
    @Target({ElementType.PARAMETER,ElementType.METHOD,ElementType.FIELD})
    //仅仅在源码阶段有效
    @Retention(RetentionPolicy.SOURCE)
    public @interface Model{

    }

    //单例
    public static SystemBar with(Activity activity){
        mActivity = (Activity) new WeakReference(activity).get();
        return SystemBarHolder.instance;
    }

    /**
     * 设置状态栏颜色(不设置就是透明色)
     * @param statusBarColor
     * @return
     */
    public SystemBar setStatusBarColor(int statusBarColor){
        mStatusBarColor = statusBarColor;
        return SystemBarHolder.instance;
    }

    /**
     * 设置状态栏颜色(不设置就是透明色)
     * @param navigationBarColor
     * @return
     */
    public SystemBar setNavigationBarColor(int navigationBarColor){
        mNavigationBarColor = navigationBarColor;
        return SystemBarHolder.instance;
    }

    /**
     * 设置策略
     * @param strategy
     * @return
     */
    public SystemBar setStrategy(@Model int strategy){
        mStrategy = strategy;
        return SystemBarHolder.instance;
    }

    /**
     * 添加策略
     * @param strategy
     * @return
     */
    public SystemBar addStrategy(@Model int strategy){
        mStrategy |= strategy;
        return SystemBarHolder.instance;
    }

    /**
     * 移除策略
     * @param strategy
     * @return
     */
    public SystemBar removeStrategy(@Model int strategy){
        mStrategy &= ~strategy;
        return SystemBarHolder.instance;
    }

    /**
     * 判断是否含有某种策略
     * @param strategy
     * @return
     */
    private static boolean isContainStrategy(@Model int strategy){
        return (mStrategy & strategy) > 0;
    }

    public SystemBar build(){

        if(mActivity == null){
            throw new RuntimeException("please set a activity content!");
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

            //去除灰色背影
            mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            //移除隐藏状态栏的Flag
            mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            //移除隐藏底部虚拟键盘的Flag
            mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);

            if(isContainStrategy(STATUS_BAR_COLOR)){
                //设置状态栏颜色
                mActivity.getWindow().setStatusBarColor(mStatusBarColor);
            }

            if(isContainStrategy(NAVIGATION_BAR_COLOR)){
                //设置虚拟键盘颜色
                mActivity.getWindow().setNavigationBarColor(mNavigationBarColor);
            }

            //View.SYSTEM_UI_FLAG_LOW_PROFILE:弱化状态栏和导航栏的图标
            //View.SYSTEM_UI_FLAG_FULLSCREEN:全屏,隐藏状态栏,需要从状态栏位置下拉才会出现
            //View.SYSTEM_UI_FLAG_HIDE_NAVIGATION:隐藏导航栏,用户点击屏幕会显示导航栏
            //View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION:拓展布局到导航栏后面
            //View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN:拓展布局到状态栏后面
            //View.SYSTEM_UI_FLAG_LAYOUT_STABLE:稳定的布局,不会随系统栏的隐藏、显示而变化
            //View.SYSTEM_UI_FLAG_IMMERSIVE:沉浸模式,用户可以交互的界面
            //View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY:沉浸模式,用户可以交互的界面。同时,用户上下拉系统栏时,会自动隐藏系统栏
            int systemUiFlag = View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
            if(isContainStrategy(REMOVE_STATUS_BAR)){
                systemUiFlag |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
                //设置状态栏颜色
                mActivity.getWindow().setStatusBarColor(Color.TRANSPARENT);
            }
            if(isContainStrategy(REMOVE_NAVIGATION_BAR)){
                systemUiFlag |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
                //设置虚拟键盘颜色
                mActivity.getWindow().setNavigationBarColor(Color.TRANSPARENT);
            }
            if(isContainStrategy(DARK_STATUS_ICON)){//Android 6.0的手机支持将状态栏的小图标设置为黑色
                systemUiFlag |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
            }
            mActivity.getWindow().getDecorView().setSystemUiVisibility(systemUiFlag);

        }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

            //Android 4.4适配

            if(isContainStrategy(NORMAL)){
                //什么都不做
            }
            if(isContainStrategy(REMOVE_STATUS_BAR)){
                //移除状态栏
                mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            }
            if(isContainStrategy(REMOVE_NAVIGATION_BAR)){
                //移除底部虚拟键盘
                mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
            }
        }
        return SystemBarHolder.instance;
    }


    /**
     * 处理顶部view和状态栏重合问题
     * @param topView
     */
    public void dealwithTop(View topView) {
        if (topView != null) {
            int statusHeight=getStatusHeight();
            //这里LayoutParams是ViewGroup类型,具体是什么类型按需求而定
            ViewGroup.LayoutParams layoutParams =  topView.getLayoutParams();
            int viewHeight = layoutParams.height;//view的高度, -2:wrap_content   -1:match_parent  具体数值(大于0的数值)
            if(viewHeight > 0){//只需要考虑具体数值,wrap_content和match_parent的请路不需要考虑
                layoutParams.height += statusHeight;
                topView.setLayoutParams(layoutParams);
            }
            topView.setPadding(0,topView.getPaddingTop()+statusHeight,0,0);
        }
    }

    /**
     * 处理底部view和状态栏重合问题
     * @param bottomView
     */
    public void dealwithBottom(View bottomView) {
        if (bottomView != null && haveNavgtion()) {
            int navigationHeight = getNavigationHeight();
            ViewGroup.LayoutParams layoutParams = bottomView.getLayoutParams();
            int viewHeight = layoutParams.height;//view的高度, -2:wrap_content   -1:match_parent  具体数值(大于0的数值)
            if(viewHeight > 0){//只需要考虑具体数值,wrap_content和match_parent的请路不需要考虑
                layoutParams.height += navigationHeight;
                bottomView.setLayoutParams(layoutParams);
            }
            bottomView.setPadding(0,0,0,bottomView.getPaddingBottom()+navigationHeight);
        }
    }


    /**
     * 获取底部虚拟键盘的高度
     * @return
     */
    private int getNavigationHeight() {
        int height=-1;
        try {
            Class<?> clazz=Class.forName("com.android.internal.R$dimen");
            Object  object=clazz.newInstance();
            String heightStr=clazz.getField("navigation_bar_height").get(object).toString();
            height = Integer.parseInt(heightStr);
            height = mActivity.getResources().getDimensionPixelSize(height);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return height;
    }

    /**
     * 底部是否含有虚拟键盘
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    private boolean haveNavgtion() {
        //屏幕的高度  真实物理的屏幕
        Display display=mActivity.getWindowManager().getDefaultDisplay();
        DisplayMetrics displayMetrics=new DisplayMetrics();
        display.getRealMetrics(displayMetrics);
        int heightDisplay=displayMetrics.heightPixels;
        //为了防止横屏
        int widthDisplay=displayMetrics.widthPixels;
        DisplayMetrics contentDisplaymetrics=new DisplayMetrics();
        display.getMetrics(contentDisplaymetrics);
        int contentDisplay=contentDisplaymetrics.heightPixels;
        int contentDisplayWidth=contentDisplaymetrics.widthPixels;
        //屏幕内容高度  显示内容的屏幕
        int w=widthDisplay-contentDisplayWidth;
        //哪一方大于0   就有导航栏
        int h=heightDisplay-contentDisplay;
        return w>0||h>0;
    }

    /**
     * 获取状态栏高度
     * @return
     */
    private int getStatusHeight() {
        int height=-1;
        try {
            Class<?> clazz=Class.forName("com.android.internal.R$dimen");
            Object  object=clazz.newInstance();
            String heightStr=clazz.getField("status_bar_height").get(object).toString();
            height = Integer.parseInt(heightStr);
            //dp--px
            height = mActivity.getResources().getDimensionPixelSize(height);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return height;
    }


}

这是实打实的干货,大家可以直接拿来用,还请帮忙点个赞,给我一个爱心,这样我才会自信满满的继续奋斗。

沉浸式导航栏的代码基本都是修改Window的参数,所以放在setContentView之前,顶部状态栏和底部导航栏添加padding的操作放在setContentView之后,因为需要顶部或底部控件的支持。

使用代码如下:

    //设置沉浸式导航栏
    SystemBar.with(this)
            .setStrategy(SystemBar.NORMAL)
            .addStrategy(SystemBar.REMOVE_STATUS_BAR | SystemBar.REMOVE_NAVIGATION_BAR)
            .setStatusBarColor(Color.parseColor("#50C953"))
            .build();


    setContentView(R.layout.activity_main);

    //请根据具体界面决定是否在顶部添加padding
    SystemBar.with(this).dealwithTop(toolbar);
    //请根据具体界面决定是否在底部添加padding
    SystemBar.with(this).dealwithBottom(findViewById(R.id.button));

[本章完...]

上一篇 下一篇

猜你喜欢

热点阅读