preface

CollapsingToolbarLayout is a component provided by Android MaterialDeign that collapses the Toolbar with AppBarLayout. Below through the imitation of rare earth digging personal center page to explain its specific use. First, the effect picture:

implementation

We split the Toolbars into two layers:

  • The large module in the red circle, working name: A
  • Module in the green circle, provisional name: B

Through stratified reanalysis, the results are as follows:

  • As the view scrolls up, A gradually collapses
  • When A is fully folded, the status of B’s icon is updated, and the user’s information is displayed floating upwards in B

  • When A is not fully folded, the user information disappears and the icon in B is restored to its original state

Through effect analysis, we can design as follows:

  • In CollapsingToolbarLayout, the content of A can be collapsed by cooperating with CoordinatorLayout+AppBarLayout
  • Monitor whether the contents of A are folded and change the state of B

CollapsingToolbarLayout Collapses the listener for the collapse event

  1. Through AppBarLayout. OnOffsetChangedListener callback
Interface definition for a callback to be invoked when an AppBarLayout's vertical offset changes.
// TODO(b/76413401): update this interface after the widget migration
public interface OnOffsetChangedListener extends BaseOnOffsetChangedListener<AppBarLayout> {
  void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset);
}
Copy the code

Check the official API documentation for this callback to get the vertical offset of AppBarLayout. The state of the current toolbar is obtained by determining the size of the offset

abstract class AppBarStateChangeListener : AppBarLayout.OnOffsetChangedListener { enum class State { EXPANDED, COLLAPSED, IDLE } private var mCurrentState = State.IDLE override fun onOffsetChanged(appBarLayout: AppBarLayout? , verticalOffset: Int) { if (verticalOffset == 0) { if (mCurrentState ! = State.EXPANDED) { onStateChanged(appBarLayout, State.EXPANDED); } mCurrentState = State.EXPANDED } else if (appBarLayout ! = null) { if (Math.abs(verticalOffset) >= appBarLayout.totalScrollRange) { if (mCurrentState ! = State.COLLAPSED) { onStateChanged(appBarLayout, State.COLLAPSED); } mCurrentState = State.COLLAPSED } else { if (mCurrentState ! = State.IDLE) { onStateChanged(appBarLayout, State.IDLE); } mCurrentState = State.IDLE; } } } abstract fun onStateChanged(appBarLayout: AppBarLayout? , state: State) }Copy the code
  1. View the setScrimsShown function in CollapsingToolbarLayout
Set whether the content scrim and/or status bar scrim should be shown or not. Any change in the vertical scroll may overwrite this value. Params: Shown -- whether the scrims should be shown animate -- whether to animate the visibility change See Also:getStatusBarScrim(), getContentScrim() public void setScrimsShown(boolean shown, boolean animate) { if (scrimsAreShown ! = shown) { if (animate) { animateScrim(shown ? 0xFF : 0x0); } else { setScrimAlpha(shown ? 0xFF : 0x0); } scrimsAreShown = shown; }}Copy the code

Looking at the official API documentation, you can see that this method is automatically called when the content changes, so we can extend this method

class NestCollapsingToolbarLayout : CollapsingToolbarLayout { private var mIsScrimsShown: Boolean = false private lateinit var scrimsShowListener: OnScrimsShowListener constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet? , defStyleAttr: Int) : super( context, attrs, defStyleAttr ) override fun setScrimsShown(shown: Boolean, animate: Boolean) { super.setScrimsShown(shown, animate) if (mIsScrimsShown ! = shown) { mIsScrimsShown = shown if (scrimsShowListener ! = null) { scrimsShowListener.onScrimsShowChange(this, mIsScrimsShown) } } } fun setOnScrimesShowListener(listener: OnScrimsShowListener){ scrimsShowListener = listener } interface OnScrimsShowListener { fun onScrimsShowChange( nestCollapsingToolbarLayout: NestCollapsingToolbarLayout, isScrimesShow: Boolean ) } }Copy the code

Method 1 listens for the height of the content in the entire AppBarLayout, while method 2 only listens for the height in the CollapsingToolbarLayout. I’m going to use method two here

The layout design

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <com.example.behaviordemo.NestCollapsingToolbarLayout
                android:id="@+id/toolbarLayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                app:layout_scrollFlags="scroll|exitUntilCollapsed">

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                    <ImageView
                        android:layout_width="match_parent"
                        android:layout_height="150dp"
                        android:scaleType="centerCrop"
                        android:src="@drawable/user_profile_header" />

                    <ImageView
                        android:id="@+id/ivHead"
                        android:layout_width="60dp"
                        android:layout_height="60dp"
                        android:layout_marginLeft="10dp"
                        android:layout_marginTop="125dp"
                        android:background="@drawable/empty_avatar_user" />

                    <LinearLayout
                        android:id="@+id/llAuthor"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_below="@+id/ivHead"
                        android:layout_marginLeft="10dp"
                        android:layout_marginTop="10dp"
                        android:gravity="center_vertical"
                        android:orientation="horizontal">

                        <TextView
                            android:id="@+id/author_name"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:text="PG_KING"
                            android:textColor="@android:color/black"
                            android:textSize="16sp" />

                        <ImageView
                            android:id="@+id/ivLevel"
                            android:layout_width="27dp"
                            android:layout_height="15dp"
                            android:layout_marginLeft="5dp"
                            android:background="@drawable/ic_user_big_lv1" />
                    </LinearLayout>


                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_below="@+id/llAuthor"
                        android:layout_marginLeft="8dp"
                        android:text="Android"
                        android:textColor="@android:color/darker_gray"
                        android:textSize="13sp" />


                    <TextView
                        android:layout_width="80dp"
                        android:layout_height="35dp"
                        android:layout_alignTop="@+id/llAuthor"
                        android:layout_alignParentRight="true"
                        android:layout_marginRight="10dp"
                        android:background="@drawable/bg_edit"
                        android:gravity="center"
                        android:text="编辑"
                        android:textColor="@color/theme_blue"
                        android:textSize="15sp" />
                </RelativeLayout>
            </com.example.behaviordemo.NestCollapsingToolbarLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="@android:color/white"
                android:orientation="horizontal">

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:gravity="center_vertical"
                    android:orientation="vertical"
                    android:paddingLeft="10dp">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="10000"
                        android:textColor="@android:color/black"
                        android:textSize="14sp" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="关注"
                        android:textColor="@android:color/darker_gray"
                        android:textSize="13sp" />
                </LinearLayout>

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:gravity="center_vertical"
                    android:orientation="vertical"
                    android:paddingLeft="10dp">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="10000"
                        android:textColor="@android:color/black"
                        android:textSize="14sp" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="关注"
                        android:textColor="@android:color/darker_gray"
                        android:textSize="13sp" />
                </LinearLayout>

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:gravity="center_vertical"
                    android:orientation="vertical"
                    android:paddingLeft="10dp">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="10000"
                        android:textColor="@android:color/black"
                        android:textSize="14sp" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="关注"
                        android:textColor="@android:color/darker_gray"
                        android:textSize="13sp" />
                </LinearLayout>
            </LinearLayout>

            <View
                android:layout_width="match_parent"
                android:layout_height="10dp"
                android:background="@android:color/darker_gray" />

            <com.google.android.material.tabs.TabLayout
                android:id="@+id/tabLayout"
                android:layout_width="match_parent"
                android:layout_height="60dp"
                app:tabIndicatorColor="@color/theme_blue"
                app:tabIndicatorFullWidth="false"
                app:tabSelectedTextColor="@color/theme_blue"
                app:tabTextColor="@android:color/darker_gray" />

        </com.google.android.material.appbar.AppBarLayout>

        <androidx.viewpager.widget.ViewPager
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/white"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />
    </androidx.coordinatorlayout.widget.CoordinatorLayout>

    <RelativeLayout
        android:id="@+id/rlTitle"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:paddingLeft="15dp"
        android:paddingRight="15dp">

        <ImageView
            android:id="@+id/ivBack"
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_centerVertical="true"
            android:src="@drawable/ic_back" />

        <LinearLayout
            android:id="@+id/llSmallAuthor"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginLeft="30dp"
            android:layout_toRightOf="@+id/ivBack"
            android:gravity="center_vertical"
            android:orientation="horizontal"
            android:visibility="invisible">

            <ImageView
                android:layout_width="35dp"
                android:layout_height="35dp"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/empty_avatar_user" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:text="PG_KING"
                android:textColor="@android:color/black"
                android:textSize="16sp" />

            <ImageView
                android:layout_width="27dp"
                android:layout_height="15dp"
                android:layout_marginLeft="5dp"
                android:background="@drawable/ic_user_big_lv1" />
        </LinearLayout>

        <ImageView
            android:id="@+id/ivShare"
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:src="@drawable/ic_share" />

        <ImageView
            android:id="@+id/ivUserData"
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_centerVertical="true"
            android:layout_marginRight="30dp"
            android:layout_toLeftOf="@+id/ivShare"
            android:src="@drawable/ic_userdata" />

    </RelativeLayout>

</FrameLayout>
Copy the code
  • To CollapsingToolbarLayout, you must choose the CoordinatorLayout as the root layout
  • CollapsingToolbarLayout need to set up the app: layout_scrollFlags = “scroll | exitUntilCollapsed”
  • Add app:layout_behavior to RecyclerView or any view that supports nested scrolling, such as NestedScrollView

Achieve scrolling effect

appBarLayout.setOnScrimesShowListener(object :
    NestCollapsingToolbarLayout.OnScrimsShowListener {
    override fun onScrimsShowChange(
        nestCollapsingToolbarLayout: NestCollapsingToolbarLayout,
        isScrimesShow: Boolean
    ) {
        if (isScrimesShow) {
            rlTitle.setBackgroundColor(Color.WHITE)
            ivBack.setImageResource(R.drawable.ic_back_blue)
            ivShare.setImageResource(R.drawable.ic_share_blue)
            ivUserData.setImageDrawable(
                tintDrawable(
                    resources.getDrawable(R.drawable.ic_userdata),
                    ColorStateList.valueOf(Color.parseColor("#B6B6B6"))
                )
            )
            showSmallAuthor()
        } else {
            rlTitle.setBackgroundColor(Color.TRANSPARENT)
            ivBack.setImageResource(R.drawable.ic_back)
            ivShare.setImageResource(R.drawable.ic_share)
            ivUserData.setImageDrawable(
                tintDrawable(
                    ivUserData.drawable,
                    ColorStateList.valueOf(Color.parseColor("#FFFFFF"))
                )
            )
            if (objectAnimator.isRunning) {
                objectAnimator.cancel()
            }
            llSmallAuthor.visibility = View.INVISIBLE
        }
    }

})
Copy the code

supplement

A combination of TabLayout+ViewPager is also used to implement the logic of the main content area. Associated binding of tabLayout and ViewPager

pagerAdapter = object : ListPagerAdapter(supportFragmentManager, fragments) {
    override fun getPageTitle(position: Int): CharSequence? {
        if (tabList.size > position) {
            return tabList.get(position)
        }
        return ""
    }
}
viewPager.adapter = pagerAdapter
tabLayout.setupWithViewPager(viewPager)
Copy the code

2. Dynamically change the color of drawable

See above, the statistical histogram change from white to gray ~ there is no switch to use two pictures, but use, is the system to provide DrawableCompat. SetTintList function

fun tintDrawable(drawable: Drawable, colors: ColorStateList): Drawable { val wrappedDrawable = DrawableCompat.wrap(drawable) DrawableCompat.setTintList(wrappedDrawable, Colors) return wrappedDrawable} / / using ivUserData setImageDrawable (tintDrawable ( resources.getDrawable(R.drawable.ic_userdata), ColorStateList.valueOf(Color.parseColor("#B6B6B6")) ) )Copy the code

expand

App :layout_scrollFlags property introduction

<attr name="layout_scrollFlags"> <! -- Disable scrolling on the view. This flag should not be combined with any of the other scroll flags. --> <flag name="noScroll" value="0x0"/> <! -- The view will be scroll in direct relation to scroll events. This flag needs to be set for any of the other flags to take effect. If any sibling views before this one do not have this flag, then this value has no effect. --> <flag name="scroll" value="0x1"/> <! -- When exiting (scrolling off screen) the view will be scrolled until it is 'collapsed'. The collapsed height is defined by the view's minimum height. --> <flag name="exitUntilCollapsed" value="0x2"/> <! -- When entering (scrolling on screen) the view will scroll on any downwards scroll event, regardless of whether the scrolling view is also scrolling. This is commonly referred to as the 'quick return' pattern. --> <flag name="enterAlways" value="0x4"/> <! -- An additional flag for 'enterAlways' which modifies the returning view to only initially scroll back to it's collapsed height. Once the scrolling view has reached the end of it's scroll range, the remainder of this view will be scrolled into view. --> <flag name="enterAlwaysCollapsed" value="0x8"/> <! -- Upon a scroll ending, if the view is only partially visible then it will be snapped and scrolled to it's closest edge. --> <flag name="snap" value="0x10"/> <! -- An additional flag to be used with 'snap'. If set, the view will be snapped to its top and bottom margins, as opposed to the edges of the view itself. --> <flag name="snapMargins" value="0x20"/> </attr>Copy the code

Check the official API documentation, let’s sort out the overall:

  • The use of other properties need to cooperate with the scroll, otherwise you will have no effect, the proper use, for example: app: layout_scrollFlags = “scroll | exitUntilCollapsed”
  • EnterAlways: Whether scroll down or up, the first scroll is childView in AppBarLayout
  • ExitUntilCollapsed: Children view from AppBarLayout Scroll down, giving priority to other views, and scroll AppBarLayout until the other views have been scrolled to the top
  • Snap: Can be used in conjunction with other properties to ensure that parts of AppBarLayout are fully displayed or completely hidden

Specific effects can be experienced by writing examples. Running over the effect of understanding is not so boring ~

Finally, the demo address is attached: gitee-Demo