使用RecyclerView + AppBarLayout

tyl*_*ach 171 android android-appcompat android-layout android-design-library

我正在使用新的CoordinatorLayout与AppBarLayout和CollapsingToolbarLayout.在AppBarLayout下面,我有一个带有内容列表的RecyclerView.

我已经验证了当我在列表中上下滚动时,滚动滚动在RecyclerView上工作.但是,我还希望AppBarLayout在扩展期间顺利滚动.

向上滚动以展开CollaspingToolbarLayout时,一旦将手指从屏幕上抬起,滚动会立即停止.如果向上快速向上滚动,有时CollapsingToolbarLayout也会重新折叠.与使用NestedScrollView时,RecyclerView的这种行为似乎有很大不同.

我试图在recyclerview上设置不同的滚动属性,但我无法弄清楚这一点.

这是一个显示一些滚动问题的视频. https://youtu.be/xMLKoJOsTAM

这是一个显示RecyclerView(CheeseDetailActivity)问题的示例. https://github.com/tylerjroach/cheesesquare

以下是使用Chris Banes的NestedScrollView的原始示例. https://github.com/chrisbanes/cheesesquare

小智 114

Kirill Boyarshinov的答案几乎是正确的.

主要问题是RecyclerView有时会给出不正确的fling方向,所以如果你将以下代码添加到他的答案中它可以正常工作:

public final class FlingBehavior extends AppBarLayout.Behavior {
    private static final int TOP_CHILD_FLING_THRESHOLD = 3;
    private boolean isPositive;

    public FlingBehavior() {
    }

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

    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
        if (velocityY > 0 && !isPositive || velocityY < 0 && isPositive) {
            velocityY = velocityY * -1;
        }
        if (target instanceof RecyclerView && velocityY < 0) {
            final RecyclerView recyclerView = (RecyclerView) target;
            final View firstChild = recyclerView.getChildAt(0);
            final int childAdapterPosition = recyclerView.getChildAdapterPosition(firstChild);
            consumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD;
        }
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        isPositive = dy > 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望这个对你有用.

  • 如果您使用SwipeRefreshLayout作为您的recyclerview的父级,只需添加以下代码:`if(target instanceof SwipeRefreshLayout && velocityY <0){target =((SwipeRefreshLayout)target).getChildAt(0); ``之前`if(目标实例,RecyclerView && velocityY <0){` (9认同)
  • 你好如何用appbarlayout和Nestedscrollview实现同样的事情...在此先感谢.. (3认同)

Kir*_*nov 69

似乎v23更新尚未解决.

我找到了一些黑客来解决这个问题.如果ScrollingView的顶级子项接近Adapter中数据的开头,则诀窍是重建fling事件.

public final class FlingBehavior extends AppBarLayout.Behavior {

    public FlingBehavior() {
    }

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

    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
        if (target instanceof ScrollingView) {
            final ScrollingView scrollingView = (ScrollingView) target;
            consumed = velocityY > 0 || scrollingView.computeVerticalScrollOffset() > 0;
        }
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }
}
Run Code Online (Sandbox Code Playgroud)

在你的布局中使用它:

 <android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_behavior="your.package.FlingBehavior">
    <!--your views here-->
 </android.support.design.widget.AppBarLayout>
Run Code Online (Sandbox Code Playgroud)

编辑:现在重建Fling事件是基于verticalScrollOffset而不是从顶部的项目数量RecyclerView.

EDIT2:将目标检查为ScrollingView接口实例而不是RecyclerView.两者RecyclerViewNestedScrollingView实现它.

  • @Hardeep将`目标实例的RecyclerView`更改为`目标实例的NestedScrollView`,或更多的通用案例改为`target instanceof ScrollingView`.我更新了答案. (2认同)

Mak*_*ing 15

我通过将OnScrollingListener应用于recyclerView找到了修复程序.现在它运作得很好.问题是recyclerview提供了错误的消耗值,并且行为不知道何时将recyclerview滚动到顶部.

package com.singmak.uitechniques.util.coordinatorlayout;

import android.content.Context;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by maksing on 26/3/2016.
 */
public final class RecyclerViewAppBarBehavior extends AppBarLayout.Behavior {

    private Map<RecyclerView, RecyclerViewScrollListener> scrollListenerMap = new HashMap<>(); //keep scroll listener map, the custom scroll listener also keep the current scroll Y position.

    public RecyclerViewAppBarBehavior() {
    }

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

    /**
     *
     * @param coordinatorLayout
     * @param child The child that attached the behavior (AppBarLayout)
     * @param target The scrolling target e.g. a recyclerView or NestedScrollView
     * @param velocityX
     * @param velocityY
     * @param consumed The fling should be consumed by the scrolling target or not
     * @return
     */
    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
        if (target instanceof RecyclerView) {
            final RecyclerView recyclerView = (RecyclerView) target;
            if (scrollListenerMap.get(recyclerView) == null) {
                RecyclerViewScrollListener recyclerViewScrollListener = new RecyclerViewScrollListener(coordinatorLayout, child, this);
                scrollListenerMap.put(recyclerView, recyclerViewScrollListener);
                recyclerView.addOnScrollListener(recyclerViewScrollListener);
            }
            scrollListenerMap.get(recyclerView).setVelocity(velocityY);
            consumed = scrollListenerMap.get(recyclerView).getScrolledY() > 0; //recyclerView only consume the fling when it's not scrolled to the top
        }
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }

    private static class RecyclerViewScrollListener extends RecyclerView.OnScrollListener {
        private int scrolledY;
        private boolean dragging;
        private float velocity;
        private WeakReference<CoordinatorLayout> coordinatorLayoutRef;
        private WeakReference<AppBarLayout> childRef;
        private WeakReference<RecyclerViewAppBarBehavior> behaviorWeakReference;

        public RecyclerViewScrollListener(CoordinatorLayout coordinatorLayout, AppBarLayout child, RecyclerViewAppBarBehavior barBehavior) {
            coordinatorLayoutRef = new WeakReference<CoordinatorLayout>(coordinatorLayout);
            childRef = new WeakReference<AppBarLayout>(child);
            behaviorWeakReference = new WeakReference<RecyclerViewAppBarBehavior>(barBehavior);
        }

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            dragging = newState == RecyclerView.SCROLL_STATE_DRAGGING;
        }

        public void setVelocity(float velocity) {
            this.velocity = velocity;
        }

        public int getScrolledY() {
            return scrolledY;
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            scrolledY += dy;

            if (scrolledY <= 0 && !dragging && childRef.get() != null && coordinatorLayoutRef.get() != null && behaviorWeakReference.get() != null) {
                //manually trigger the fling when it's scrolled at the top
                behaviorWeakReference.get().onNestedFling(coordinatorLayoutRef.get(), childRef.get(), recyclerView, 0, velocity, false);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Xia*_*zou 13

自支持设计26.0.0以来,它已得到修复.

compile 'com.android.support:design:26.0.0'
Run Code Online (Sandbox Code Playgroud)

  • 这需要向上移动。这在 [此处](https://chris.banes.me/2017/06/09/carry-on-scrolling/) 中有所描述,以防有人对细节感兴趣。 (2认同)
  • @Xiaozou我正在使用26.1.0但仍然遇到了投掷问题.快速投掷有时会导致相反的运动(运动的速度是相反的/错误的,如onNestedFling方法中所见).在小米Redmi Note 3和Galaxy S3中重现了它 (2认同)

Man*_*hir 5

这是Google支持设计AppBarLayout的流畅版本.如果你正在使用AppBarLayout,你会发现它有一个问题.

compile "me.henrytao:smooth-app-bar-layout:<latest-version>"
Run Code Online (Sandbox Code Playgroud)

请参阅此处的图书馆.. https://github.com/henrytao-me/smooth-app-bar-layout