How to have a collapsing of top view into a smaller sized view?

and*_*per 8 android android-collapsingtoolbarlayout android-appbarlayout

This question was asked before in a too broad and unclear way here, so I've made it much more specific, with full explanation and code of what I've tried.

Background

I'm required to mimic the way the Google Calendar has a view at the top, that can animate and push down the view at the bottom, yet it has extra, different behavior. I've summarized what I'm trying to do on 3 characteristics:

  1. Pressing on the toolbar will always work, to toggle expanding/collapsing of the top view, while having an arrow icon that changes its rotation. This is like on Google Calendar app.
  2. The top view will always snap, just like on Google Calendar app.
  3. 折叠顶视图时,只需按工具栏上的按钮即可展开它.这就像Google日历应用
  4. 展开顶视图时,在底部视图中滚动只允许折叠.如果您尝试向另一个方向滚动,则不会发生任何事情,甚至不会出现在底部视图中.这就像Google日历应用
  5. 折叠后,顶视图将替换为较小的视图.这意味着它总是需要一些空间,在底部视图上方.这与Google日历应用不同,因为在日历应用中,顶视图在折叠后会完全消失.

以下是Google日历应用的外观:

enter image description here

滚动底部视图也会慢慢隐藏顶部的视图:

enter image description here

问题

使用我过去发现的各种解决方案,我成功地只实现了所需行为的一部分:

  1. 在工具栏中有一些UI是通过在其中包含一些视图来完成的,包括箭头视图.对于手动扩展/折叠,我setExpandedAppBarLayout视图上使用.对于箭头的旋转,我使用一个监听器来AppBarLayout调整已调整大小的数量addOnOffsetChangedListener.

  2. 通过将snap值添加到layout_scrollFlags属性中可以轻松完成捕捉CollapsingToolbarLayout.但是,为了使它真正运行良好,没有奇怪的问题(在此报道),我使用了这个解决方案.

  3. 滚动时阻止影响顶视图可以通过使用我在#2(此处)上使用的相同代码,通过调用来完成setExpandEnabled.这适用于顶视图折叠时.

  4. 类似于#3,但遗憾的是,因为它使用setNestedScrollingEnabled了两个方向,所以只有顶视图折叠时才能正常工作.当它扩展时,它仍然允许底部视图向上滚动,而不是日历应用程序.展开后,我需要它只允许折叠,而不允许真正滚动.

这是好的和坏的示范:

enter image description here

  1. 我完全失败了.我已经尝试了很多我想过的解决方案,用不同的标志在各个地方放置视图.

简而言之,我成功地做了1-3,但不是4-5.

代码

这是当前的代码(这里也可以作为整个项目使用):

ScrollingActivity.kt

class ScrollingActivity : AppCompatActivity(), AppBarTracking {

    private var mNestedView: MyRecyclerView? = null
    private var mAppBarOffset: Int = 0
    private var mAppBarIdle = false
    private var mAppBarMaxOffset: Int = 0

    private var isExpanded: Boolean = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_scrolling)
        val toolbar = findViewById<Toolbar>(R.id.toolbar)
        setSupportActionBar(toolbar)
        mNestedView = findViewById(R.id.nestedView)
        app_bar.addOnOffsetChangedListener({ appBarLayout, verticalOffset ->
            mAppBarOffset = verticalOffset
            val totalScrollRange = appBarLayout.totalScrollRange
            val progress = (-verticalOffset).toFloat() / totalScrollRange
            arrowImageView.rotation = 180 + progress * 180
            isExpanded = verticalOffset == 0;
            mAppBarIdle = mAppBarOffset >= 0 || mAppBarOffset <= mAppBarMaxOffset
            if (mAppBarIdle)
                setExpandAndCollapseEnabled(isExpanded)
        })

        app_bar.post(Runnable { mAppBarMaxOffset = -app_bar.totalScrollRange })

        mNestedView!!.setAppBarTracking(this)
        mNestedView!!.layoutManager = LinearLayoutManager(this)
        mNestedView!!.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            override fun getItemCount(): Int = 100

            override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
                return object : ViewHolder(LayoutInflater.from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false)) {}
            }

            override fun onBindViewHolder(holder: ViewHolder, position: Int) {
                (holder.itemView.findViewById<View>(android.R.id.text1) as TextView).text = "item $position"
            }
        }

        expandCollapseButton.setOnClickListener({ v ->
            isExpanded = !isExpanded
            app_bar.setExpanded(isExpanded, true)
        })
    }

    private fun setExpandAndCollapseEnabled(enabled: Boolean) {
        mNestedView!!.isNestedScrollingEnabled = enabled
    }

    override fun isAppBarExpanded(): Boolean = mAppBarOffset == 0
    override fun isAppBarIdle(): Boolean = mAppBarIdle
}
Run Code Online (Sandbox Code Playgroud)

MyRecyclerView.kt

/**A RecyclerView that allows temporary pausing of casuing its scroll to affect appBarLayout, based on https://stackoverflow.com/a/45338791/878126 */
class MyRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : RecyclerView(context, attrs, defStyle) {
    private var mAppBarTracking: AppBarTracking? = null
    private var mView: View? = null
    private var mTopPos: Int = 0
    private var mLayoutManager: LinearLayoutManager? = null

    interface AppBarTracking {
        fun isAppBarIdle(): Boolean
        fun isAppBarExpanded(): Boolean
    }

    override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?,
                                         type: Int): Boolean {
        if (type == ViewCompat.TYPE_NON_TOUCH && mAppBarTracking!!.isAppBarIdle()
                && isNestedScrollingEnabled) {
            if (dy > 0) {
                if (mAppBarTracking!!.isAppBarExpanded()) {
                    consumed!![1] = dy
                    return true
                }
            } else {
                mTopPos = mLayoutManager!!.findFirstVisibleItemPosition()
                if (mTopPos == 0) {
                    mView = mLayoutManager!!.findViewByPosition(mTopPos)
                    if (-mView!!.top + dy <= 0) {
                        consumed!![1] = dy - mView!!.top
                        return true
                    }
                }
            }
        }

        val returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
        if (offsetInWindow != null && !isNestedScrollingEnabled && offsetInWindow[1] != 0)
            offsetInWindow[1] = 0
        return returnValue
    }

    override fun setLayoutManager(layout: RecyclerView.LayoutManager) {
        super.setLayoutManager(layout)
        mLayoutManager = layoutManager as LinearLayoutManager
    }

    fun setAppBarTracking(appBarTracking: AppBarTracking) {
        mAppBarTracking = appBarTracking
    }

}
Run Code Online (Sandbox Code Playgroud)

ScrollingCalendarBehavior.kt

class ScrollingCalendarBehavior(context: Context, attrs: AttributeSet) : AppBarLayout.Behavior(context, attrs) {
    override fun onInterceptTouchEvent(parent: CoordinatorLayout?, child: AppBarLayout?, ev: MotionEvent): Boolean = false
}
Run Code Online (Sandbox Code Playgroud)

activity_scrolling.xml

<android.support.design.widget.CoordinatorLayout
    android:id="@+id/coordinatorLayout" 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" tools:context=".ScrollingActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="wrap_content"
        android:fitsSystemWindows="true" android:stateListAnimator="@null" android:theme="@style/AppTheme.AppBarOverlay"
        app:expanded="false" app:layout_behavior="com.example.user.expandingtopviewtest.ScrollingCalendarBehavior"
        tools:targetApi="lollipop">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsingToolbarLayout" android:layout_width="match_parent"
            android:layout_height="match_parent" android:fitsSystemWindows="true"
            android:minHeight="?attr/actionBarSize" app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" app:statusBarScrim="?attr/colorPrimaryDark">

            <LinearLayout
                android:layout_width="match_parent" android:layout_height="250dp"
                android:layout_marginTop="?attr/actionBarSize" app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="1.0">

                <TextView
                    android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="10dp"
                    android:paddingRight="10dp" android:text="some large, expanded view"/>
            </LinearLayout>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar" android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay">

                <android.support.constraint.ConstraintLayout
                    android:id="@+id/expandCollapseButton" android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize" android:background="?android:selectableItemBackground"
                    android:clickable="true" android:focusable="true" android:orientation="vertical">

                    <TextView
                        android:id="@+id/titleTextView" android:layout_width="wrap_content"
                        android:layout_height="wrap_content" android:layout_marginBottom="8dp"
                        android:layout_marginLeft="8dp" android:layout_marginStart="8dp" android:ellipsize="end"
                        android:gravity="center" android:maxLines="1" android:text="title"
                        android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
                        android:textColor="@android:color/white" app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintStart_toStartOf="parent"/>

                    <ImageView
                        android:id="@+id/arrowImageView" android:layout_width="wrap_content" android:layout_height="0dp"
                        android:layout_marginLeft="8dp" android:layout_marginStart="8dp"
                        app:layout_constraintBottom_toBottomOf="@+id/titleTextView"
                        app:layout_constraintStart_toEndOf="@+id/titleTextView"
                        app:layout_constraintTop_toTopOf="@+id/titleTextView"
                        app:srcCompat="@android:drawable/arrow_down_float"
                        tools:ignore="ContentDescription,RtlHardcoded"/>

                </android.support.constraint.ConstraintLayout>
            </android.support.v7.widget.Toolbar>

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

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

    <com.example.user.expandingtopviewtest.MyRecyclerView
        android:id="@+id/nestedView" android:layout_width="match_parent" android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".ScrollingActivity"/>

</android.support.design.widget.CoordinatorLayout>
Run Code Online (Sandbox Code Playgroud)

问题

  1. 如何在展开顶视图时阻止滚动,但在滚动时允许折叠?

  2. 如何在折叠时将顶视图替换为较小的视图(在展开时返回大视图),而不是完全消失?


更新

即使我已经了解了我所提出的基本知识,但目前的代码仍有2个问题(在Github上可用,这里):

  1. The small view (the one you see on collapsed state) has inner views that need to have a clicking effect on them. When using the android:background="?attr/selectableItemBackgroundBorderless" on them, and clicking on this area while being expanded, the clicking is done on the small view. I've handled it by putting the small view on a different toolbar, but then the clicking effect doesn't get shown at all. I've written about this here, including sample project.

Here's the fix:

<android.support.design.widget.CoordinatorLayout
    android:id="@+id/coordinatorLayout" 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" tools:context=".MainActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="wrap_content"
        android:fitsSystemWindows="true" android:stateListAnimator="@null" android:theme="@style/AppTheme.AppBarOverlay"
        app:expanded="false" app:layout_behavior="com.example.expandedtopviewtestupdate.ScrollingCalendarBehavior"
        tools:targetApi="lollipop">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsingToolbarLayout" android:layout_width="match_parent"
            android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false"
            android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"
            app:statusBarScrim="?attr/colorPrimaryDark">

            <!--large view -->
            <LinearLayout
                android:id="@+id/largeView" android:layout_width="match_parent" android:layout_height="280dp"
                android:layout_marginTop="?attr/actionBarSize" android:orientation="vertical"
                app:layout_collapseMode="parallax" app:layout_collapseParallaxMultiplier="1.0">

                <TextView
                    android:id="@+id/largeTextView" android:layout_width="match_parent"
                    android:layout_height="match_parent" android:layout_gravity="center"
                    android:background="?attr/selectableItemBackgroundBorderless" android:clickable="true"
                    android:focusable="true" android:focusableInTouchMode="false" android:gravity="center"
                    android:text="largeView" android:textSize="14dp" tools:background="?attr/colorPrimary"
                    tools:layout_gravity="top|center_horizontal" tools:layout_height="40dp" tools:layout_width="40dp"
                    tools:text="1"/>

            </LinearLayout>

            <!--top toolbar-->
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/small_view_height" app:contentInsetStart="0dp"
                app:layout_collapseMode="pin" app:popupTheme="@style/AppTheme.PopupOverlay">

                <android.support.constraint.ConstraintLayout
                    android:layout_width="match_parent" android:layout_height="wrap_content" android:clickable="true"
                    android:focusable="true">

                    <LinearLayout
                        android:id="@+id/expandCollapseButton" android:layout_width="match_parent"
                        android:layout_height="?attr/actionBarSize"
                        android:background="?android:selectableItemBackground" android:gravity="center_vertical"
                        android:orientation="horizontal" app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toTopOf="parent">

                        <TextView
                            android:id="@+id/titleTextView" android:layout_width="wrap_content"
                            android:layout_height="wrap_content" android:ellipsize="end" android:gravity="center"
                            android:maxLines="1" android:text="title"
                            android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
                            android:textColor="@android:color/white"/>

                        <ImageView
                            android:id="@+id/arrowImageView" android:layout_width="wrap_content"
                            android:layout_height="wrap_content" android:layout_marginLeft="8dp"
                            android:layout_marginStart="8dp" app:srcCompat="@android:drawable/arrow_up_float"
                            tools:ignore="ContentDescription,RtlHardcoded"/>
                    </LinearLayout>

                </android.support.constraint.ConstraintLayout>

            </android.support.v7.widget.Toolbar>

            <android.support.v7.widget.Toolbar
                android:id="@+id/smallLayoutContainer" android:layout_width="match_parent"
                android:layout_height="wrap_content" android:layout_marginTop="?attr/actionBarSize"
                android:clipChildren="false" android:clipToPadding="false" app:contentInsetStart="0dp"
                app:layout_collapseMode="pin">
                <!--small view-->
                <LinearLayout
                    android:id="@+id/smallLayout" android:layout_width="match_parent"
                    android:layout_height="@dimen/small_view_height" android:clipChildren="false"
                    android:clipToPadding="false" android:orientation="horizontal" tools:background="#ff330000"
                    tools:layout_height="@dimen/small_view_height">

                    <TextView
                        android:id="@+id/smallTextView" android:layout_width="match_parent"
                        android:layout_height="match_parent" android:layout_gravity="center"
                        android:background="?attr/selectableItemBackgroundBorderless" android:clickable="true"
                        android:focusable="true" android:focusableInTouchMode="false" android:gravity="center"
                        android:text="smallView" android:textSize="14dp" tools:background="?attr/colorPrimary"
                        tools:layout_gravity="top|center_horizontal" tools:layout_height="40dp"
                        tools:layout_width="40dp" tools:text="1"/>

                </LinearLayout>
            </android.support.v7.widget.Toolbar>
        </android.support.design.widget.CollapsingToolbarLayout>

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

    <com.example.expandedtopviewtestupdate.MyRecyclerView
        android:id="@+id/nestedView" android:layout_width="match_parent" android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".ScrollingActivity"/>

</android.support.design.widget.CoordinatorLayout>
Run Code Online (Sandbox Code Playgroud)
  1. The Google Calendar allows to perform a scroll-down gesture on the toolbar itself, to trigger showing the month view. I've succeeded only adding a clicking event there, but not scrolling. Here's how it looks like:

enter image description here

Che*_*amp 8

注意:此处提供完整更新的项目.

如何在展开顶视图时阻止滚动,但在滚动时允许折叠?

问题1:RecyclerView不应该能够在不倒塌应用栏在所有滚动.要解决此问题,请添加enterAlways滚动标志,CollapsingToolbarLayout如下所示:

<android.support.design.widget.CollapsingToolbarLayout
    android:id="@+id/collapsingToolbarLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:fitsSystemWindows="true"
    app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"
    app:statusBarScrim="?attr/colorPrimaryDark">
Run Code Online (Sandbox Code Playgroud)

enterAlways 不会导致应用栏在关闭时打开,因为您正在抑制该功能,但按其他方式工作.

问题#2:当应用栏完全展开时,RecyclerView不应该允许向上滚动.这恰好是问题#1的一个独特问题.

[更新]要更正此问题,请RecyclerViewRecyclerView尝试向上滚动且应用栏完全展开时修改要使用滚动的行为,或者在使用scroll(dy)后完全展开.在RecyclerView可以滚动起来,但它从来没有看到因为它的行为动作,SlidingPanelBehavior,消耗滚动.如果应用栏未完全展开但在当前滚动消耗后将展开,则该行为会强制应用栏完全展开,方法dy是在完全使用滚动之前调用修改并调用超级.(见SlidingPanelBehavior#onNestedPreScroll()).(在上一个答案中,appBar行为已被修改.更改行为更改RecyclerView是一种更好的选择.)

问题#3:RecyclerView当嵌套滚动已经处于所需状态时,为启用/禁用设置嵌套滚动会导致问题.要避免这些问题,只有在通过以下代码更改进行更改时才更改嵌套滚动的状态ScrollingActivity:

private void setExpandAndCollapseEnabled(boolean enabled) {
    if (mNestedView.isNestedScrollingEnabled() != enabled) {
        mNestedView.setNestedScrollingEnabled(enabled);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是测试应用程序与上述更改的行为:

在此输入图像描述

具有上述更改的已更改模块位于帖子的末尾.

如何在折叠时将顶视图替换为较小的视图(在展开时返回大视图),而不是完全消失?

[更新]让较小的视图成为直接的孩子,CollapsingToolbarLayout因此它是兄弟姐妹Toolbar.以下是此方法的演示.所述 collapseMode较小视图的被设定为pin.调整较小视图的边距以及工具栏的边距,以使较小的视图位于工具栏的正下方.由于CollapsingToolbarLayout是a FrameLayout,视图堆栈和FrameLayout刚刚成为最高子视图的高度.此结构将避免插件需要调整的问题以及缺少点击效果的问题.

最后一个问题仍然存在,拖动应用栏应该打开它,假设拖动较小的视图不应该打开appbar.允许appbar在拖动时打开是完成setDragCallbackAppBarLayout.Behavior.由于较小的视图已合并到appBar中,因此将其向下拖动将打开appbar.为防止这种情况,调用的新行为将MyAppBarBehavior附加到appbar.此行为与此代码中的代码一起MainActivity阻止拖动较小的视图以打开appbar但允许拖动工具栏.

activity_main.xml中

<android.support.design.widget.CoordinatorLayout 
    android:id="@+id/coordinatorLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:stateListAnimator="@null"
        android:theme="@style/AppTheme.AppBarOverlay"
        app:expanded="false"
        app:layout_behavior=".MyAppBarBehavior"
        tools:targetApi="lollipop">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsingToolbarLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipChildren="false"
            android:clipToPadding="false"
            android:fitsSystemWindows="true"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"
            app:statusBarScrim="?attr/colorPrimaryDark">

            <!--large view -->
            <LinearLayout
                android:id="@+id/largeView"
                android:layout_width="match_parent"
                android:layout_height="280dp"
                android:layout_marginTop="?attr/actionBarSize"
                android:orientation="vertical"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="1.0">

                <TextView
                    android:id="@+id/largeTextView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_gravity="center"
                    android:background="?attr/selectableItemBackgroundBorderless"
                    android:clickable="true"
                    android:focusable="true"
                    android:focusableInTouchMode="false"
                    android:gravity="center"
                    android:text="largeView"
                    android:textSize="14dp"
                    tools:background="?attr/colorPrimary"
                    tools:layout_gravity="top|center_horizontal"
                    tools:layout_height="40dp"
                    tools:layout_width="40dp"
                    tools:text="1" />

            </LinearLayout>

            <!--top toolbar-->
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/small_view_height"
                app:contentInsetStart="0dp"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay">

                <android.support.constraint.ConstraintLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:clickable="true"
                    android:focusable="true">

                    <LinearLayout
                        android:id="@+id/expandCollapseButton"
                        android:layout_width="match_parent"
                        android:layout_height="?attr/actionBarSize"
                        android:background="?android:selectableItemBackground"
                        android:gravity="center_vertical"
                        android:orientation="horizontal"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toTopOf="parent">

                        <TextView
                            android:id="@+id/titleTextView"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:ellipsize="end"
                            android:gravity="center"
                            android:maxLines="1"
                            android:text="title"
                            android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
                            android:textColor="@android:color/white" />

                        <ImageView
                            android:id="@+id/arrowImageView"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_marginLeft="8dp"
                            android:layout_marginStart="8dp"
                            app:srcCompat="@android:drawable/arrow_up_float"
                            tools:ignore="ContentDescription,RtlHardcoded" />
                    </LinearLayout>

                </android.support.constraint.ConstraintLayout>

            </android.support.v7.widget.Toolbar>

            <!--small view-->
            <LinearLayout
                android:id="@+id/smallLayout"
                android:layout_width="match_parent"
                android:layout_height="@dimen/small_view_height"
                android:layout_marginTop="?attr/actionBarSize"
                android:clipChildren="false"
                android:clipToPadding="false"
                android:orientation="horizontal"
                app:layout_collapseMode="pin"
                tools:background="#ff330000"
                tools:layout_height="@dimen/small_view_height">

                <TextView
                    android:id="@+id/smallTextView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_gravity="center"
                    android:background="?attr/selectableItemBackgroundBorderless"
                    android:clickable="true"
                    android:focusable="true"
                    android:focusableInTouchMode="false"
                    android:gravity="center"
                    android:text="smallView"
                    android:textSize="14dp"
                    tools:background="?attr/colorPrimary"
                    tools:layout_gravity="top|center_horizontal"
                    tools:layout_height="40dp"
                    tools:layout_width="40dp"
                    tools:text="1" />

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

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

    <com.example.expandedtopviewtestupdate.MyRecyclerView
        android:id="@+id/nestedView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:context=".SlidingPanelBehavior" />

</android.support.design.widget.CoordinatorLayout>
Run Code Online (Sandbox Code Playgroud)

最后,在addOnOffsetChangedListener应用栏扩展和收缩时,添加以下代码以淡出/淡出较小的视图.一旦视图的alpha为零(不可见),将其可见性设置为View.INVISIBLE无法单击.一旦视图的alpha增加到零以上,通过将其可见性设置为可见并可单击View.VISIBLE.

mSmallLayout.setAlpha((float) -verticalOffset / totalScrollRange);
// If the small layout is not visible, make it officially invisible so
// it can't receive clicks.
if (alpha == 0) {
    mSmallLayout.setVisibility(View.INVISIBLE);
} else if (mSmallLayout.getVisibility() == View.INVISIBLE) {
    mSmallLayout.setVisibility(View.VISIBLE);
}
Run Code Online (Sandbox Code Playgroud)

结果如下:

在此输入图像描述

以下是包含所有上述更改的新模块.

MainActivity.java

public class MainActivity extends AppCompatActivity
    implements MyRecyclerView.AppBarTracking {
    private MyRecyclerView mNestedView;
    private int mAppBarOffset = 0;
    private boolean mAppBarIdle = true;
    private int mAppBarMaxOffset = 0;
    private AppBarLayout mAppBar;
    private boolean mIsExpanded = false;
    private ImageView mArrowImageView;
    private LinearLayout mSmallLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        LinearLayout expandCollapse;

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar toolbar = findViewById(R.id.toolbar);
        expandCollapse = findViewById(R.id.expandCollapseButton);
        mArrowImageView = findViewById(R.id.arrowImageView);
        mNestedView = findViewById(R.id.nestedView);
        mAppBar = findViewById(R.id.app_bar);
        mSmallLayout = findViewById(R.id.smallLayout);

        // Log when the small text view is clicked
        findViewById(R.id.smallTextView).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "<<<<click small layout");
            }
        });

        // Log when the big text view is clicked.
        findViewById(R.id.largeTextView).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "<<<<click big view");
            }
        });

        setSupportActionBar(toolbar);
        ActionBar ab = getSupportActionBar();
        if (ab != null) {
            getSupportActionBar().setDisplayShowTitleEnabled(false);
        }

        mAppBar.post(new Runnable() {
            @Override
            public void run() {
                mAppBarMaxOffset = -mAppBar.getTotalScrollRange();

                CoordinatorLayout.LayoutParams lp =
                    (CoordinatorLayout.LayoutParams) mAppBar.getLayoutParams();
                MyAppBarBehavior behavior = (MyAppBarBehavior) lp.getBehavior();
                // Only allow drag-to-open if the drag touch is on the toolbar.
                // Once open, all drags are allowed.
                if (behavior != null) {
                    behavior.setCanOpenBottom(findViewById(R.id.toolbar).getHeight());
                }
            }
        });

        mNestedView.setAppBarTracking(this);
        mNestedView.setLayoutManager(new LinearLayoutManager(this));
        mNestedView.setAdapter(new RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            @Override
            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                return new ViewHolder(
                    LayoutInflater.from(parent.getContext())
                        .inflate(android.R.layout.simple_list_item_1, parent, false));
            }

            @SuppressLint("SetTextI18n")
            @Override
            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
                ((TextView) holder.itemView.findViewById(android.R.id.text1))
                    .setText("Item " + position);
            }

            @Override
            public int getItemCount() {
                return 200;
            }

            class ViewHolder extends RecyclerView.ViewHolder {
                public ViewHolder(View view) {
                    super(view);
                }
            }
        });

        mAppBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                mAppBarOffset = verticalOffset;
                int totalScrollRange = appBarLayout.getTotalScrollRange();
                float progress = (float) (-verticalOffset) / (float) totalScrollRange;
                mArrowImageView.setRotation(-progress * 180);
                mIsExpanded = verticalOffset == 0;
                mAppBarIdle = mAppBarOffset >= 0 || mAppBarOffset <= mAppBarMaxOffset;
                float alpha = (float) -verticalOffset / totalScrollRange;
                mSmallLayout.setAlpha(alpha);

                // If the small layout is not visible, make it officially invisible so
                // it can't receive clicks.
                if (alpha == 0) {
                    mSmallLayout.setVisibility(View.INVISIBLE);
                } else if (mSmallLayout.getVisibility() == View.INVISIBLE) {
                    mSmallLayout.setVisibility(View.VISIBLE);
                }
            }
        });

        expandCollapse.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setExpandAndCollapseEnabled(true);
                if (mIsExpanded) {
                    setExpandAndCollapseEnabled(false);
                }
                mIsExpanded = !mIsExpanded;
                mNestedView.stopScroll();
                mAppBar.setExpanded(mIsExpanded, true);
            }
        });
    }

    private void setExpandAndCollapseEnabled(boolean enabled) {
        if (mNestedView.isNestedScrollingEnabled() != enabled) {
            mNestedView.setNestedScrollingEnabled(enabled);
        }
    }

    @Override
    public boolean isAppBarExpanded() {
        return mAppBarOffset == 0;
    }

    @Override
    public boolean isAppBarIdle() {
        return mAppBarIdle;
    }

    private static final String TAG = "MainActivity";
}
Run Code Online (Sandbox Code Playgroud)

SlidingPanelBehavior.java

public class SlidingPanelBehavior extends AppBarLayout.ScrollingViewBehavior {
    private AppBarLayout mAppBar;

    public SlidingPanelBehavior() {
        super();
    }

    public SlidingPanelBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(final CoordinatorLayout parent, View child, View dependency) {
        if (mAppBar == null && dependency instanceof AppBarLayout) {
            // Capture our appbar for later use.
            mAppBar = (AppBarLayout) dependency;
        }
        return dependency instanceof AppBarLayout;
    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent event) {
        int action = event.getAction();

        if (event.getAction() != MotionEvent.ACTION_DOWN) { // Only want "down" events
            return false;
        }
        if (getAppBarLayoutOffset(mAppBar) == -mAppBar.getTotalScrollRange()) {
            // When appbar is collapsed, don't let it open through nested scrolling.
            setNestedScrollingEnabledWithTest((NestedScrollingChild2) child, false);
        } else {
            // Appbar is partially to fully expanded. Set nested scrolling enabled to activate
            // the methods within this behavior.
            setNestedScrollingEnabledWithTest((NestedScrollingChild2) child, true);
        }
        return false;
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child,
                                       @NonNull View directTargetChild, @NonNull View target,
                                       int axes, int type) {
        //noinspection RedundantCast
        return ((NestedScrollingChild2) child).isNestedScrollingEnabled();
    }

    @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child,
                                  @NonNull View target, int dx, int dy, @NonNull int[] consumed,
                                  int type) {
        // How many pixels we must scroll to fully expand the appbar. This value is <= 0.
        final int appBarOffset = getAppBarLayoutOffset(mAppBar);

        // Check to see if this scroll will expand the appbar 100% or collapse it fully.
        if (dy <= appBarOffset) {
            // Scroll by the amount that will fully expand the appbar and dispose of the rest (dy).
            super.onNestedPreScroll(coordinatorLayout, mAppBar, target, dx,
                                    appBarOffset, consumed, type);
            consumed[1] += dy;
        } else if (dy >= (mAppBar.getTotalScrollRange() + appBarOffset)) {
            // This scroll will collapse the appbar. Collapse it and dispose of the rest.
            super.onNestedPreScroll(coordinatorLayout, mAppBar, target, dx,
                                    mAppBar.getTotalScrollRange() + appBarOffset,
                                    consumed, type);
            consumed[1] += dy;
        } else {
            // This scroll will leave the appbar partially open. Just do normal stuff.
            super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
        }
    }

    /**
     * {@code onNestedPreFling()} is overriden to address a nested scrolling defect that was
     * introduced in API 26. This method prevent the appbar from misbehaving when scrolled/flung.
     * <p>
     * Refer to <a href="https://issuetracker.google.com/issues/65448468"  target="_blank">"Bug in design support library"</a>
     */

    @Override
    public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
                                    @NonNull View child, @NonNull View target,
                                    float velocityX, float velocityY) {
        //noinspection RedundantCast
        if (((NestedScrollingChild2) child).isNestedScrollingEnabled()) {
            // Just stop the nested fling and let the appbar settle into place.
            ((NestedScrollingChild2) child).stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
            return true;
        }
        return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
    }

    private static int getAppBarLayoutOffset(AppBarLayout appBar) {
        final CoordinatorLayout.Behavior behavior =
            ((CoordinatorLayout.LayoutParams) appBar.getLayoutParams()).getBehavior();
        if (behavior instanceof AppBarLayout.Behavior) {
            return ((AppBarLayout.Behavior) behavior).getTopAndBottomOffset();
        }
        return 0;
    }

    // Something goes amiss when the flag it set to its current value, so only call
    // setNestedScrollingEnabled() if it will result in a change.
    private void setNestedScrollingEnabledWithTest(NestedScrollingChild2 child, boolean enabled) {
        if (child.isNestedScrollingEnabled() != enabled) {
            child.setNestedScrollingEnabled(enabled);
        }
    }

    @SuppressWarnings("unused")
    private static final String TAG = "SlidingPanelBehavior";
}
Run Code Online (Sandbox Code Playgroud)

MyRecyclerView.kt

/**A RecyclerView that allows temporary pausing of casuing its scroll to affect appBarLayout, based on https://stackoverflow.com/a/45338791/878126 */
class MyRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : RecyclerView(context, attrs, defStyle) {
    private var mAppBarTracking: AppBarTracking? = null
    private var mView: View? = null
    private var mTopPos: Int = 0
    private var mLayoutManager: LinearLayoutManager? = null

    interface AppBarTracking {
        fun isAppBarIdle(): Boolean
        fun isAppBarExpanded(): Boolean
    }

    override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?, type: Int): Boolean {
        if (type == ViewCompat.TYPE_NON_TOUCH && mAppBarTracking!!.isAppBarIdle()
                && isNestedScrollingEnabled) {
            if (dy > 0) {
                if (mAppBarTracking!!.isAppBarExpanded()) {
                    consumed!![1] = dy
                    return true
                }
            } else {
                mTopPos = mLayoutManager!!.findFirstVisibleItemPosition()
                if (mTopPos == 0) {
                    mView = mLayoutManager!!.findViewByPosition(mTopPos)
                    if (-mView!!.top + dy <= 0) {
                        consumed!![1] = dy - mView!!.top
                        return true
                    }
                }
            }
        }
        if (dy < 0 && type == ViewCompat.TYPE_TOUCH && mAppBarTracking!!.isAppBarExpanded()) {
            consumed!![1] = dy
            return true
        }

        val returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
        if (offsetInWindow != null && !isNestedScrollingEnabled && offsetInWindow[1] != 0)
            offsetInWindow[1] = 0
        return returnValue
    }

    override fun setLayoutManager(layout: RecyclerView.LayoutManager) {
        super.setLayoutManager(layout)
        mLayoutManager = layoutManager as LinearLayoutManager
    }

    fun setAppBarTracking(appBarTracking: AppBarTracking) {
        mAppBarTracking = appBarTracking
    }

    override fun fling(velocityX: Int, velocityY: Int): Boolean {
        var velocityY = velocityY
        if (!mAppBarTracking!!.isAppBarIdle()) {
            val vc = ViewConfiguration.get(context)
            velocityY = if (velocityY < 0) -vc.scaledMinimumFlingVelocity
            else vc.scaledMinimumFlingVelocity
        }

        return super.fling(velocityX, velocityY)
    }
}
Run Code Online (Sandbox Code Playgroud)

MyAppBarBehavior.java

/**
 * Attach this behavior to AppBarLayout to disable the bottom portion of a closed appBar
 * so it cannot be touched to open the appBar. This behavior is helpful if there is some
 * portion of the appBar that displays when the appBar is closed, but should not open the appBar
 * when the appBar is closed.
 */
public class MyAppBarBehavior extends AppBarLayout.Behavior {

    // Touch above this y-axis value can open the appBar.
    private int mCanOpenBottom;

    // Determines if the appBar can be dragged open or not via direct touch on the appBar.
    private boolean mCanDrag = true;

    @SuppressWarnings("unused")
    public MyAppBarBehavior() {
        init();
    }

    @SuppressWarnings("unused")
    public MyAppBarBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        setDragCallback(new AppBarLayout.Behavior.DragCallback() {
            @Override
            public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
                return mCanDrag;
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent,
                                         AppBarLayout child,
                                         MotionEvent event) {

        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            // If appBar is closed. Only allow scrolling in defined area.
            if (child.getTop() <= -child.getTotalScrollRange()) {
                mCanDrag = event.getY() < mCanOpenBottom;
            }
        }
        return super.onInterceptTouchEvent(parent, child, event);
    }

    public void setCanOpenBottom(int bottom) {
        mCanOpenBottom = bottom;
    }
}
Run Code Online (Sandbox Code Playgroud)