Android 透明状态栏实现方案
values-v19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
MainActivity 布局代码:
<?xml version="1.0" encoding="utf-8"?><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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
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); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
DetailActivity 布局代码
<?xml version="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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
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); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
在 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; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
自定义 CompatToolbar 主要通过 (getWindowSystemUiVisibility() &
判断是不是透明状态栏,如果是就执行里面的内容:
(SYSTEM_UI_FLAG_LAYOUT_STABLE|SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) ==
(SYSTEM_UI_FLAG_LAYOUT_STABLE|SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
- 获取状态栏高度。
- 修改 父ViewGroup 的 LayoutParams 的高度为 原高度 + 状态栏高度。
- 最后给 Toolbar 设置 PaddingTop 以保证 Toolbar 显示在状态栏下方。
下面修改一下布局,把我们的自定义 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(); } ...... }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
在 Android 5.0 以上的系统中, mLastInsets
这个参数是不为空的:
- 先通过
mLastInsets.getSystemWindowInsetTop()
拿到状态栏高度。 - 然后通过
ViewCompat.offsetTopAndBottom(child, insetTop)
去给子 View (CollapsingToolbarLayout 布局内的 ImageView 和 Toolbar)设置顶部偏移量为 状态栏高度。
那么 mLastInsets 又是什么呢?
private WindowInsetsCompat mLastInsets;
- 1
- 1
从名字可以看出来:窗口插图。那么它是什么时候初始化的呢?
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(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
从上面的代码可以看出来, 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); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
查看 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(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
可以看到这是一个私有的方法,也就是说我们用不了这个方法(就算能用也不能传个 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); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
还是老方法,通过 (getWindowSystemUiVisibility() &
判断是不是透明状态栏,如果是:
(SYSTEM_UI_FLAG_LAYOUT_STABLE|SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) ==
(SYSTEM_UI_FLAG_LAYOUT_STABLE|SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
- 通过反射拿到 CollapsingToolbarLayout 中的变量
mLastInsets
,然后置为空。
下面是 Androi 5.0+ 效果图:
至此,大功告成!
/**************************************************/
也许有人会考虑另一种方法:
- 只自定义CollapsingToolbarLayout,通过反射给
private WindowInsetsCompat setWindowInsets(windowinsets)
方法设值, 结合android:fitsSystemWindows="true"
,去兼容 Android 4.4
这样不是更简单吗?
我只能很遗憾的告诉你,WindowInsets 相关的类和方法是 API 20+ 才有的。
源码地址:https://github.com/fanxin92/TransparentStatusBarSample
更多相关文章
- android获取屏幕长宽的方法
- android 进程自杀再重启的方法
- Android监听HOME键的最简单的方法
- Android适配底部虚拟键盘遮挡布局的解决方案
- android EditText 不自动弹出键盘的方法
- 关于Android Studio关闭模拟器死机解决方案的尝试