RecyclerView + AppBarLayout을 사용한 파일링


171

AppBarLayout 및 CollapsingToolbarLayout과 함께 새로운 CoordinatorLayout을 사용하고 있습니다. AppBarLayout 아래에 내용 목록이있는 RecyclerView가 있습니다.

목록을 위아래로 스크롤 할 때 Flicker 스크롤이 RecyclerView에서 작동하는지 확인했습니다. 그러나 AppBarLayout이 확장 중에 부드럽게 스크롤되기를 원합니다.

CollaspingToolbarLayout을 확장하기 위해 위로 스크롤하면 화면에서 손가락을 떼면 스크롤이 즉시 중지됩니다. 빠른 동작으로 위로 스크롤하면 CollapsingToolbarLayout도 다시 축소됩니다. RecyclerView와의이 동작은 NestedScrollView를 사용할 때와 다르게 작동하는 것 같습니다.

recyclerview에서 다른 스크롤 속성을 설정하려고했지만 이것을 알아낼 수 없었습니다.

다음은 스크롤 문제 중 일부를 보여주는 비디오입니다. https://youtu.be/xMLKoJOsTAM

다음은 RecyclerView (CheeseDetailActivity) 관련 문제를 보여주는 예입니다. https://github.com/tylerjroach/cheesesquare

다음은 Chris Banes의 NestedScrollView를 사용하는 원래 예제입니다. https://github.com/chrisbanes/cheesesquare


이 똑같은 문제가 발생했습니다 (RecyclerView와 함께 사용하고 있습니다). 모든 앱의 Google Play 스토어 목록을 보면 제대로 작동하는 것 같습니다. 따라서 해결책이있을 것입니다 ...
Aneem

Aneem 님, 이것이 가장 좋은 해결책은 아니지만 github.com/ksoichiro/Android-ObservableScrollView 라이브러리를 실험하기 시작했습니다 . 특히이 활동에서 필요한 결과를 얻으려면 FlexibleSpaceWithImageRecyclerViewActivity.java가 필요합니다. 수정하기 전에 이름을 잘못 입력하여 죄송합니다. 자동 고침 ..
tylerjroach 2016 년

2
여기서 동일한 문제가 발생하여 AppBarLayout을 피했습니다.
Renaud Cerrato 2016 년

네. OvservableScrollView 라이브러리에서 필요한 것을 정확하게 얻었습니다. 향후 버전에서 수정 될 것이라고 확신합니다.
tylerjroach 2016 년

8
날아 다니는 버그가 있으며 문제 가 제기되었습니다.
Renaud Cerrato

답변:


114

Kirill Boyarshinov 의 답변 은 거의 정확했습니다.

주요 문제는 RecyclerView가 때로는 잘못된 방향을 제시한다는 것입니다. 따라서 다음 코드를 답변에 추가하면 올바르게 작동합니다.

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;
    }
}

이것이 도움이되기를 바랍니다.


당신은 내 하루를 구했다! 절대적으로 잘 작동하는 것 같습니다! 왜 대답이 받아 들여지지 않습니까?
Zordid

9
recyclerview의 부모로 SwipeRefreshLayout을 사용하는 경우 다음 코드를 추가하십시오. if (target instanceof SwipeRefreshLayout && velocityY < 0) { target = ((SwipeRefreshLayout) target).getChildAt(0); }이전 if (target instanceof RecyclerView && velocityY < 0) {
LucasFM

1
+ 1이 수정 프로그램을 분석 할 때 Google에서 아직이 문제를 해결하지 못한 이유를 이해하지 못합니다. 코드는 매우 단순 해 보입니다.
가스통 플로레스

3
안녕하세요 방법을 사전에 appbarlayout 및 Nestedscrollview ... 감사와 같은 일을 달성하기 위해 ...
해리 샤르마

1
그것은 나를 위해 작동하지 않았습니다 = / 그런데, 클래스를 지원 패키지로 옮기지 않아도 클래스에 DragCallback을 등록 할 수 있습니다.
Augusto Carmo

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);
    }
}

레이아웃에서 다음과 같이 사용하십시오.

 <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>

편집 : 이제 이벤트 재 소거가에서 verticalScrollOffset의 양이 아닌 대신을 기준으로합니다 RecyclerView.

EDIT2 : 대상을 ScrollingView인터페이스 인스턴스 대신 인터페이스 인스턴스 로 확인하십시오 RecyclerView. 모두 RecyclerViewNestedScrollingView그것을 구현합니다.


layout_behavior 오류에 문자열 유형을 가져올 수 없습니다.
Vaisakh N

나는 그것을 테스트하고 더 잘 작동합니다! 그러나 TOP_CHILD_FLING_THRESHOLD의 목적은 무엇입니까? 왜 3입니까?
Julio_oa

@Julio_oa TOP_CHILD_FLING_THRESHOLD는 리사이클 뷰가이 임계 값보다 낮은 위치로 스크롤되는 경우 플링 이벤트가 재개됨을 의미합니다. Btw verticalScrollOffset더 일반적인 답변을 업데이트했습니다 . 이제 플링 이벤트가 recyclerView맨 위로 스크롤 되면 재개됩니다 .
Kirill Boyarshinov

안녕하세요 방법을 사전에 appbarlayout 및 Nestedscrollview ... 감사와 같은 일을 달성하기 위해 ...
해리 샤르마

2
@Hardeep 변화 target instanceof RecyclerViewtarget instanceof NestedScrollView, 이상으로 일반적인 경우에 target instanceof ScrollingView. 나는 대답을 업데이트했다.
Kirill Boyarshinov

15

recyclerView에 OnScrollingListener를 적용하여 수정 사항을 찾았습니다. 지금은 아주 잘 작동합니다. 문제는 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);
            }
        }
    }
}

게시물 주셔서 감사합니다. 이 페이지의 모든 답변을 시도했으며 내 경험상 가장 효과적인 답변입니다. 그러나 RecyclerView를 충분히 강제로 스크롤하지 않으면 레이아웃의 RecylerView가 AppBarLayout이 화면 밖으로 스크롤되기 전에 내부적으로 스크롤됩니다. 즉, RecyclerView를 충분히 강제로 스크롤하면 AppBar가 내부적으로 ScrollrView를 스크롤하지 않고 화면 밖으로 스크롤하지만, RecyclerView를 충분히 강제로 스크롤하지 않으면 RecyclerView가 내부적으로 스크롤되지 않으면 AppbarLayout이 화면 밖으로 스크롤됩니다. 그 원인을 알고 있습니까?
Micah Simmons

recyclerview는 여전히 터치 이벤트를 수신하므로 여전히 스크롤되는 이유는 onNestedFling 동작이 동시에 appbarLayout을 스크롤하도록 애니메이션화하는 것입니다. 어쩌면이 동작을 변경하기 위해 onInterceptTouch를 재정의 할 수 있습니다. 나에게 현재의 행동은 내가 본 것에서 받아 들일 수 있습니다. (우리가 같은 것을보고 있는지 확실하지 않음)
Mak Sing

@MakSing 가장 기다려온 솔루션에 정말 도움이 CoordinatorLayout되고 ViewPager설정에 감사드립니다. 다른 개발자도 혜택을받을 수 있도록 GIST를 작성하십시오. 이 솔루션도 공유하고 있습니다. 다시 감사합니다.
Nitin Misra

1
@MakSing 모든 솔루션을 차단하면 이것이 가장 효과적입니다. 나는 onNestedFling에 전달 된 속도를 약간의 비트 속도 * 0.6f로 조정했습니다 ... 더 좋은 흐름을주는 것으로 보입니다.
saberrider

나를 위해 작동합니다. @MakSing onScrolled 메서드에서 RecyclerViewAppBarBehavior가 아닌 AppBarLayout.Behavior의 onNestedFling을 호출해야합니까? 나에게는 조금 이상해 보인다.
Anton Malmygin 2012

13

지원 디자인 26.0.0 이후로 수정되었습니다.

compile 'com.android.support:design:26.0.0'

2
이것은 위로 올라 가야합니다. 누군가가 세부 사항에 관심이있는 경우 여기 에 설명되어 있습니다 .
Chris Dinon

1
이제 상태 표시 줄에 문제가있는 것 같습니다. 여기서 아래로 스크롤하면 상태 표시 줄이 스크롤로 약간 내려갑니다 ... 매우 성가시다!
box

2
@Xiaozou 26.1.0을 사용하고 있는데 여전히 깜박임에 문제가 있습니다. 빠른 플링으로 인해 반대 방향으로 움직이는 경우가 있습니다 (onNestedFling 방법에서 볼 수 있듯이 동작 속도가 반대 / 잘못된). Xiaomi Redmi Note 3 및 Galaxy S3에서 재현
dor506

@ dor506 stackoverflow.com/a/47298312/782870 반대 운동 결과를 말할 때 동일한 문제가 있는지 확실하지 않습니다. 그러나 나는 여기에 답을 올렸다. 그것이 도움이
vida



2

이것은 내 레이아웃과 스크롤입니다. 작동합니다.

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:id="@+id/container">

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbarLayout"
    android:layout_height="192dp"
    android:layout_width="match_parent">

    <android.support.design.widget.CollapsingToolbarLayout
        android:id="@+id/ctlLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_scrollFlags="scroll|exitUntilCollapsed"
        app:contentScrim="?attr/colorPrimary"
        app:layout_collapseMode="parallax">

        <android.support.v7.widget.Toolbar
            android:id="@+id/appbar"
            android:layout_height="?attr/actionBarSize"
            android:layout_width="match_parent"
            app:layout_scrollFlags="scroll|enterAlways"
            app:layout_collapseMode="pin"/>

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

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

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

2

Mak SingManolo Garcia의 답변 에 따라 지금까지 내 솔루션 .

완전히 완벽하지는 않습니다. 지금은 이상한 효과를 피하기 위해 유효한 속도를 다시 계산하는 방법을 모릅니다. 앱 바가 스크롤 속도보다 빠르게 확장 될 수 있습니다. 그러나 확장 된 앱 바와 스크롤 된 재활용보기가있는 상태에는 도달 할 수 없습니다.

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
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;

public class FlingAppBarLayoutBehavior
        extends AppBarLayout.Behavior {

    // The minimum I have seen for a dy, after the recycler view stopped.
    private static final int MINIMUM_DELTA_Y = -4;

    @Nullable
    RecyclerViewScrollListener mScrollListener;

    private boolean isPositive;

    public FlingAppBarLayoutBehavior() {
    }

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

    public boolean callSuperOnNestedFling(
            CoordinatorLayout coordinatorLayout,
            AppBarLayout child,
            View target,
            float velocityX,
            float velocityY,
            boolean consumed) {
        return super.onNestedFling(
                coordinatorLayout,
                child,
                target,
                velocityX,
                velocityY,
                consumed
        );
    }

    @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) {
            RecyclerView recyclerView = (RecyclerView) target;

            if (mScrollListener == null) {
                mScrollListener = new RecyclerViewScrollListener(
                        coordinatorLayout,
                        child,
                        this
                );
                recyclerView.addOnScrollListener(mScrollListener);
            }

            mScrollListener.setVelocity(velocityY);
        }

        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;
    }

    private static class RecyclerViewScrollListener
            extends RecyclerView.OnScrollListener {

        @NonNull
        private final WeakReference<AppBarLayout> mAppBarLayoutWeakReference;

        @NonNull
        private final WeakReference<FlingAppBarLayoutBehavior> mBehaviorWeakReference;

        @NonNull
        private final WeakReference<CoordinatorLayout> mCoordinatorLayoutWeakReference;

        private int mDy;

        private float mVelocity;

        public RecyclerViewScrollListener(
                @NonNull CoordinatorLayout coordinatorLayout,
                @NonNull AppBarLayout child,
                @NonNull FlingAppBarLayoutBehavior barBehavior) {
            mCoordinatorLayoutWeakReference = new WeakReference<>(coordinatorLayout);
            mAppBarLayoutWeakReference = new WeakReference<>(child);
            mBehaviorWeakReference = new WeakReference<>(barBehavior);
        }

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                if (mDy < MINIMUM_DELTA_Y
                        && mAppBarLayoutWeakReference.get() != null
                        && mCoordinatorLayoutWeakReference.get() != null
                        && mBehaviorWeakReference.get() != null) {

                    // manually trigger the fling when it's scrolled at the top
                    mBehaviorWeakReference.get()
                            .callSuperOnNestedFling(
                                    mCoordinatorLayoutWeakReference.get(),
                                    mAppBarLayoutWeakReference.get(),
                                    recyclerView,
                                    0,
                                    mVelocity, // TODO find a way to recalculate a correct velocity.
                                    false
                            );

                }
            }
        }

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

        public void setVelocity(float velocity) {
            mVelocity = velocity;
        }

    }

}

리플렉션을 사용하여 recyclerView의 현재 속도를 얻을 수 있습니다 (25.1.0 현재). Field viewFlingerField = recyclerView.getClass().getDeclaredField("mViewFlinger"); viewFlingerField.setAccessible(true); Object flinger = viewFlingerField.get(recyclerView); Field scrollerField = flinger.getClass().getDeclaredField("mScroller"); scrollerField.setAccessible(true); ScrollerCompat scroller = (ScrollerCompat) scrollerField.get(flinger); velocity = Math.signum(mVelocity) * Math.abs(scroller.getCurrVelocity());
Nicholas

2

내 경우에는 나는 도망 치는 문제를 겪고 있었다. RecyclerView 부드럽게 스크롤되지 않아 했습니다.

어떤 이유 때문이었다 내가 내를 넣어했다고 잊고 있었던 RecyclerViewA의NestedScrollView .

어리석은 실수이지만 그것을 알아내는 데 시간이 걸렸습니다 ...


1

AppBarLayout 안에 1dp 높이의 뷰를 추가하면 훨씬 잘 작동합니다. 이것이 나의 레이아웃입니다.

  <android.support.design.widget.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"
android:background="@android:color/white"
tools:context="com.spof.spof.app.UserBeachesActivity">

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

    <android.support.v7.widget.Toolbar
        android:id="@+id/user_beaches_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:layout_alignParentTop="true"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/WhiteTextToolBar"
        app:layout_scrollFlags="scroll|enterAlways" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp" />
</android.support.design.widget.AppBarLayout>


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


위로 스크롤하는 경우에만 작동합니다. 아래로 스크롤 할 때 아님
Arthur

나를 위해 양방향으로 잘 작동합니다. appbarlayout 내에 1dp보기를 추가 했습니까?. 나는 안드로이드 롤리팝과 키트 캣에서만 테스트했습니다.
Jachumbelechao Unto Mantekilla

글쎄, 툴바를 감싸는 CollapsingToolbarLayout도 사용하고 있습니다. 나는 그 안에 1dp보기를 넣었습니다. 이 AppBarLayout-> CollapsingToolbarLayout-> 도구 모음 + 1dp보기
Arthur

CollapsingToolbarLayout과 잘 작동하는지 모르겠습니다. 이 코드로만 테스트했습니다. CollapsingToolbarLayout 외부에 1dp 뷰를 넣으려고 했습니까?
Jachumbelechao 운토 만 테킬라

예. 위로 스크롤 작동, 아래로 스크롤해도 툴바가 확장되지 않습니다.
Arthur

1

이미 여기에서 꽤 인기있는 솔루션이 있지만 그것들을 가지고 놀면서 나는 훨씬 더 간단한 해결책을 찾았습니다. 내 솔루션은 또한 AppBarLayout스크롤 가능한 콘텐츠가 맨 위에 도달 할 때만 확장 되도록 보장하므로 다른 솔루션보다 이점이 있습니다.

private int mScrolled;
private int mPreviousDy;
private AppBarLayout mAppBar;

myRecyclerView.addOnScrollListener(new OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            mScrolled += dy;
            // scrolled to the top with a little more velocity than a slow scroll e.g. flick/fling.
            // Adjust 10 (vertical change of event) as you feel fit for you requirement
            if(mScrolled == 0 && dy < -10 && mPrevDy < 0) {
                mAppBar.setExpanded(true, true);
            }
            mPreviousDy = dy;
    });

mPrevDy 무엇인가
ARR.s

1

RecyclerView안에 a SwipeRefreshLayout와 a 가 있기 때문에 허용 된 답변이 효과가 없었 습니다 ViewPager. 이것은 추구하는 개선 된 버전입니다RecyclerView 계층 구조에서 모든 레이아웃에서 작동 .

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) {
            RecyclerView recycler = findRecycler((ViewGroup) target);
            if (recycler != null){
                target = recycler;
            }
        }
        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;
    }

    @Nullable
    private RecyclerView findRecycler(ViewGroup container){
        for (int i = 0; i < container.getChildCount(); i++) {
            View childAt = container.getChildAt(i);
            if (childAt instanceof RecyclerView){
                return (RecyclerView) childAt;
            }
            if (childAt instanceof ViewGroup){
                return findRecycler((ViewGroup) childAt);
            }
        }
        return null;
    }
}

1

대답: 지원 라이브러리 v26에서 수정되었습니다.

그러나 v26에는 플링에 문제가 있습니다. 때로 플링이 너무 단단하지 않더라도 AppBar가 다시 반송됩니다.

앱바에서 수신 거부 효과를 제거하려면 어떻게합니까?

v26을 지원하도록 업데이트 할 때 동일한 문제가 발생하면이 답변 의 요약을 참조하십시오 .

솔루션 : NestedScroll이 아직 중지되지 않은 상태에서 AppBar를 터치하면 AppBar의 기본 동작을 확장하고 AppBar.Behavior의 onNestedPreScroll () 및 onNestedScroll ()에 대한 호출을 차단하십시오.


0

줄리안 오스 가 옳습니다.

recyclerview가 임계 값보다 낮고 스크롤되면 Manolo Garcia의 답변 이 작동하지 않습니다. 아이템 위치가 아니라 offset리사이클 러보기와를 비교해야합니다 velocity to the distance.

julian의 kotlin 코드를 참조하고 반사를 빼서 Java 버전을 만들었습니다.

public final class FlingBehavior extends AppBarLayout.Behavior {

    private boolean isPositive;

    private float mFlingFriction = ViewConfiguration.getScrollFriction();

    private float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
    private final float INFLEXION = 0.35f;
    private float mPhysicalCoeff;

    public FlingBehavior(){
        init();
    }

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

    private void init(){
        final float ppi = BaseApplication.getInstance().getResources().getDisplayMetrics().density * 160.0f;
        mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
                * 39.37f // inch/meter
                * ppi
                * 0.84f; // look and feel tuning
    }

    @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) {
            RecyclerView recyclerView = (RecyclerView) target;

            double distance = getFlingDistance((int) velocityY);
            if (distance < recyclerView.computeVerticalScrollOffset()) {
                consumed = true;
            } else {
                consumed = false;
            }
        }
        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;
    }

    public double getFlingDistance(int velocity){
        final double l = getSplineDeceleration(velocity);
        final double decelMinusOne = DECELERATION_RATE - 1.0;
        return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
    }

    private double getSplineDeceleration(int velocity) {
        return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
    }

}

resloveBaseApplication
ARR.s에서

@ ARR.s 죄송합니다. 아래처럼 문맥을 바꾸십시오.
정성민

YOUR_CONTEXT.getResources (). getDisplayMetrics (). density * 160.0f;
정성민


0

Google 이슈 트래커 와 관련하여 Android 26.0.0-beta2 버전의 지원 라이브러리로 수정되었습니다.

Android 지원 라이브러리 버전 26.0.0-beta2를 업데이트하십시오 .

문제가 지속되면 다시 검토 할 수 있도록 Google 문제 추적기에 보고하십시오 .


0

위의 답변으로 다른 답변을 추가하면 내 요구를 완전히 충족시키지 못하거나 제대로 작동하지 않았습니다. 이것은 부분적으로 여기에 퍼진 아이디어를 기반으로합니다.

그래서이 일을 무엇입니까?

시나리오 하향식 플링 : AppBarLayout이 축소되면 아무 것도하지 않고 RecyclerView가 자체적으로 플링됩니다. 그렇지 않으면 AppBarLayout이 축소되고 RecyclerView가 깜박임을 방지합니다. 주어진 속도가 요구되는 시점까지 붕괴되고 속도가 남아있는 경우 RecyclerView는 원래 속도에서 AppBarLayout이 방금 소비 한 것을 뺀 속도로 떨어집니다.

위로 향하는 시나리오 : RecyclerView의 스크롤 오프셋이 0이 아닌 경우 원래 속도로 떨어집니다. 그것이 완료 되 자마자 여전히 남아있는 속도가 있다면 (즉, RecyclerView가 위치 0으로 스크롤 됨), AppBarLayout은 원래 속도에서 방금 소비 한 요구량을 뺀 지점까지 확장됩니다. 그렇지 않으면 AppBarLayout이 원래 속도가 요구하는 지점까지 확장됩니다.

AFAIK, 이것은 indended 동작입니다.

많은 성찰이 수반되며, 그것은 꽤 관습입니다. 그래도 문제가 없습니다. Kotlin으로 작성되었지만 이해해도 아무런 문제가 없습니다. IntelliJ Kotlin 플러그인을 사용하여 바이트 코드로 컴파일하고 Java로 다시 컴파일 할 수 있습니다. 그것을 사용하려면 android.support.v7.widget 패키지에 넣고 코드에서 AppBarLayout의 CoordinatorLayout.LayoutParams의 동작으로 설정하십시오 (또는 xml 적용 가능한 생성자 등을 추가하십시오)

/*
 * Copyright 2017 Julian Ostarek
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.support.v7.widget

import android.support.design.widget.AppBarLayout
import android.support.design.widget.CoordinatorLayout
import android.support.v4.widget.ScrollerCompat
import android.view.View
import android.widget.OverScroller

class SmoothScrollBehavior(recyclerView: RecyclerView) : AppBarLayout.Behavior() {
    // We're using this SplineOverScroller from deep inside the RecyclerView to calculate the fling distances
    private val splineOverScroller: Any
    private var isPositive = false

    init {
        val scrollerCompat = RecyclerView.ViewFlinger::class.java.getDeclaredField("mScroller").apply {
            isAccessible = true
        }.get(recyclerView.mViewFlinger)
        val overScroller = ScrollerCompat::class.java.getDeclaredField("mScroller").apply {
            isAccessible = true
        }.get(scrollerCompat)
        splineOverScroller = OverScroller::class.java.getDeclaredField("mScrollerY").apply {
            isAccessible = true
        }.get(overScroller)
    }

    override fun onNestedFling(coordinatorLayout: CoordinatorLayout?, child: AppBarLayout, target: View?, velocityX: Float, givenVelocity: Float, consumed: Boolean): Boolean {
        // Making sure the velocity has the correct sign (seems to be an issue)
        var velocityY: Float
        if (isPositive != givenVelocity > 0) {
            velocityY = givenVelocity * - 1
        } else velocityY = givenVelocity

        if (velocityY < 0) {
            // Decrement the velocity to the maximum velocity if necessary (in a negative sense)
            velocityY = Math.max(velocityY, - (target as RecyclerView).maxFlingVelocity.toFloat())

            val currentOffset = (target as RecyclerView).computeVerticalScrollOffset()
            if (currentOffset == 0) {
                super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, false)
                return true
            } else {
                val distance = getFlingDistance(velocityY.toInt()).toFloat()
                val remainingVelocity = - (distance - currentOffset) * (- velocityY / distance)
                if (remainingVelocity < 0) {
                    (target as RecyclerView).addOnScrollListener(object : RecyclerView.OnScrollListener() {
                        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                                recyclerView.post { recyclerView.removeOnScrollListener(this) }
                                if (recyclerView.computeVerticalScrollOffset() == 0) {
                                    super@SmoothScrollBehavior.onNestedFling(coordinatorLayout, child, target, velocityX, remainingVelocity, false)
                                }
                            }
                        }
                    })
                }
                return false
            }
        }
        // We're not getting here anyway, flings with positive velocity are handled in onNestedPreFling
        return false
    }

    override fun onNestedPreFling(coordinatorLayout: CoordinatorLayout?, child: AppBarLayout, target: View?, velocityX: Float, givenVelocity: Float): Boolean {
        // Making sure the velocity has the correct sign (seems to be an issue)
        var velocityY: Float
        if (isPositive != givenVelocity > 0) {
            velocityY = givenVelocity * - 1
        } else velocityY = givenVelocity

        if (velocityY > 0) {
            // Decrement to the maximum velocity if necessary
            velocityY = Math.min(velocityY, (target as RecyclerView).maxFlingVelocity.toFloat())

            val topBottomOffsetForScrollingSibling = AppBarLayout.Behavior::class.java.getDeclaredMethod("getTopBottomOffsetForScrollingSibling").apply {
                isAccessible = true
            }.invoke(this) as Int
            val isCollapsed = topBottomOffsetForScrollingSibling == - child.totalScrollRange

            // The AppBarlayout is collapsed, we'll let the RecyclerView handle the fling on its own
            if (isCollapsed)
                return false

            // The AppbarLayout is not collapsed, we'll calculate the remaining velocity, trigger the appbar to collapse and fling the RecyclerView manually (if necessary) as soon as that is done
            val distance = getFlingDistance(velocityY.toInt())
            val remainingVelocity = (distance - (child.totalScrollRange + topBottomOffsetForScrollingSibling)) * (velocityY / distance)

            if (remainingVelocity > 0) {
                (child as AppBarLayout).addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
                    override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
                        // The AppBarLayout is now collapsed
                        if (verticalOffset == - appBarLayout.totalScrollRange) {
                            (target as RecyclerView).mViewFlinger.fling(velocityX.toInt(), remainingVelocity.toInt())
                            appBarLayout.post { appBarLayout.removeOnOffsetChangedListener(this) }
                        }
                    }
                })
            }

            // Trigger the expansion of the AppBarLayout
            super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, false)
            // We don't let the RecyclerView fling already
            return true
        } else return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY)
    }

    override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout?, child: AppBarLayout?, target: View?, dx: Int, dy: Int, consumed: IntArray?) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed)
        isPositive = dy > 0
    }

    private fun getFlingDistance(velocity: Int): Double {
        return splineOverScroller::class.java.getDeclaredMethod("getSplineFlingDistance", Int::class.javaPrimitiveType).apply {
            isAccessible = true
        }.invoke(splineOverScroller, velocity) as Double
    }

}

어떻게 설정합니까?
ARR.s

0

이것이 내 프로젝트의 솔루션입니다.
Action_Down을 얻을 때 mScroller를 중지하십시오.

xml :

    <android.support.design.widget.AppBarLayout
        android:id="@+id/smooth_app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        app:elevation="0dp"
        app:layout_behavior="com.sogou.groupwenwen.view.topic.FixAppBarLayoutBehavior">

FixAppBarLayoutBehavior.java :

    public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
        if (ev.getAction() == ACTION_DOWN) {
            Object scroller = getSuperSuperField(this, "mScroller");
            if (scroller != null && scroller instanceof OverScroller) {
                OverScroller overScroller = (OverScroller) scroller;
                overScroller.abortAnimation();
            }
        }

        return super.onInterceptTouchEvent(parent, child, ev);
    }

    private Object getSuperSuperField(Object paramClass, String paramString) {
        Field field = null;
        Object object = null;
        try {
            field = paramClass.getClass().getSuperclass().getSuperclass().getDeclaredField(paramString);
            field.setAccessible(true);
            object = field.get(paramClass);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return object;
    }

//or check the raw file:
//https://github.com/shaopx/CoordinatorLayoutExample/blob/master/app/src/main/java/com/spx/coordinatorlayoutexample/util/FixAppBarLayoutBehavior.java

0

androidx의 경우

매니페스트 파일에 android : hardwareAccelerated = "false"줄이 있으면 삭제하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.