美文网首页Android开发技术中心UIAndroid开发
适配安卓沉浸式状态栏的新姿势

适配安卓沉浸式状态栏的新姿势

作者: 殇透俄0心 | 来源:发表于2017-10-14 23:25 被阅读403次

    Github Demo: https://github.com/lliuguangbo/AutoSystemBar

    针对状态栏,官方从4.4版本开始支持,但是4.4和5.0以上API是不同的,6.0以上提供了两种状态栏图标样式
    分别是白色和黑色样式。

    针对状态栏图标样式的修改,小米和魅族提供额外的API,在6.0以下都支持,可以参考它们的文档:

    Android4.4状态栏的API:
    window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    

    设置该属性后,Activity的Layout会嵌入到状态栏下,
    也就是setContentView(View)的set进去的view高度比之前高出了状态栏的高度。

    Android5.0以上状态栏的API:
    View decorView = window.getDecorView();
    decorView.setSystemUiVisibility(decorView.getSystemUiVisibility()
            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
    window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
    
    window.setStatusBarColor(Color.TRANSPARENT);
    

    5.0 以上 FLAG_TRANSLUCENT_STATUS 与 FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS是有冲突的,需要调用clearFlags清楚这个flag否则会没有效果。 通过调用setStatusBarColor()来改变状态栏颜色。
    但是当设置的颜色不是全透明时,Activity的setContentView(View)的set进去的view高度是没有发生变化的,如果设置的颜色是去透明时,setContentView(View)的set进去的view高度同样比之前高出了状态栏的高度。

    官方Android6.0修改状态栏图标样式API
    View decorView = window.getDecorView();
    if(darkFont){
        //黑色样式
        decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
    }else {
        //白色样式
        decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
    }
    
    适配状态栏的方案思考
    • 现在越来越多的应用开始适配状态栏了,不再是黑黑的状态栏;
    • 越来越多的应用会使用透明状态栏,把图片或者视屏嵌入到状态栏下充分利用空间,通常这个界面是可以滚动的,当滚动一定的位置时改变状态栏颜色。
    • 状态栏图标样式的适配

    开发者通常的适配方法是:

    • 修改状态栏颜色,4.4版本需要额外添加一个状态栏高度的View来填充,通过改变这个View的背景来实现;5.0以上有两种方法,一种是调用setStatusBarColor(), 另一种和4.4的方案一致。
    • 这种情况适配起来还是挺麻烦的,透明状态栏时,activity的contentView的高度高出了状态栏的高度会引起一些布局上的问题,例如图片嵌入状态栏下,当滚动时ActionBar会重新出现,会导致ActionBar也嵌入状态栏下,很难看。适配时还要对不同的版本进行布局的调整(因为4.4以下没问题)
    • 对于状态栏图标样式的适配,如果你的ActionBar背景是白色的,状态栏改为白色,那么状态栏图标样式就需要改成黑色,如果还是白色会导致看不清状态栏图标

    总的来说,对于第二种情况时适配还是挺麻烦的。

    • 采用额外添加一个状态栏高度的View来填充的方法可以统一4.4和5.0版本;
    • 获取ActionBar下最多的颜色,使用该颜色当做状态栏颜色,这时想到了官方提供com.android.support:palette-v7:26.1.0
    • 状态栏图标的样式根据状态栏颜色做出调整。
    代码的实现

    先撸个自定义ViewGroup,继承RelativeLayout。
    下面是这个自定义ViewGroup需要加载的布局文件,分三部分:状态栏View,底部导航栏View, 中间内容部分

    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <FrameLayout
            android:id="@+id/content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@+id/status_view"
            android:layout_above="@+id/navigation_view"
            >
        </FrameLayout>
    
        <cn.albert.autosystembar.SystemBarView
            android:id="@+id/status_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="gone"
            android:layout_alignParentTop="true"
            />
    
        <cn.albert.autosystembar.SystemBarView
            android:id="@+id/navigation_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="gone"
            android:layout_alignParentBottom="true"
            />
    
    </merge>
    

    下面是自定义ViewGroup关键代码,命名为InternalLayout,在构造器里对状态栏View,底部导航栏View初始化高度。

    class InternalLayout extends RelativeLayout{
        // 部分关键代码
        public InternalLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            Utils.initializeStatusBar(this);
            Utils.initializeNavigationBar(this);
    
            ViewCompat.setFitsSystemWindows(this, true);
            ViewCompat.requestApplyInsets(this);
            ViewCompat.setOnApplyWindowInsetsListener(this, new android.support.v4.view.OnApplyWindowInsetsListener() {
                @Override
                public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
                    return insets.consumeSystemWindowInsets();
                }
            });
            inflate(context, R.layout.layout_content, this);
            mStatusView = findViewById(R.id.status_view);
            mStatusView.getLayoutParams().height = Utils.sStatusBarHeight;
            mNavigationView = findViewById(R.id.navigation_view);
            mNavigationView.getLayoutParams().height = Utils.sNavigationBarHeight;
    
            mContentLayout = (ViewGroup) findViewById(R.id.content);
    
        }
        
        
        void setContentView(View content) {
            if(content.getParent() == null){
                mContentLayout.addView(content);
            }
        }
    }
    

    下面是SystemBarView的代码, android4.4以下SystemBarView高度,宽带都为0。

    class SystemBarView extends View{
    
        public SystemBarView(Context context) {
            super(context);
        }
    
        public SystemBarView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }else {
                setMeasuredDimension(0, 0);
                setVisibility(GONE);
            }
        }
    }
    

    InternalLayout内部有一个FrameLayout,用来装Activity的setContentView()里View,代码如下:

    final View decorView = window.getDecorView();
    final View androidContent = window.getDecorView().findViewById(Window.ID_ANDROID_CONTENT);
    final ViewGroup realContent = ((ViewGroup) androidContent);
    View content = realContent.getChildAt(0);
    //content是Activity的setContentView()里的View
    realContent.removeView(content);
    InternalLayout layout = new InternalLayout(activity);
    layout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    layout.setContentView(content);
    realContent.addView(layout);
    

    接着实现在ActionBar里获取主颜色作为状态栏的颜色:

    //decorView是Activity的DecorView, 获取decorView的bitmap
    decorView.setDrawingCacheEnabled(true);
    final Bitmap bitmap = Bitmap.createBitmap(decorView.getDrawingCache());
    decorView.setDrawingCacheEnabled(false);
    
    
    int top = Utils.sStatusBarHeight + PADDING;
    int bottom = (int) (top + ACTION_BAR_DEFAULT_HEIGHT * decorView.getResources().getDisplayMetrics().density);
    //PADDING = 10, ACTION_BAR_DEFAULT_HEIGHT = 48, rect 记录了一个矩形,在状态栏下面的一个矩形,10, 48 我随便定义的距离,这个矩形不用太精准,不用就是ActionBar的位置。
    Rect rect = new Rect(0, top, bitmap.getWidth(), bottom);
    
    //利用Palette的API来获取颜色
    mBuilder = new Palette.Builder(bitmap)
                    .clearFilters()
                    .addFilter(FILTER)
                    .setRegion(rect.left, rect.top, rect.right, rect.bottom);
    Palette p = mBuilder.generate()
    //这里会获取一个List<Palette.Swatch>, Swatch就是代表获取到一种颜色,
    // getPopulation() :返回这颜色的数量。
    // getRgb(): 返回rgb颜色
    
    List<Palette.Swatch> swatches = new ArrayList<>(palette.getSwatches());                
    
    //给swatches排排序,获取数量最大的那个
    Collections.sort(swatches, new Comparator<Palette.Swatch>() {
        @Override
        public int compare(Palette.Swatch lhs, Palette.Swatch rhs) {
            if (lhs == null && rhs != null) {
                return 1;
            } else if (lhs != null && rhs == null) {
                return -1;
            } else if (lhs == null) {
                return 0;
            } else {
                return rhs.getPopulation() - lhs.getPopulation();
            }
        }
    });
    populationSwatch = swatches.get(0);
    color = populationSwatch.getRgb();//color就是我需要的颜色,把它设置到状态栏即可
    
    

    根据颜色去判断使用状态栏图标样式:

    
    private static final float BLACK_MAX_LIGHTNESS = 0.05f;
    private static final float WHITE_MIN_LIGHTNESS = 0.95f;
    
    // 将color转成hsl色彩模式
    private boolean isDarkStyle(int color) {
        boolean isDarkStyle = false;
        //mTemp是一个float数组,数组长度为3,数组的值依次为对色相(H)、饱和度(S)、明度(L)
        ColorUtils.colorToHSL(color, mTemp);
        if (mTemp[2] <= BLACK_MAX_LIGHTNESS) {
            isDarkStyle = false; // 白色
        } else if (mTemp[2] >= WHITE_MIN_LIGHTNESS) {
            isDarkStyle = true;  // 黑色
        }
        return isDarkStyle;
    }
    

    最后我将实践写成了一个库: https://github.com/lliuguangbo/AutoSystemBar

    使用起来很简单, 有问题可以提issue, 也可以star以下表示支持,谢谢.

    //1.
    SystemBarHelper.Builder().into(activity)
    
    //2.
    SystemBarHelper.Builder()
                    .statusBarColor()    // 设置状态栏颜色
                    .statusBarFontStyle()  // 设置状态栏时间,电量的风格, 6.0以上, 部分国产机可以不用6.0以上.
                    .navigationBarColor()  // 设置导航栏颜色
                    .enableImmersedStatusBar()  // 布局嵌入状态栏,例如图片嵌入状态栏
                    .enableImmersedNavigationBar()  // 布局嵌入导航栏,例如图片嵌入导航栏
                    .enableAutoSystemBar(false)  // 根据状态栏下面的背景颜色自动调整状态栏的颜色, 自动调整状态栏时间,电量的风格, 默认是开启的
                    .into(this)
                    
    //3.                
    SystemBarHelper helper = SystemBarHelper.Builder().into(activity);
    helper.setNavigationBarColor()
    helper.setStatusBarColor()
    helper.statusBarFontStyle()
    helper.enableImmersedStatusBar()
    helper.enableImmersedNavigationBar()
    

    相关文章

      网友评论

        本文标题:适配安卓沉浸式状态栏的新姿势

        本文链接:https://www.haomeiwen.com/subject/ioxquxtx.html