values-v19
MainActivity 布局代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:background="@mipmap/bg_toolbar"
android:layout_height="?actionBarSize"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
LinearLayout>
MainActivity 代码:
public class MainActivity extends AppCompatActivity {
private Toolbar mToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_statusbar, menu);
return super.onCreateOptionsMenu(menu);
}
}
DetailActivity 布局代码
"1.0" encoding="utf-8"?>
.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
.support.design.widget.AppBarLayout
android:id="@+id/id_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="180dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:expandedTitleMarginStart="48dp"
app:expandedTitleMarginEnd="64dp"
app:statusBarScrim="@null">
"@+id/id_toolbar_backdrop"
android:scaleType="centerCrop"
android:src="@mipmap/img"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_collapseMode="parallax" />
.support.v7.widget.Toolbar
android:id="@+id/id_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:layout_collapseMode="pin" />
.support.design.widget.CollapsingToolbarLayout>
.support.design.widget.AppBarLayout>
.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
.support.v4.widget.NestedScrollView>
.support.design.widget.CoordinatorLayout>
DetailActivity
public class DetailActivity extends AppCompatActivity {
private Toolbar mToolbar;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
mToolbar = (Toolbar) findViewById(R.id.id_toolbar);
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
ActivityCompat.finishAfterTransition(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}
在 style.xml 中设置了
属性后,会发现出现了下面这种情况:
Toolbar 到了状态栏的下面。
那么这种情况,网上也已经给出了相关的解决方法:在布局里面设置 android:fitsSystemWindows="true"
属性。
但是这不是最终解决方案,因为我们想要这样的效果:
当然,这在 5.0 以上是可以轻松实现的,但是在 Android 4.4 上,却是这样子的:
背景图并没有铺满到状态栏的位置,所以,为了兼容 Android 4.4 ,我们不使用 android:fitsSystemWindows="true"
属性。
有研究过的朋友,可能已经知道我要做什么了。
原理也很简单:就是给 Toolbar 设置一个 PaddingTop。
- 如果有使用过 SystemBarTint
这个库的朋友,应该能看的出来,SystemBarTint 也是通过获取 状态栏高度 来实现的。- SystemBarTint
会新建一个高度等于状态栏高度的 View ,并把 View 添加到 DecorView 。- SystemBarTint 这种方法不是很好,这样 ContentView
区域就少了一块,而且给 Toolbar 设置背景图片也不能铺满到状态栏位置。
自定义 Toolbar:
public class CompatToolbar extends Toolbar {
private boolean mLayoutReady;
public CompatToolbar(Context context) {
this(context, null);
}
public CompatToolbar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, android.support.v7.appcompat.R.attr.toolbarStyle);
}
public CompatToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (!mLayoutReady) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if ((getWindowSystemUiVisibility() &
(SYSTEM_UI_FLAG_LAYOUT_STABLE|SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) ==
(SYSTEM_UI_FLAG_LAYOUT_STABLE|SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) {
int statusBarHeight = getStatusBarHeight();
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getHeight() + statusBarHeight;
setPadding(0, statusBarHeight, 0, 0);
}
}
mLayoutReady = true;
}
}
private int getStatusBarHeight() {
Resources res = Resources.getSystem();
int resId = res.getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0) {
return res.getDimensionPixelSize(resId);
}
return 0;
}
}
自定义 CompatToolbar 主要通过 (getWindowSystemUiVisibility() &
判断是不是透明状态栏,如果是就执行里面的内容:
(SYSTEM_UI_FLAG_LAYOUT_STABLE|SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) ==
(SYSTEM_UI_FLAG_LAYOUT_STABLE|SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
下面修改一下布局,把我们的自定义 CompatToolbar 替换掉原的 Toolbar:
Android 4.4 上效果出来了,已经实现了我们想要的效果。
但是, Android 5.0+ 上却出问题了;如果已经尝试过自定义 Toolbar 解决 Android 4.4 兼容的朋友可能已经遇到过了:
(由于不会搞 GIF 图,所以只截了两张图,一张是 Toolbar 收起前,一张是 Toolbar 收起后)
这又是什么情况??
经过多次查看源码和 Debug 调试,终于发现了问题所在:
在 CollapsingToolbarLayout 的 onLayout 方法中:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
......
// Update our child view offset helpers
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
if (mLastInsets != null && !ViewCompat.getFitsSystemWindows(child)) {
final int insetTop = mLastInsets.getSystemWindowInsetTop();
if (child.getTop() < insetTop) {
// If the child isn't set to fit system windows but is drawing within the inset
// offset it down
ViewCompat.offsetTopAndBottom(child, insetTop);
}
}
getViewOffsetHelper(child).onViewLayout();
}
......
}
在 Android 5.0 以上的系统中, mLastInsets
这个参数是不为空的:
mLastInsets.getSystemWindowInsetTop()
拿到状态栏高度。ViewCompat.offsetTopAndBottom(child, insetTop)
去给子 View (CollapsingToolbarLayout 布局内的 ImageView 和 Toolbar)设置顶部偏移量为 状态栏高度。那么 mLastInsets 又是什么呢?
private WindowInsetsCompat mLastInsets;
从名字可以看出来:窗口插图。那么它是什么时候初始化的呢?
public CollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
......
ViewCompat.setOnApplyWindowInsetsListener(this,
new android.support.v4.view.OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
return setWindowInsets(insets);
}
});
}
private WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
if (mLastInsets != insets) {
mLastInsets = insets;
requestLayout();
}
return insets.consumeSystemWindowInsets();
}
从上面的代码可以看出来, CollapsingToolbarLayout 是在初始化的时候给自己设置了一个窗口插图监听器。
/**
* Set an {@link OnApplyWindowInsetsListener} to take over the policy for applying
* window insets to this view. This will only take effect on devices with API 21 or above.
*/
public static void setOnApplyWindowInsetsListener(View v,
OnApplyWindowInsetsListener listener) {
IMPL.setOnApplyWindowInsetsListener(v, listener);
}
查看 ViewCompat.setOnApplyWindowInsetsListener()
源码可以看到,注释里面写着:此方法只对 Api 21 以上有效。
那么现在可以理解为什么 Android 4.4 上的 mLastInsets
为空了;ViewCompat.setOnApplyWindowInsetsListener()
原来就是为了实现 Android 5.0 以上的插图效果的。
现在清楚了,只要 mLastInsets
为空就能解决这个问题了,可是翻遍了 CollapsingToolbarLayout 的源码只有一个设置 mLastInsets
方法:
private WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
if (mLastInsets != insets) {
mLastInsets = insets;
requestLayout();
}
return insets.consumeSystemWindowInsets();
}
可以看到这是一个私有的方法,也就是说我们用不了这个方法(就算能用也不能传个 null
进去吧……)。
自定义 CompatCollapsingToolbarLayout:
public class CompatCollapsingToolbarLayout extends CollapsingToolbarLayout {
private boolean mLayoutReady;
public CompatCollapsingToolbarLayout(Context context) {
this(context, null);
}
public CompatCollapsingToolbarLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CompatCollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (!mLayoutReady) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT_WATCH) {
if ((getWindowSystemUiVisibility() &
(SYSTEM_UI_FLAG_LAYOUT_STABLE|SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) ==
(SYSTEM_UI_FLAG_LAYOUT_STABLE|SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) {
try {
Field mLastInsets = CollapsingToolbarLayout.class.getDeclaredField("mLastInsets");
mLastInsets.setAccessible(true);
mLastInsets.set(this, null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
mLayoutReady = true;
}
super.onLayout(changed, left, top, right, bottom);
}
}
还是老方法,通过 (getWindowSystemUiVisibility() &
判断是不是透明状态栏,如果是:
(SYSTEM_UI_FLAG_LAYOUT_STABLE|SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) ==
(SYSTEM_UI_FLAG_LAYOUT_STABLE|SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
mLastInsets
,然后置为空。下面是 Androi 5.0+ 效果图:
至此,大功告成!
/**************************************************/
也许有人会考虑另一种方法:
private WindowInsetsCompat setWindowInsets(windowinsets)
方法设值, 结合android:fitsSystemWindows="true"
,去兼容 Android 4.4这样不是更简单吗?
我只能很遗憾的告诉你,WindowInsets 相关的类和方法是 API 20+ 才有的。
源码地址:https://github.com/fanxin92/TransparentStatusBarSample