The original link cloud.tencent.com/developer/a…

There are many times in APP development when we need to achieve an immersive experience like the one below.

Immersive experience

At the beginning of the experience, it seems that everyone will find it difficult to implement. The difficulty is:

  1. As the background image of the head is pushed up, it gradually becomes invisible, the color of the whole area becomes dark, and then the title appears.
  2. The StatusBar becomes transparent and the space can be used, which is the top of our picture.
  3. When our viewPager is pushed below the Actionbar, it is fixed below the Actionbar and cannot be pushed above it.
  4. There is a control at the bottom that slides the list up and out of view range to give more room for the list to display, but the whole immersive experience is about giving more room for the list to display.

Ok, to sum up, the above are our problems, but also need to be solved, one by one, this demand will be realized, so, how do we go step by step to solve the above problems?

1. Fade in and out of the header background and title

First of all, let us analysis the first question, the background of the head in the process of up, slowly become invisible, this sounds like some kind of collapse, therefore, it is easy to think of CollapsingToolbarLayout, CollapsingToolbarLayout if you want to easily understand CollapsingToolbarLayout

Application, I suggest to see this brother’s article, he also gave an animation, a more detailed introduction to the application of this, such as:

CollapsingToolbarLayout

For usage, I am not be explained here, but if you don’t understand the application of the layout, I strongly suggest you to look at, can continue to go below, just want to explain, go here, do you have a need to fill in the pit, that is what we can not like that, the title of the animation and or title or centered, note that here, This is the Android design specification, but the designer doesn’t buy the Android specification. Therefore, we have to lay on this hole and then learn from Stack Overflow.

<android.support.v7.widget.Toolbar
    android:id="@+id/toolbar_top"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:minHeight="? attr/actionBarSize"
    android:background="@color/action_bar_bkgnd"
    app:theme="@style/ToolBarTheme" >


     <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Toolbar Title"
        android:layout_gravity="center"
        android:id="@+id/toolbar_title" />


</android.support.v7.widget.Toolbar>Copy the code

Assuming that this works, to solve the centring problem, change the back button to our button style, and then, after a bit of trickery, make the title transparent and change the image of the back button:

collapsingToolbarLayout.setCollapsedTitleTextColor(Color.WHITE);
//collapsingToolbarLayout.setExpandedTitleColor(Color.WHITE);
collapsingToolbarLayout.setExpandedTitleColor(Color.TRANSPARENT);Copy the code

However, the hypothesis, which is always an assumption, is actually not true. When I tried it, I found that the TextView in the Toolbar couldn’t use the Android :layout_gravity=”center” property at all. Well, even if it was forced, the effect would be left.

So, how do I do that? My solution is this

<android.support.design.widget.AppBarLayout
            android:id="@+id/appbarlayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:elevation="0dp">

            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/collapsing_tool_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:contentScrim="@color/b_G6"
                app:expandedTitleMarginEnd="10dp"
                app:expandedTitleMarginStart="10dp"
                app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

                <android.support.constraint.ConstraintLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">

                    <ImageView
                        android:id="@+id/igame_arena_rank_class_header_bg"
                        android:layout_width="match_parent"
                        android:layout_height="0dp"
                        android:scaleType="centerCrop"
                        android:src="@drawable/bg_arena_rank_class"
                        app:layout_constraintDimensionRatio="375:156"/ >... </android.support.constraint.ConstraintLayout> <android.support.v7.widget.Toolbar android:id="@+id/common_index_activity_tb_title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="? android:attr/actionBarSize"
                    android:visibility="visible"
                    app:contentInsetLeft="0dp"
                    app:contentInsetStart="0dp"
                    app:layout_collapseMode="pin">

                    <include
                        layout="@layout/igame_common_tool_bar"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center" />
                </android.support.v7.widget.Toolbar>


            </android.support.design.widget.CollapsingToolbarLayout>

        </android.support.design.widget.AppBarLayout>Copy the code

And then, the layout inside the include looks like this

<? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"> //***** please note the View*******// <View android:id="@+id/common_index_activity_view_status_bar"
        android:layout_width="match_parent"
        android:layout_height="0dp" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="50dp">

        <TextView
            android:id="@+id/tv_toolbar_bg"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_centerInParent="true"
            tools:background="@color/b_G6" />

        <TextView
            android:id="@+id/common_index_header_tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:textColor="@color/b_G99"
            android:textSize="@dimen/igame_textsize_xl"
            tools:text="Here's the title." />


        <RelativeLayout
            android:id="@+id/common_index_header_rl_back"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_centerVertical="true"
            android:layout_gravity="center_vertical"
            android:visibility="visible">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_centerInParent="true"
                android:contentDescription="@string/image_desc"
                android:scaleType="centerInside"
                android:src="@drawable/igame_actionbar_arrow_left" />
        </RelativeLayout>

    </RelativeLayout>
</LinearLayout>
Copy the code

That’s what it looks like

Of course, at this point, the title needs to fade in and out yourself. So, what do we rely on?

appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { @Override public void onOffsetChanged(AppBarLayout appBarLayout, Int verticalOffset) {mTitle. SetAlpha (- verticalOffset * 1.0 f/appBarLayout. GetTotalScrollRange ()); }});Copy the code

This is based on listening to appBarLayout.

2. Make the statusBar transparent and use its space for our layout content.

/** * make the status bar transparent and overwrite the status bar. The interface with API greater than 19 is displayed normally, but the interface with API smaller than 19 is extended to the status bar. But the status bar is not transparent * / @ TargetApi (Build. VERSION_CODES. KITKAT) public static void transparentAndCoverStatusBar (Activity Activity) { //FLAG_LAYOUT_NO_LIMITS Models with virtual buttons can be particularly problematic // //FLAG_TRANSLUCENT_STATUS requires an API greater than 19 // activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); // activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); / / / / FLAG_LAYOUT_NO_LIMITS for API does not require / / activity. The getWindow () addFlags (WindowManager. LayoutParams. FLAG_LAYOUT_NO_LIMITS);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(Color.TRANSPARENT);
            window.setNavigationBarColor(Resources.getSystem().getColor(android.R.color.background_dark));
        } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Window window = activity.getWindow(); window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); }}Copy the code

Here is a method found on the Internet, directly call, but the API needs to be greater than 19, I believe that the current basically meet it. Notice that I don’t have this property in my AppBarLayout

android:fitsSystemWindows="true"Copy the code

If you add this property, hey, hey, the Statusbar, while the space is available, has a color that you can’t get out of it,

And then, you remember in the layout above

//***** <View android:id="@+id/common_index_activity_view_status_bar"
        android:layout_width="match_parent"
        android:layout_height="0dp" />Copy the code

The height of the View is dynamically changed to the height of the statusBar. This is used to offset the original space of the status_bar.

/** * get the status bar height ** @param context context * @returnStatus bar height */ public static int getStatusBarHeight(Context Context) {// getStatusBarHeight int resourceId = context.getResources().getIdentifier("status_bar_height"."dimen"."android");
        return context.getResources().getDimensionPixelSize(resourceId);
    }
Copy the code

Once we’re done, we need to set the height of the toolbar we inserted ourselves to add the height of the Toolbar plus the height of the StatusBar.

3, ViewPager pushed below the Actionbar will not be pushed

CollapsingToolbarLayout has a child view that uses pin mode in the CollapsingToolbarLayout, so that child view is clearly the toolbar

<android.support.v7.widget.Toolbar
                    android:id="@+id/common_index_activity_tb_title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="? android:attr/actionBarSize"
                    android:visibility="visible"
                    app:contentInsetLeft="0dp"
                    app:contentInsetStart="0dp"
                    app:layout_collapseMode="pin">

                    <include
                        layout="@layout/igame_common_tool_bar"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center" />
                </android.support.v7.widget.Toolbar>
Copy the code

4. The bottom control is gradually hidden as the list slides

As you can see, the control at the bottom is overlaid on the list. When the list slides up, hide it to free up more controls to view the list. So how do you do that?

Since we are wrapped in CoordinatorLayout, obviously, the best way is to use layout_behavior, and I’ve implemented a BottomBehavior here:

public class BottomBehavior extends CoordinatorLayout.Behavior {
    private int id;
    private float bottomPadding;
    private int screenWidth;
    private floatDesignWidth = 375.0 f; // The width of the design view, usually 375dp, is publicBottomBehavior() {
        super();
    }

    public BottomBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        screenWidth = getScreenWidth(context);
        TypedArray typedArray = context.getResources().obtainAttributes(attrs, R.styleable.BottomBehavior);
        id = typedArray.getResourceId(R.styleable.BottomBehavior_anchor_id, -1);
        bottomPadding = typedArray.getFloat(R.styleable.BottomBehavior_bottom_padding, 0f);
        typedArray.recycle();
    }

    @Override
    public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {
        params.dodgeInsetEdges = Gravity.BOTTOM;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        return dependency.getId() == id;
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        child.setTranslationY(-(dependency.getTop() - (screenWidth * bottomPadding / designWidth)));
        Log.e("BottomBehavior"."layoutDependsOn() called with: parent = [" + dependency.getTop());
        return true;
    }


    public static int getScreenWidth(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = null;
        if(wm ! = null) { display = wm.getDefaultDisplay(); Point size = new Point(); display.getSize(size); int width = size.x; // int height = size.y;return width;
        }
        return0; }}Copy the code

So this one has two custom properties in it, ID, bottomPadding, which is based on which control is changing relative position, and I’m going to base this on viewPager

A layoutDependsOn will only be called if onDependentViewChanged returns ture. BottomPadding represents an initial offset, since the viewPager itself is not at the top of the screen (the image occupies part of the control at the beginning), so you need to subtract that portion of possession.

In the same way, adding a hover on the left, right, slide to hide, stop to show, can also refer to a similar Behavior to reduce code coupling.

conclusion

The final layout looks something like this

<? xml version="1.0" encoding="utf-8"? > <com.tencent.igame.view.common.widget.IGameRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/igame_competition_detail_fragment_refresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.AppBarLayout
            android:id="@+id/appbarlayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:elevation="0dp">

            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/collapsing_tool_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:contentScrim="@color/b_G6"
                app:expandedTitleMarginEnd="10dp"
                app:expandedTitleMarginStart="10dp"
                app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

                <android.support.constraint.ConstraintLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">

                    <ImageView
                        android:id="@+id/igame_arena_rank_class_header_bg"
                        android:layout_width="match_parent"
                        android:layout_height="0dp"
                        android:scaleType="centerCrop"
                        android:src="@drawable/bg_arena_rank_class"
                        app:layout_constraintDimensionRatio="375:156"/ >... </android.support.constraint.ConstraintLayout> <android.support.v7.widget.Toolbar android:id="@+id/common_index_activity_tb_title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="? android:attr/actionBarSize"
                    android:visibility="visible"
                    app:contentInsetLeft="0dp"
                    app:contentInsetStart="0dp"
                    app:layout_collapseMode="pin">

                    <include
                        layout="@layout/igame_common_tool_bar"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center" />
                </android.support.v7.widget.Toolbar>


            </android.support.design.widget.CollapsingToolbarLayout>

        </android.support.design.widget.AppBarLayout>

        <com.tencent.igame.widget.viewpager.IgameViewPager
            android:id="@+id/igame_arena_rank_class_vp_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />

        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_gravity="bottom"
            android:background="@color/b_G6"
            android:paddingLeft="12dp"
            android:paddingRight="12dp"
            app:anchor_id="@+id/igame_arena_rank_class_vp_content"
            app:bottom_padding="156.0"
            app:layout_behavior="com.tencent.igame.common.widget.BottomBehavior">... At the bottom of the layout < / android. Support. The constraint. ConstraintLayout > < / android. Support. The design. The widget. CoordinatorLayout > </com.tencent.igame.view.common.widget.IGameRefreshLayout>Copy the code

Note: IGameRefreshLayout is actually the encapsulated PullToRefreshView, IgameViewPager is the encapsulated Viewpager, reduce the need to write Viewpager routine code every time.

With this framework, you can easily create a layout like this.