Android开发笔记(一)沉浸式状态栏

在Android开发中我们越来越重视用户的App操作体验,在使用App中我们主张减少对用户的干扰,经常会提到一致性体验。为了追求界面的风格的一致性,Google官方在Android 4.4 开始,支持了系统最上方的状态栏(StatusBar)和最下方的导航栏(Navigation Bar)可以被透明化,使得APP中的设计可以过渡更加平滑,不像之前那样的割裂感,让整个APP更加一致。而且后续的系统版本中,持续增加了对状态栏操作的api接口,但这也导致了如果直接使用4.4的方法在Android 5.0 以上会导致显示效果不一致的问题。为了避免这个情况,那么开发者需要考虑版本的兼容性,对不同的系统版本进行分别处理。下面将从不同的系统版本中介绍如何实现沉浸式状态栏的效果。

代码实例:沉浸式状态栏

4.4(KITKAT)

在设置沉浸式状态栏时,首先要将对应Activity设置一个主题(theme),该主题直接继承Theme.AppCompat.Light.NoActionBar,然后直接在主题中设置状态栏透明:

<activity
    android:name=".MainActivity"
    android:theme="@style/AppTheme.NoActionBar">
    ....
</activity>

.....

<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- 设置系统Status Bar颜色透明 -->
    <item name="android:windowTranslucentStatus">true</item>
    <!-- 设置系统Navigation Bar颜色透明 -->
    <item name="android:windowTranslucentNavigation">true</item>
</style>

当直接使用如上设置时,细心的你可能会发现一个问题:本来在状态栏下面显示的文字,尽然都跑到状态栏中去了而且与状态栏中信息发生重叠。你一看,感觉这操作还不如刚才呢。别急,系统早已为我们提供了一个解决方案,那就是在主题中补充一个android:fitsSystemWindows = true 的属性或者加在Activity对应的布局文件的根属性上。这样就避免了这个问题。

fitSystemWindows属性:

该属性的作用是设置为true时,可以避免应用内容和系统的窗口(statusbar)发生重叠,通过在View上设置和系统窗口一样高度的边框(padding)来确保应用内容不会出现到系统窗口中。这样使得系统会自己计算好布局距状态栏的高度,使界面内容布局位于状态栏下方和导航栏上方。

如果一个布局中的多个view都设置了android:fitsSystemWindows="true"的属性,那么只有第一个View会生效,其他view的设置无效。而且这个View中,再设置android:padding属性会失效。

代码实现

那么一切按照如上配置操作,我们的确可以在4.4中实现状态栏的透明效果,但是并不能随心所欲的达到自定义设置状态栏颜色的效果,而且不具有我们所提倡的插拔式体验。所以我们从代码层面寻求更好的设计。

我们同样可以通过代码来实现windowTranslucentStatus 的效果,如下:

public void setTranslucentStatus(Activity activity, boolean on) {
    Window win = activity.getWindow();
    WindowManager.LayoutParams winParams = win.getAttributes();
    final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
    if (on) {
        winParams.flags |= bits;
    } else {
        winParams.flags &= ~bits;
    }
    win.setAttributes(winParams);
}

因为我们在4.4中不能直接修改状态栏的颜色,所以可以通过创建一个View然后设置为系统状态栏同样的高度,接着将它置于DecorView窗口的顶部将真正的状态栏覆盖,这样就可以改变这个View的背景色来实现状态栏颜色改变。然后设置根布局fitSystemWindows,这样就实现了4.4中的沉浸式状态栏。

public void compat(Activity activity, int statusColor) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        Window win = activity.getWindow();
        setTranslucentStatus(activity,true);
        ViewGroup decorView = (ViewGroup) win.getDecorView();
        decorView.addView(createStatusBarView(activity, statusColor));
        setRootView(activity, true);
    }

}
    
/**
 * create a view which it's height equal system status bar's height
 *
 * @param context
 * @param color
 * @return
 */
private View createStatusBarView(Context context, @ColorInt int color) {
    View statusBarView = new View(context);
    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams
            (FrameLayout.LayoutParams.MATCH_PARENT, getStatusBarHeight(context));
    params.gravity = Gravity.TOP;
    statusBarView.setLayoutParams(params);
    statusBarView.setBackgroundColor(color);
    return statusBarView;
}

/**
 * sets whether or not the root view of layout fitSystemWindows.
 *
 * @param activity
 * @param fitSystemWindows
 */
private void setRootView(Activity activity, boolean fitSystemWindows) {
    ViewGroup parent = activity.findViewById(Window.ID_ANDROID_CONTENT);
    for (int i = 0; i < parent.getChildCount(); i++) {
        View childView = parent.getChildAt(i);
        if (childView instanceof ViewGroup) {
            childView.setFitsSystemWindows(fitSystemWindows);
            ((ViewGroup) childView).setClipToPadding(fitSystemWindows);
        }
    }
}

/**
 * receive the status bar height of the system.
 *
 * @param context
 * @return
 */
private int getStatusBarHeight(Context context) {
    int result = 0;
    int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = context.getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}
5.0(LOLLIPOP)

对于在4.4系统上,我们需要做的很多但是效果却很少,所幸的是只要Android还在发展低版本系统所占的份额只会越来越少以至慢慢被淘汰。而Google官方也已经意识到对开发者开放状态栏操作接口是必要的,因此在5.0的版本上,开发者无需做过多操作就可以直接修改状态栏颜色。

Google直接在Window类中提供了setStatusBarColor方法:

public abstract class Window {

    .....
    
    /**
     * Sets the color of the status bar to {@code color}.
     *
     * For this to take effect,
     * the window must be drawing the system bar backgrounds with
     * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
     * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS} must not be set.
     *
     * If {@code color} is not opaque, consider setting
     * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
     * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
     * <p>
     * The transitionName for the view background will be "android:status:background".
     * </p>
     */
    public abstract void setStatusBarColor(@ColorInt int color);
}

1:设置状态栏颜色

方法中注释中说明了,在设置状态栏颜色的同时,还需要同步设置WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS这个Window Flag,并且需要保证WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS这个Window Flag没有被设置。否则,不会生效。

Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.setStatusBarColor(statusColor);

2:设置状态栏透明

如果我们需要实现状态栏的透明效果同时上浮在布局内容上方,我们可以考虑这样做:

/**
 * set status bar transparent .
 *
 * @param activity
 */
public void setStatusBarTransparent(Activity activity) {

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

        Window window = activity.getWindow();
        View decorView = window.getDecorView();
        int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
        window.setStatusBarColor(Color.TRANSPARENT);
        decorView.setSystemUiVisibility(option);

    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        setTranslucentStatus(activity, true);
    }
}

/**
 * set status bar translucent or not.
 *
 * @param activity
 * @param on
 */
private void setTranslucentStatus(Activity activity, boolean on) {
    Window win = activity.getWindow();
    WindowManager.LayoutParams winParams = win.getAttributes();
    final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
    if (on) {
        winParams.flags |= bits;
    } else {
        winParams.flags &= ~bits;
    }
    win.setAttributes(winParams);
}

注:

这里出现了setSystemUiVisibility中两个View的标记:
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN:视图内容延伸至状态栏区域,状态栏上浮于视图之上。这时再配合View.SYSTEM_UI_FLAG_LAYOUT_STABLE 标记,就能够在status bar隐藏和显示时,内容区域不会改变大小,从而保证布局的稳定。所以这两者经常联合使用。

3:设置全屏
在设置全局显示时,我们同样既可以通过主题设置,也可以通过代码动态设置:

<style name="AppTheme.FullScreen" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowFullscreen">true</item>
</style>

这里我们主要分析代码设置的方法:

/**
 * set systemUI hide or not
 *
 * @param activity
 */
public void setFullScreen(Activity activity, boolean fullScreen) {

    if (fullScreen) {
        Window window = activity.getWindow();
        View decorView = window.getDecorView();
        // Set the IMMERSIVE flag.
        // Set the content to appear under the system bars so that the content
        // doesn't resize when the system bars hide and show.
        int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
                | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
        decorView.setSystemUiVisibility(option);
    } else {
        Window window = activity.getWindow();
        window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    }
}

View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION:视图延伸至导航栏区域,导航栏上浮于视图之上;

View.SYSTEM_UI_FLAG_HIDE_NAVIGATION:暂时隐藏导航栏, 由于导航栏的重要性,当与用户交互后,比如单击屏幕,都可能会导致navigation bar重新出现,源于系统clear掉该标志与SYSTEM_UI_FLAG_FULLSCREEN 标志,同SYSTEM_UI_FLAG_IMMERSIVE 标志一起使用可避免被clear;

View.SYSTEM_UI_FLAG_FULLSCREEN:隐藏状态栏,效果同设置WindowManager.LayoutParams.FLAG_FULLSCREEN;

View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY:沉浸式效果,当用户在系统栏区域向内滑动时,系统栏会显示几秒钟然后重新消失;

View.SYSTEM_UI_FLAG_IMMERSIVE:沉浸式效果,当用户在系统栏区域向内滑动时,系统栏会重新显示并保持可见;(注意与STICKY的区别)

6.0(M)

在Android 6.0中,系统又提供新的方法来改变状态栏中的字体颜色,这样便能够更好的适应系统状态栏背景色。通过setSystemUiVisibility方法设置 View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 可以自行修改状态栏中字体为黑色或者白色。
看API中文档对此的解释有:

/**
 * Flag for {@link #setSystemUiVisibility(int)}: Requests the status bar to draw in a mode that
 * is compatible with light status bar backgrounds.
 *
 * <p>For this to take effect, the window must request
 * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
 *         FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} but not
 * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS
 *         FLAG_TRANSLUCENT_STATUS}.
 *
 * @see android.R.attr#windowLightStatusBar
 */
public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;

提示了在绘制状态栏背景色时,它可以兼容light的模式。而且同样需要设置WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS这个Window Flag,并且需要保证WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS这个Window Flag没有被设置。否则,不会生效。

计算状态栏背景色的light和dark,可以使用系统提供的方法,所以进行如下设置:

Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.setStatusBarColor(statusColor);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    View decorView = window.getDecorView();
    if (decorView != null) {
        int vis = decorView.getSystemUiVisibility();
        if (isLightColor(statusColor)) {
            vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; //black
        } else {
            vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; //white
        }
        decorView.setSystemUiVisibility(vis);
    }
}


/**
 * calculate the color is light or dark.
 *
 * @param color
 * @return
 */
private boolean isLightColor(@ColorInt int color) {
    return ColorUtils.calculateLuminance(color) >= 0.5;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值