Android4.4及以上版本实现状态栏与顶栏同色

在Android中实现状态栏与顶栏同色,可以增加应用的美观性,提升用户体验。那么这一点是如何实现的呢?

目前,比较常用的是github上一个叫做SystemBarTint的库。但是在实际使用的过程中,当与碎总的SwipeBackLayout滑 动返回一起使用的时候,会发现通知栏并不能和当前页面一起被滑走,在StatusBar与ActionBar连接的地方会有很明显的撕裂感,非常影响用户 体验。所以在我们自己的项目中放弃了这种实现方式并寻求其他解决方案,最终通过阅读SwipeBackLayout源码和SystemBarTint源码 找到了解决方案,实现了状态栏和顶栏同色与滑动返回的完美结合。

首先我们通过SystemBarTint的源码分析来搞明白状态栏和变色是如何实现的

/**
     * Constructor. Call this in the host activity onCreate method after its
     * content view has been set. You should always create new instances when
     * the host activity is recreated.
     *
     * @param activity The host activity.
     */
    @TargetApi(19)
    public SystemBarTintManager(Activity activity) {
        Window win = activity.getWindow();
        ViewGroup decorViewGroup = (ViewGroup) win.getDecorView();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            // check theme attrs
            int\[\] attrs = {android.R.attr.windowTranslucentStatus,
                    android.R.attr.windowTranslucentNavigation};
            TypedArray a = activity.obtainStyledAttributes(attrs);
            try {
                mStatusBarAvailable = a.getBoolean(0, false);
                mNavBarAvailable = a.getBoolean(1, false);
            } finally {
                a.recycle();
            }
            // check window flags
            WindowManager.LayoutParams winParams = win.getAttributes();
            int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
            if ((winParams.flags & bits) != 0) {
                mStatusBarAvailable = true;
            }
            bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
            if ((winParams.flags & bits) != 0) {
                mNavBarAvailable = true;
            }
        }
        mConfig = new SystemBarConfig(activity, mStatusBarAvailable, mNavBarAvailable);
        // device might not have virtual navigation keys
        if (!mConfig.hasNavigtionBar()) {
            mNavBarAvailable = false;
        }
        if (mStatusBarAvailable) {
            setupStatusBarView(activity, decorViewGroup);
        }
        if (mNavBarAvailable) {
            setupNavBarView(activity, decorViewGroup);
        }
    }

可以看到在27~31行通过条件判断首先设置statusbar为透明(只有Android4.4及以上的系统才可以设置statusbar为透明,这也 是状态栏变色只在Android4.4及以上系统有效的原因),将statusbar设置为透明之后,statusbar下边的那一层黑条就不见了,当前 页面的内容也就会顶到屏幕最上边了(statusbar和navigationbar其实是两个window)。接下来,我们再来看一段代码

private void setupStatusBarView(Context context, ViewGroup decorViewGroup) {
        mStatusBarTintView = new View(context);
        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mConfig.getStatusBarHeight());
        params.gravity = Gravity.TOP;
        if (mNavBarAvailable && !mConfig.isNavigationAtBottom()) {
            params.rightMargin = mConfig.getNavigationBarWidth();
        }
        mStatusBarTintView.setLayoutParams(params);
        mStatusBarTintView.setBackgroundColor(DEFAULT_TINT_COLOR);
        mStatusBarTintView.setVisibility(View.GONE);
        decorViewGroup.addView(mStatusBarTintView);
    }

通过这一段代码可以看出,SystemBarTint通过给decorview add了一个和statusbar高度一样的view进去。

至此,我们应该知道SystemBarTint是如何实现状态栏变色的了,接下来我们再看看SwipeBackLayout的源码

首先,我们来看SwipeBackActivity这个类

public class SwipeBackActivity extends ActionBarActivity implements SwipeBackActivityBase {
    private SwipeBackActivityHelper mHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHelper = new SwipeBackActivityHelper(this);
        mHelper.onActivityCreate();
    }
    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mHelper.onPostCreate();
    }
    @Override
    public View findViewById(int id) {
        View v = super.findViewById(id);
        if (v == null && mHelper != null)
            return mHelper.findViewById(id);
        return v;
    }
    @Override
    public SwipeBackLayout getSwipeBackLayout() {
        return mHelper.getSwipeBackLayout();
    }
    @Override
    public void setSwipeBackEnable(boolean enable) {
        getSwipeBackLayout().setEnableGesture(enable);
    }
    @Override
    public void scrollToFinishActivity() {
        getSwipeBackLayout().scrollToFinishActivity();
    }
}

可以看到在14行的地方有一个mHelper.onPostCreate(),我们接着往下看

public class SwipeBackActivityHelper {
    private Activity mActivity;
    private SwipeBackLayout mSwipeBackLayout;
    public SwipeBackActivityHelper(Activity activity) {
        mActivity = activity;
    }
    @SuppressWarnings("deprecation")
    public void onActivityCreate() {
        mActivity.getWindow().setBackgroundDrawable(new ColorDrawable(0));
        mActivity.getWindow().getDecorView().setBackgroundDrawable(null);
        mSwipeBackLayout = (SwipeBackLayout) LayoutInflater.from(mActivity).inflate(
                me.imid.swipebacklayout.lib.R.layout.swipeback_layout, null);
        mSwipeBackLayout.addSwipeListener(new SwipeBackLayout.SwipeListener() {
            @Override
            public void onScrollStateChange(int state, float scrollPercent) {
            }
            @Override
            public void onEdgeTouch(int edgeFlag) {
            }
            @Override
            public void onScrollOverThreshold() {
            }
        });
    }
    public void onPostCreate() {
        mSwipeBackLayout.attachToActivity(mActivity);
    }
    public View findViewById(int id) {
        if (mSwipeBackLayout != null) {
            return mSwipeBackLayout.findViewById(id);
        }
        return null;
    }
    public SwipeBackLayout getSwipeBackLayout() {
        return mSwipeBackLayout;
    }
}

可以发现在33行的位置有一个mSwipeBackLayout.attachToActivity(mActivity),我们接着看下去

public void attachToActivity(Activity activity) {
        mActivity = activity;
        TypedArray a = activity.getTheme().obtainStyledAttributes(new int\[\] {
            android.R.attr.windowBackground
        });
        int background = a.getResourceId(0, 0);
        a.recycle();
        ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
        ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
        decorChild.setBackgroundResource(background);
        decor.removeView(decorChild);
        addView(decorChild);
        setContentView(decorChild);
        decor.addView(this);
    }

终于,我们找到了最初始的位置,通过这一段代码,我们会发现SwipeBackLayout的原理其实就是获取到当前页面的DecorView然后 再获取到这棵view树位于0位置的view并将其移除,之后再把SwipeBackLayout(通过源码可以知道这个Layout是继承自 FrameLayout的) add到DecorView里边来。

综合分析了SystemBarTint和SwipebackLayout的源码之后,我们也就知道为什么statusbar不能一起划走的原因了, 因为SystemBarTint是给DecorView add了一个和statusbar同高的view,而SwipeBackLayout也是DecorView的子view,这两个view在同一层次上, 所以不能一起滑走。知道原因之后,解决方案也就浮出了水面,我们可以在把那个view add到SwipwBacklayout里边来,这样子就可以一起滑走了。下面代码是我们项目里边的实现方式

@TargetApi(Build.VERSION_CODES.KITKAT)
    protected void setSystemBarTransparent() {
        Window window = getWindow();
        WindowManager.LayoutParams layoutParams = window.getAttributes();
        layoutParams.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
        if (immerseNavigationBar() && !SmartBarUtils.isMeizu()) {
            layoutParams.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
        }
        window.setAttributes(layoutParams);
        if (shouldHackStatusBarTransparent()) {
            hackStatusBarTransparent();
            setContentPadding();
        }
    }
    protected void setContentPadding() {
        int actionBarHeight = mHasActionBarHeight ? CommonUtils.getActionBarHeight(this) : 0;
        ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0).setPadding(0,
                CommonUtils.getStatusBarHeight(this) + actionBarHeight, 0, 0);
    }
    protected void hackStatusBarTransparent() {
        Window window = getWindow();
        ViewGroup contentFrame = (ViewGroup) window.getDecorView().findViewById(
                android.R.id.content);
        View hackView = new View(this);
        setStatusViewBackground(hackView);
        contentFrame.addView(hackView, ViewGroup.LayoutParams.MATCH_PARENT,
                CommonUtils.getStatusBarHeight(this));
    }

可以看到我们是把这个view add到content这一层了,也不再需要用到SystemBarTint这个库了。

最后通过hierarchyviewer工具load出来一个view布局很容易看明白这个问题,截图如下