Android 透明状态栏实现方案

values-v19


    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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>
  • 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 布局代码

"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 中设置了 true 属性后,会发现出现了下面这种情况: 
Toolbar 到了状态栏的下面

Android 透明状态栏实现方案_第1张图片

那么这种情况,网上也已经给出了相关的解决方法:在布局里面设置 android:fitsSystemWindows="true" 属性。

但是这不是最终解决方案,因为我们想要这样的效果:

Android 透明状态栏实现方案_第2张图片

当然,这在 5.0 以上是可以轻松实现的,但是在 Android 4.4 上,却是这样子的:

Android 透明状态栏实现方案_第3张图片

背景图并没有铺满到状态栏的位置,所以,为了兼容 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张图片

Android 透明状态栏实现方案_第5张图片

Android 4.4 上效果出来了,已经实现了我们想要的效果。

但是, Android 5.0+ 上却出问题了;如果已经尝试过自定义 Toolbar 解决 Android 4.4 兼容的朋友可能已经遇到过了: 
(由于不会搞 GIF 图,所以只截了两张图,一张是 Toolbar 收起前,一张是 Toolbar 收起后)

Android 透明状态栏实现方案_第6张图片

Android 透明状态栏实现方案_第7张图片

这又是什么情况??

经过多次查看源码和 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+ 效果图:

Android 透明状态栏实现方案_第8张图片

Android 透明状态栏实现方案_第9张图片

至此,大功告成!

/**************************************************/

也许有人会考虑另一种方法:

  • 只自定义CollapsingToolbarLayout,通过反射给 private WindowInsetsCompat setWindowInsets(windowinsets) 方法设值, 结合android:fitsSystemWindows="true" ,去兼容 Android 4.4

这样不是更简单吗?

我只能很遗憾的告诉你,WindowInsets 相关的类和方法是 API 20+ 才有的。

源码地址:https://github.com/fanxin92/TransparentStatusBarSample

你可能感兴趣的:(android)