Android Transparency/Immersion Status Bar Practice and Source Code Analysis

What is the transparent status bar? Some people say that the transparent status bar is the immersive status bar. There are not many explanations here. See more about it. Why do many users in China refer to Translucent Bars as "immersive top bars"? . This article will explain how to set the transparent status bar, and how to solve the problems encountered to meet various needs.

The transparent status bar mentioned in this article actually refers to extending the top navigation bar to the status bar to make it all in one (the official recommendation of Google is that the color of the status bar is a little darker than the color of the navigation bar), which does not necessarily mean that the background color is not set, such as the navigation bar is white, you can set the status bar to white, depending on the situation.

Compared with iOS system, Android system has a slightly more complex settings for the status bar. Android system provides API 19 or more to set the status bar interface, and until API 23 or more to provide the icon color settings, there are also manufacturers (such as Meizu, millet, etc.) for the status bar has their own customization, for the need to use light background status bar application is not handled properly, often lead to light background, white icon, status bar regardless of you and me sad. Drama.

(Inner os: Hmm? The green battery in the upper right corner, the user must know that it is the status bar, right?

I then compared some of the mainstream app s and found that my Android 5.1 Flyme 4.5 did not support the transparent status bar, which is intolerable for my aesthetic pursuit (actually the need for visual presentation). After several days of tossing and turning, I finally solved these problems, hoping to help you think a little.

To return to the truth, this paper mainly focuses on the following points: 1. Whether to hide the status bar (full screen mode). 2. Setting the background color of the status bar. 3. Setting the color of the status bar icon. In the process of setting the transparent status bar, the icon color settings may be successful, while the background color settings fail, and so on. Solution 4. Source code implementation.

Let's discuss the implementation of transparent status bar in Android from the above points.

Full screen mode (immersive status bar)

In fact, this situation is not very useful, basically using scenes to display advertisements or logo s on flash pages, and some reading app s need to use the screen size as much as possible to show more content.

The setup method is very simple, divided into two kinds, when API > 16:

  • Definition in theme: The theme of the Activty will be customized and added to it

    <item name="android:windowFullscreen">true</item>
  • The code defines:

window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)

Background of status bar

As we all know, in Android 5.0 (API > 21), Google officially provides the interface to set the corresponding status bar background color.

window.setStatusBarColor(@ColorInt int color);

Is it really impossible for us to set the background color below Android 5.0? No, we found that after Android 4.4, the feature of Windows Translucent Statues appeared, so the idea is as follows:

  1. Set the transparent properties of the status bar first

  2. Add a rectangular View the same size as the status bar to the top of the root layout to act as a false status bar

  3. Set the FitsSystem Windows property to true, then the following layout will extend to the status bar, at the top is the view set before, so you can be false.

       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            // Setting status bar transparency
            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            // Generate a rectangular View of the size of the status bar
            View statusView = createStatusView(activity, color);
            ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
            decorView.addView(statusView);
            // Setting parameters for root layout
            ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
            rootView.setFitsSystemWindows(true);
            rootView.setClipToPadding(true);
        }

The code for setting the rectangular color block is as follows:

  private static View createStatusView(Activity activity, int color) {
        // Get the height of the status bar
        int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
        int statusBarHeight = activity.getResources().getDimensionPixelSize(resourceId);

        // Draw a rectangle as high as the status bar
        View statusView = new View(activity);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                statusBarHeight);
        statusView.setLayoutParams(params);
        statusView.setBackgroundColor(color);
        return statusView;
    }

The results are as follows:

The color of the status bar icon

Through research and development, we found that two manufacturers of Meizu and Xiaomi customized the status bar for Flyme and MIUI respectively, so we can replace the icon color above Flyme 4.0 and MIUIV6. Combined with the above status bar background color replacement, we can adapt the light navigation bar for Meizu and Xiaomi models.

Meizu Flyme 4.0+

    /**
     * Meizu Flyme 4 + above transparent status bar
     * @param window 
     * @param dark Whether to set the status bar font and icon color to dark
     * @return Successful setup
     */
    public static boolean setMeizuStatusBarDarkIcon(Window window, boolean dark) {
        boolean result = false;
        if (window != null) {
            try {
                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);
                result = true;
            } catch (Exception e) {
                //error handling
            }
        }
        return result;
    }

MIUIV6.0+

 /**
     * Setting the status bar Font Icon to dark requires MIUIV6 or more
     * @param window
     * @param dark Whether to set the status bar font and icon color to dark
     * @return  Successful setup
     *
     */
    public static boolean setMIUIStatusBarLightMode(Window window, boolean dark) {
        boolean result = false;
        if (window != null) {
            Class clazz = window.getClass();
            try {
                int darkModeFlag = 0;
                Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
                Field  field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
                darkModeFlag = field.getInt(layoutParams);
                Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
                if(dark){
                    extraFlagField.invoke(window,darkModeFlag,darkModeFlag);//State bar transparent and black font
                }else{
                    extraFlagField.invoke(window, 0, darkModeFlag);//Clear Black Fonts
                }
                result=true;
            }catch (Exception e){
                //error handling
            }
        }
        return result;
    }

The results are as follows:

Setting Background Color Failure Handling Scheme

Solutions

Having said so much, children's shoes may ask, the code mentioned above may be effective for most models, but what if some models fail to set up? Not only did the experience not improve, but it was unbearable for users.

Okay, let's see what we can do next. The train of thought is as follows:

  1. Get the background color of the status bar and compare it with the color you need to set. If it is equal, the setting is successful.

  2. If the background color settings fail, for non-Flyme models, we go back to the status bar icon color and restore to the original state.

  3. The reason why we emphasize the non-Flyme model is that children who have used the Flyme model must know that there is a transparent status bar switch. When it is opened, Flyme will process the status bar, but the processing effect is not the same as what we want (for example, the top of the view is black background, but the white status bar is set), and both of them operate on the status bar and impulse the operation. It is also possible that the color of icon is not obvious, so we define a color Drable to replace it.

Get the status bar color

   /**
     * Get the status bar color
     *
     * @param window Activities to be acquired
     * @return color   The color value of the status bar
     */
    public static int getColor(Window window){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return window.getStatusBarColor();
        }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            ViewGroup decorView = (ViewGroup) window.getDecorView();
            int count = decorView.getChildCount();
            if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) {
                int color = Color.TRANSPARENT;
                Drawable background = decorView.getChildAt(count - 1).getBackground();
                if (background instanceof ColorDrawable)
                    color = ((ColorDrawable) background).getColor();
                return color;
            }
        }
        return -1;
    }

Here's how to shield the effect of the Flyme submerged status bar when it opens:

 private static boolean setMeizuStatusColor(Window window, @ColorInt int color, int alpha) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            final View view = window.getDecorView().findViewById(android.R.id.statusBarBackground);
            if (view != null) {
                if (!(view.getBackground() instanceof MeizuColorDrawable)) {
                        //Replace with custom ColorDrawable
                    view.setBackground(new MeizuColorDrawable());
                }

                if (view.getBackground() instanceof MeizuColorDrawable) {
                    MeizuColorDrawable colorDrawable = (MeizuColorDrawable) view.getBackground();
                    int finalColor = StatusBarUtil.calculateStatusColor(color, alpha);
                    view.setBackgroundColor(finalColor);
                    colorDrawable.setColor(finalColor);
                    return true;
                }
            }
        }
        return false;
    }

Bottom up plan

Some failure judgments are mentioned above. What if they are invalid for some models? My suggestion is that a switch can be embedded locally. In case of a problem, the server can turn off the immersion switch of the corresponding model, or use hotfix to repair it.

Source code analysis

Looking at the source code of setting icon for Meizu and Xiaomi above, we know that the common point is to call the corresponding method through reflection, or to set the color for the corresponding status bar view.
Let's look at the source code. In Windows, there is an abstract method called setStatusBarColor. In Phone Windows, we found the implementation of this method.

 @Override
    public void setStatusBarColor(int color) {
        mStatusBarColor = color;
        mForcedStatusBarColor = true;
        if (mDecor != null) {
            mDecor.updateColorViews(null, false /* animate */);
        }
    }

mDecor.updateColorViews()

        private WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
        ...
                 updateColorViewInt(mNavigationColorViewState, sysUiVisibility, mNavigationBarColor,
                        navBarSize, navBarToRightEdge, 0 /* rightInset */,
                        animate && !disallowAnimate);

                boolean statusBarNeedsRightInset = navBarToRightEdge
                        && mNavigationColorViewState.present;
                int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
                updateColorViewInt(mStatusColorViewState, sysUiVisibility, mStatusBarColor,
                        mLastTopInset, false /* matchVertical */, statusBarRightInset,
                        animate && !disallowAnimate);
        ...
}

So what are mNavigation ColorView State and mStatusColorView State?
Don't worry. Let's keep looking down.

       private final ColorViewState mStatusColorViewState = new ColorViewState(
                SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
                Gravity.TOP,
                Gravity.LEFT,
                STATUS_BAR_BACKGROUND_TRANSITION_NAME,
                com.android.internal.R.id.statusBarBackground,
                FLAG_FULLSCREEN);
        private final ColorViewState mNavigationColorViewState = new ColorViewState(
                SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
                Gravity.BOTTOM,
                Gravity.RIGHT,
                NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
                com.android.internal.R.id.navigationBarBackground,
                0 /* hideWindowFlag */);

MNavigation ColorViewState and mStatusColorViewState are both ColorViewState defined by ColorViewState, which mainly stores some view information about statusbar. Including id, translucent Flag, vertical Gravity and so on.

Next, look at the updateColorViewInt() method, which sets the color of a View. Naturally, this View refers to the View of the status bar. Let's look at the implementation below:

private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
              int size, boolean verticalBar, int rightMargin, boolean animate) {
              state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0
                    && (getAttributes().flags & state.hideWindowFlag) == 0
                    && (getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
            boolean show = state.present
                    && (color & Color.BLACK) != 0
                    && (getAttributes().flags & state.translucentFlag) == 0;

            boolean visibilityChanged = false;
            View view = state.view;

            int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
            int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
            int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;

              //Make a non-null judgement on the view of the incoming ColorViewState
            if (view == null) {
                if (show) {
                    //Create a new view
                    state.view = view = new View(mContext);
                    //Setting Background Colors
                    view.setBackgroundColor(color);
                    view.setTransitionName(state.transitionName);
                    view.setId(state.id);
                    visibilityChanged = true;
                    //Some people may wonder why INVISIBLE is set here when show is true. As you can see from the later code, here is nothing more than an animation to do a gradient operation, rather than show it immediately.
                    view.setVisibility(INVISIBLE);
                    state.targetVisibility = VISIBLE;

                    LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
                            resolvedGravity);
                    lp.rightMargin = rightMargin;
                    addView(view, lp);
                    updateColorViewTranslations();
                }
            } else {
                int vis = show ? VISIBLE : INVISIBLE;
                visibilityChanged = state.targetVisibility != vis;
                state.targetVisibility = vis;
                if (show) {
                    LayoutParams lp = (LayoutParams) view.getLayoutParams();
                    if (lp.height != resolvedHeight || lp.width != resolvedWidth
                            || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
                        lp.height = resolvedHeight;
                        lp.width = resolvedWidth;
                        lp.gravity = resolvedGravity;
                        lp.rightMargin = rightMargin;
                        view.setLayoutParams(lp);
                    }
                    //Setting Background Colors
                    view.setBackgroundColor(color);
                }
            }
            //Add animation when changing the visibility property of the status bar
            if (visibilityChanged) {
                view.animate().cancel();
                if (animate) {
                    if (show) {
                        //Set to full transparency when displaying
                        if (view.getVisibility() != VISIBLE) {
                            view.setVisibility(VISIBLE);
                            view.setAlpha(0.0f);
                        }
                        //Animation Interpolator on Transparency
                        view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
                                setDuration(mBarEnterExitDuration);
                    } else {
                        view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
                                .setDuration(mBarEnterExitDuration)
                                .withEndAction(new Runnable() {
                                    @Override
                                    public void run() {
                                        state.view.setAlpha(1.0f);
                                        state.view.setVisibility(INVISIBLE);
                                    }
                                });
                    }
                } else {
                    view.setAlpha(1.0f);
                    view.setVisibility(show ? VISIBLE : INVISIBLE);
                }
            }
        }

summary

Having said so much, I want you to know why many apps give up visual modification of the status bar in the lower version of Android system, especially the application of light navigation bar. [cover your face]

Hope this article can be helpful to you, if there are any questions in this article, welcome to pat the bricks! __________

Finally, welcome to pat bricks and welcome exchanges in places where you don't speak well.~

If you are interested, I would like to add a collection to the migrant workers. Thank you.~

Tags: Java Android Flyme Windows Google

Posted on Tue, 09 Apr 2019 14:30:32 -0400 by LonelyPixel