我们的App一向是尽量遵循Android的设计,比如顶部的导航栏,我们知道在4.4版本之前Android是不可以自定义状态栏的,在4.4版本Android推出了一个透明状态栏的概念,使手机最顶部的状态栏的颜色全透明,并且颜色可以定义,而5.0推出了Material Design,这个时候的状态栏就变成了半透明的颜色。
首先Toolbar是在appcompact-v7包下面,所以第一步需要依赖该库,方式很简单,gradle依赖加入如下代码:compile ‘com.android.support:appcompat-v7:27.0.2’
自定义Toolbar设置(layout_toolbar.xml):
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/toolBarHeight"
android:background="@color/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:paddingTop="@dimen/toolBarPaddingTop"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:contentInsetStart="@dimen/dd_0">
<RelativeLayout
android:id="@+id/toolbar_content_ly"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/pd_25"
android:visibility="visible">
<ImageView
android:id="@+id/toolbar_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/dp_10"
android:focusable="false"
android:src="@drawable/ic_title_back_b"
android:visibility="gone" />
<TextView
android:id="@+id/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/app_first_tv"
android:textSize="@dimen/ts_18" />
<View
android:id="@+id/toolbar_line"
android:layout_width="match_parent"
android:layout_height="@dimen/dd_0_5"
android:layout_alignParentBottom="true"
android:background="@color/app_line" />
</RelativeLayout>
</android.support.v7.widget.Toolbar>
这里给它设一个id与你app的主颜色,然后关于其他属性android:minHeight、app:theme、app:popupTheme等就不在解释,关键是在这里加了一个paddingTop的属性,这里的解决方案就是利用状态栏的高度,在4.4以上的版本给Toolbar设置一个paddingTop的属性为status_bar的高度,然后让他“全屏”,Toolbar就自然的延伸到了status_bar的位置,而且在4.4系统status_bar默认就是透明,在5.0以上status_bar是半透明,paddingTop的属性为status_bar的高度设置为0dp
status_bar的高度从如下代码得知为25dp
最后在values-v19文件夹下声明AppTheme为透明状态栏,代码如下、
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:windowTranslucentStatus">true</item>
</style>
这句话意为在api大于等于19以上版本为状态栏透明
以上便解决了第一个问题,那么接下来第二个问题的解决方案就是抽象成一个BaseActivity,在
方法中实现支持toolBar
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 这句很关键,注意是调用父类的方法
super.setContentView(R.layout.activity_base);
// 经测试在代码里直接声明透明状态栏更有效
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WindowManager.LayoutParams localLayoutParams = getWindow().getAttributes();
localLayoutParams.flags = (WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | localLayoutParams.flags);
}
initToolbar();
}
private void initToolbar() {
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
if (toolbar != null) {
setSupportActionBar(toolbar);
}
}
layout_toolbar在布局中引用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar_layout" />
</LinearLayout>
在android4.4系统上状态栏的背景是渐变半透明。而在android5.0+的系统上又有所不同又有差异:
在模拟器、nexus6p的android5.0+系统上是半透明。
在xiaomi、oppo、huawei、leshi等5.0+系统是全透明的。
从效果图也可以看出,上面两种方式使得内容区域延伸到状态栏下面去了。我们可以添加android:fitsSystemWindows
来避免这样的情况。
在Activity的根布局文件中添加
android:fitsSystemWindows="true"
或者在主题style文件中添加
<item name="android:fitsSystemWindows">true</item>
效果如下
android4.4
模拟器android5.0+
小米5android6.0
上图为了方便观察状态栏的背景颜色,就将Activity的布局文件的背景色设置成了红色。可以看出Toolbar所在的内容区域没有沉浸到状态栏下面了,并且状态栏的背景颜色还是之前一样,只是将内容区域向下偏移了状态栏高度的距离。这是因为fitsSystemWindows
属性使得布局的paddingTop
被重新改写了(paddingTop
增加了状态栏的高度)。
要实现沉浸式的状态栏,其实就是状态栏的背景颜色和Toolbar的颜色一样。那么我们将Activity的背景色改为Toolbar紫色(内容区域的背景颜色设置为白色)看下效果:
android4.4
模拟器android5.0+
小米5android6.0
除了通过fitsSystemWindows
这个属性,我们可以自己给在Toolbar的上方添加一个和状态栏高度一样的Veiw,并将这个View的背景色设置成和Toolbar的背景色一样。也可以直接给Toobar设置paddingTop
等于状态栏高度值。
为了方便,我一般是采用重写Toolbar,改写它的paddingTop
值来实现。
/**
* Created by xiaoyanger on 2017/3/1.
* 沉浸式、版本兼容的Toolbar,状态栏透明.
*/
public class CompatToolbar extends Toolbar {
public CompatToolbar(Context context) {
this(context, null);
}
public CompatToolbar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CompatToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setup();
}
public void setup() {
int compatPadingTop = 0;
// android 4.4以上将Toolbar添加状态栏高度的上边距,沉浸到状态栏下方
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
compatPadingTop = getStatusBarHeight();
}
this.setPadding(getPaddingLeft(), getPaddingTop() + compatPadingTop, getPaddingRight(), getPaddingBottom());
}
public int getStatusBarHeight() {
int statusBarHeight = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
}
Log.d("CompatToolbar", "状态栏高度:" + px2dp(statusBarHeight) + "dp");
return statusBarHeight;
}
public float px2dp(float pxVal) {
final float scale = getContext().getResources().getDisplayMetrics().density;
return (pxVal / scale);
}
}
需要注意的是,网上有很多文章说状态栏的高度是25dp
,但实际测试后发现并不是所有的机型都是25dp
。使用getStatusBarHeight()
可以准确的获取状态栏的高度值,看下获取到高度的日志:
android4.4模拟器
android6.0模拟器
oppo r9
小米5
各个机型获取到的状态栏高度很多都不一样,所以最好还是重写Toolbar,动态获取到系统状态栏的高度来改写它的上边距。
国产的大部分手机在android5.0+的系统都将原生的半透明状态栏改成了全透明,因此通过上面的方式基本达到了沉浸式体验。
其实在原生android5.0+系统上可以通过这个方法使状态栏背景色完全透明:
@Override
protected void onCreate(Bundle savedInstanceState) {
// 5.0以上系统状态栏透明
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
模拟器(原生系统)android5.0+
可以将上面两个方法封装到BaseActivity
中,以便在各个界面直接调用:
protected void setTranslucentStatus() {
// 5.0以上系统状态栏透明
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
通过setTranslucentStatus()
方法,4.4的系统状态栏背景色渐变半透明,绝大多数5.0+的系统状态栏背景色全透明(测试时发现有部分华为手机状态栏背景是浅紫色,估计是定制系统给修改了)。
需要注意的是,不管是<item name="android:windowTranslucentStatus">true</item>
还是setTranslucentStatus()
方法,这两种方式都会使这个设置<itemname="colorPrimaryDark">@color/colorPrimaryDark</item>
不会有任何效果。
有时候会遇到这样的需求,状态栏和Toolbar背景色都是白底黑字,比如简书消息界面:
简书消息界面Toobar和状态栏白底黑字
状态栏的白色背景可以通过上面的方式来实现,但是修改状态栏的文字和图标颜色就比较麻烦了,原生的android系统只有在6.0+才有官方的api,但是在MIUI和FlymeUI提供了相应的方法。
/**
* 设置Android状态栏的字体颜色,状态栏为亮色的时候字体和图标是黑色,状态栏为暗色的时候字体和图标为白色
*
* @param dark 状态栏字体和图标是否为深色
*/
protected void setStatusBarFontDark(boolean dark) {
// 小米MIUI
try {
Window window = getWindow();
Class clazz = getWindow().getClass();
Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
int darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
if (dark) { //状态栏亮色且黑色字体
extraFlagField.invoke(window, darkModeFlag, darkModeFlag);
} else { //清除黑色字体
extraFlagField.invoke(window, 0, darkModeFlag);
}
} catch (Exception e) {
e.printStackTrace();
}
// 魅族FlymeUI
try {
Window window = getWindow();
WindowManager.LayoutParams lp = window.getAttributes();
Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags");
darkFlag.setAccessible(true);
meizuFlags.setAccessible(true);
int bit = darkFlag.getInt(null);
int value = meizuFlags.getInt(lp);
if (dark) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
window.setAttributes(lp);
} catch (Exception e) {
e.printStackTrace();
}
// android6.0+系统
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (dark) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
}
状态栏和Toobar白底黑字只能适配到android6.0+以及小米和魅族手机,其他手机的只能调整UI设计了。可以看一下简书和掘金,在小米、魅族和android6.0+的系统上都是白底黑字,但是在其他的系统上一般是将状态栏的背景色设置为浅灰色。
简书消息界面oppo r9(5.1系统)
其实状态栏显示的颜色是灰色,但是有多种方式来实现,参考下简书、掘金、今日头条,他们都使用了右滑返回上一界面,在滑动过程中就能看出状态背景色的实现。
简书
掘金
今日头条
简书的效果可以不用上面的方式使内容区域沉浸到状态栏下面,直接使用的<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
直接修改的状态栏的颜色。
掘金的状态栏是半透明的,内容区域已经沉浸到状态栏下方,可以使用上面的方法,对于不是android6.0+、MIUI、FlymeUI的系统通过window.setStatusbarColor
方法将状态栏颜色改为半透明的灰色。
今日头条的效果可以采用上面的方法实现沉浸式,状态栏全透明,Toolbar没有增加paddingTop
,而是在第二个界面中状态栏的下方(Toolbar上方)加了一块同状态栏高度一样的View,颜色为灰色。
其实,从上面三个app的状态栏适配效果来看,要达到真正的沉浸式,今日头条的效果要好一些。但是今日头条并没有在各个机型上适配状态栏的字体和图标颜色。
下面自己来实现不同机型的状态栏背景色和字体图标颜色的适配。
首先,新建一个CompatStartusbarActivity
,继承自BaseActivity
:
public class CompatStatusBarActivity extends BaseActivity {
private FrameLayout mFrameLayoutContent;
private View mViewStatusBarPlace;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_compat_status_bar);
mViewStatusBarPlace = findViewById(R.id.view_status_bar_place);
mFrameLayoutContent = (FrameLayout) findViewById(R.id.frame_layout_content_place);
ViewGroup.LayoutParams params = mViewStatusBarPlace.getLayoutParams();
params.height = getStatusBarHeight();
mViewStatusBarPlace.setLayoutParams(params);
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
View contentView = LayoutInflater.from(this).inflate(layoutResID, null);
mFrameLayoutContent.addView(contentView);
}
/**
* 设置沉浸式状态栏
*
* @param fontIconDark 状态栏字体和图标颜色是否为深色
*/
protected void setImmersiveStatusBar(boolean fontIconDark, int statusBarColor) {
setTranslucentStatus();
if (fontIconDark) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|| OsUtil.isMIUI()
|| OsUtil.isFlyme()) {
setStatusBarFontIconDark(true);
} else {
if (statusBarColor == Color.WHITE) {
statusBarColor = 0xffcccccc;
}
}
}
setStatusBarPlaceColor(statusBarColor);
}
private void setStatusBarPlaceColor(int statusColor) {
if (mViewStatusBarPlace != null) {
mViewStatusBarPlace.setBackgroundColor(statusColor);
}
}
public int getStatusBarHeight() {
int statusBarHeight = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
}
return statusBarHeight;
}
/**
* 设置状态栏透明
*/
private void setTranslucentStatus() {
// 5.0以上系统状态栏透明
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
/**
* 设置Android状态栏的字体颜色,状态栏为亮色的时候字体和图标是黑色,状态栏为暗色的时候字体和图标为白色
*
* @param dark 状态栏字体是否为深色
*/
private void setStatusBarFontIconDark(boolean dark) {
// 小米MIUI
try {
Window window = getWindow();
Class clazz = getWindow().getClass();
Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
int darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
if (dark) { //状态栏亮色且黑色字体
extraFlagField.invoke(window, darkModeFlag, darkModeFlag);
} else { //清除黑色字体
extraFlagField.invoke(window, 0, darkModeFlag);
}
} catch (Exception e) {
e.printStackTrace();
}
// 魅族FlymeUI
try {
Window window = getWindow();
WindowManager.LayoutParams lp = window.getAttributes();
Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags");
darkFlag.setAccessible(true);
meizuFlags.setAccessible(true);
int bit = darkFlag.getInt(null);
int value = meizuFlags.getInt(lp);
if (dark) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
window.setAttributes(lp);
} catch (Exception e) {
e.printStackTrace();
}
// android6.0+系统
// 这个设置和在xml的style文件中用这个<item name="android:windowLightStatusBar">true</item>属性是一样的
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (dark) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
}
}
布局文件activity_compat_status_bar.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.xiao.compatstatusbar.MainActivity">
<View
android:id="@+id/view_status_bar_place"
android:layout_width="match_parent"
android:layout_height="25dp"/>
<FrameLayout
android:id="@+id/frame_layout_content_place"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
setImmersiveStatusBar
方法中中判断了不能修改状态栏字体和图标颜色则将白色的状态栏底色改成灰色,能修改状态栏字体和图标的颜色则改为给定的颜色statusBarColor
。使用的时候直接调用这个方法即可。
下面是使用示例:MainActivity
继承自CompatStatusBarActivity
:
public class MainActivity extends CompatStatusBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_main);
int color = 0xffaa66cc;
toolbar.setBackgroundColor(color);
setImmersiveStatusBar(false, color);
}
public void go(View view) {
startActivity(new Intent(this, NextActivity.class));
}
}
布局文件activity_main.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.xiao.compatstatusbar.MainActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_main"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/colorPrimary">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="标题"
android:textColor="@android:color/white"
android:textSize="18sp"/>
</LinearLayout>
</android.support.v7.widget.Toolbar>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#99FF4081"
android:onClick="go"
android:text="页面1"
android:textSize="44sp"/>
</LinearLayout>
NextActivity
同样继承自CompatStatusBarActivity
:
public class NextActivity extends CompatStatusBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_next);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_next);
int color = 0xffffffff;
toolbar.setBackgroundColor(color);
setImmersiveStatusBar(true, color);
}
}
布局文件activity_next.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.xiao.compatstatusbar.MainActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_next"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/colorPrimary">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="标题"
android:textColor="#353535"
android:textSize="18sp"/>
</LinearLayout>
</android.support.v7.widget.Toolbar>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#99FF4081"
android:text="页面2"
android:textSize="44sp"/>
</LinearLayout>
最终适配效果:
模拟器android4.4
模拟器android5.0
模拟器android6.0
oppo r9
mi5