Android 在AppBarLayout中使用带有enterAlways scrollFlag的视图和另一个带有exitUntilCollapsed scrollFlag的视图

Android 在AppBarLayout中使用带有enterAlways scrollFlag的视图和另一个带有exitUntilCollapsed scrollFlag的视图,android,material-design,android-coordinatorlayout,android-appbarlayout,Android,Material Design,Android Coordinatorlayout,Android Appbarlayout,我正在尝试使用CoordinatorLayout和AppBarLayout构建以下布局: |视图1(标题)| |视图2---------| |回收视图--| 我希望实现的行为如下所示: 当我滚动RecyclerView时,视图1将完全折叠 当我继续滚动时,视图2将折叠,直到“折叠”状态 视图2折叠后,RecyclerView应开始滚动 当我从中间向上滚动RecyclerView时,视图1应该立即返回,而视图2处于折叠状态 一旦RecyclerView到达顶部,它应该展开视图2 这是我作为概念

我正在尝试使用CoordinatorLayout和AppBarLayout构建以下布局:

|视图1(标题)|
|视图2---------|
|回收视图--|

我希望实现的行为如下所示:

  • 当我滚动RecyclerView时,视图1将完全折叠
  • 当我继续滚动时,视图2将折叠,直到“折叠”状态
  • 视图2折叠后,RecyclerView应开始滚动
  • 当我从中间向上滚动RecyclerView时,视图1应该立即返回,而视图2处于折叠状态
  • 一旦RecyclerView到达顶部,它应该展开视图2
这是我作为概念证明而创建的测试布局

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/view1"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:text="TEST TITLE"
            android:textSize="50sp"
            app:layout_scrollFlags="scroll|enterAlways|snap" />

        <TextView
            android:id="@+id/view2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="TEST TEST TEST TEST TEST TEST TEST TEST"
            android:textSize="70sp"
            android:minHeight="50dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed" />

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

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android:support.design.widget.CoordinatorLayout>

  • 向下滚动时,我确认视图1已完全折叠

  • 向下滚动更多。视图2将塌陷,直到达到最小高度。之后RV开始滚动。到目前为止,这是预期的效果

  • 我研究了AppBarLayout实现,问题似乎是因为AppBarLayout根据scrollFlags计算整个视图的滚动范围,并根据滚动偏移量偏移整个视图,而不是更新每个子视图

    有人知道是否有任何解决方法或开源库来解决这个问题吗?它不必是协调布局/AppBarLayout方法,但我需要生成行为


    提前谢谢。

    好的,我自己找到了一个解决方案,并决定为有类似问题的人发布我的解决方案

    解决方案是创建NestedCoordinatorLayout,通过实现NestedScollingChild来扩展CoordinatorLayout,以便我们可以在两个AppBarLayout之间交互。我在这篇文章中引用了NestedScrollView源代码和答案,但我不得不做一些修改,使其按我想要的方式工作

    这是NestedCoordinator布局的代码

    public class NestedCoordinatorLayout extends CoordinatorLayout implements NestedScrollingChild {
    
        private final NestedScrollingChildHelper scrollingChildHelper;
    
        private final int[] parentOffsetInWindow = new int[2];
        private final int[] parentScrollConsumed = new int[2];
    
        public NestedCoordinatorLayout(Context context) {
            this(context, null);
        }
    
        public NestedCoordinatorLayout(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public NestedCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            scrollingChildHelper = new NestedScrollingChildHelper(this);
            setNestedScrollingEnabled(true);
        }
    
        //NestedScrollingChild
    
        @Override
        public void setNestedScrollingEnabled(boolean enabled) {
            scrollingChildHelper.setNestedScrollingEnabled(enabled);
        }
    
        @Override
        public boolean isNestedScrollingEnabled() {
            return scrollingChildHelper.isNestedScrollingEnabled();
        }
    
        @Override
        public boolean startNestedScroll(int axes) {
            return scrollingChildHelper.startNestedScroll(axes);
        }
    
        @Override
        public void stopNestedScroll() {
            scrollingChildHelper.stopNestedScroll();
        }
    
        @Override
        public boolean hasNestedScrollingParent() {
            return scrollingChildHelper.hasNestedScrollingParent();
        }
    
        @Override
        public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
                                        int dyUnconsumed, int[] offsetInWindow) {
            return scrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
        }
    
        @Override
        public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
            return scrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
        }
    
        @Override
        public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
            return scrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
        }
    
        @Override
        public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
            return scrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
        }
    
        //NestedScrollingParent
    
        @Override
        public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
            scrollingChildHelper.startNestedScroll(nestedScrollAxes);
            return super.onStartNestedScroll(child, target, nestedScrollAxes);
        }
    
        @Override
        public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
            final int[] parentConsumed = parentScrollConsumed;
            //This is where the most important change happens.
            //During the prescroll, we want to decrease dx/dy.
            //This will make sure the top bar gets the scroll event first.
            if (dispatchNestedPreScroll(dx, dy, parentConsumed, null)) {
                dx -= parentConsumed[0];
                dy -= parentConsumed[1];
                consumed[0] += parentConsumed[0];
                consumed[1] += parentConsumed[1];
            }
            super.onNestedPreScroll(target, dx, dy, consumed);
        }
    
        @Override
        public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
            dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, parentOffsetInWindow);
            super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
        }
    
        @Override
        public void onStopNestedScroll(View target) {
            scrollingChildHelper.onStopNestedScroll(target);
            super.onStopNestedScroll(target);
        }
    
        @Override
        public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
            scrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
            return super.onNestedFling(target, velocityX, velocityY, consumed);
        }
    
        @Override
        public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
            scrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
            return super.onNestedPreFling(target, velocityX, velocityY);
        }
    
        @Override
        public void onNestedScrollAccepted(View child, View target, int axes) {
            super.onNestedScrollAccepted(child, target, axes);
            startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
        }
    }
    
    更新后的xml文件如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    
    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.design.widget.AppBarLayout
            android:id="@+id/app_bar_layout_for_view1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <TextView
                android:id="@+id/view1"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:text="TEST TITLE"
                android:background="@android:color/white"
                android:textSize="50sp"
                app:layout_scrollFlags="scroll|enterAlways|snap" />
    
        </android.support.design.widget.AppBarLayout>
    
        <!-- Consider this like a NestedScrollView.
             You need to have a scrolling behavior -->
        <NestedCoordinatorLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
            <android.support.design.widget.AppBarLayout
                android:id="@+id/app_bar_layout_for_view2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <TextView
                    android:id="@+id/view2"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:minHeight="50dp"
                    android:text="TEST TEST TEST TEST TEST TEST TEST TEST"
                    android:textSize="70sp"
                    app:layout_scrollFlags="scroll|exitUntilCollapsed" />
    
            </android.support.design.widget.AppBarLayout>
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior" />
    
        </NestedCoordinatorLayout>
    
    </android.support.design.widget.CoordinatorLayout>