RecyclerView + AppBarLayoutでフリング


171

AppBarLayoutおよびCollapsingToolbarLayoutで新しいCoordinatorLayoutを使用しています。AppBarLayoutの下に、コンテンツのリストを含むRecyclerViewがあります。

リストを上下にスクロールすると、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

こんにちは、これが最高の解決策ではないことはわかっていますが、このライブラリgithub.com/ksoichiro/Android-ObservableScrollViewを試してみました。特に、私が必要とする結果を達成するために、このアクティビティで:FlexibleSpaceWithImageRecyclerViewActivity.java。編集前に名前のスペルを間違えて申し訳ありません。
Autocorrect

2
ここでも同じ問題で、AppBarLayoutを回避してしまいました。
Renaud Cerrato 2015年

うん。私はOvservableScrollViewライブラリから必要なものを正確に取得することになりました。私はそれが将来のバージョンで修正されると確信しています。
tylerjroach 2015年

8
フリングはバギーであり、問題が発生しました(そして受け入れられました)。
Renaud Cerrato、2015

回答:


114

キリル・ボヤルシノフの答えはほぼ正しかった。

主な問題は、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
SwipeRefreshLayoutをrecyclerviewの親として使用している場合は、次のコードを追加するだけです。 if (target instanceof SwipeRefreshLayout && velocityY < 0) { target = ((SwipeRefreshLayout) target).getChildAt(0); }前に if (target instanceof RecyclerView && velocityY < 0) {
LucasFM

1
+ 1この修正を分析しても、Googleがまだ修正していない理由がわかりません。コードは非常に単純なようです。
Gaston Flores

3
こんにちは、どのようにappbarlayoutとNestedscrollviewと同じことを達成するために...事前のおかげで...
ハリー・シャルマ

1
私にとってはうまくいきませんでした= /ちなみに、それを実現するためにクラスをサポートパッケージに移動する必要はありません。コンストラクタにDragCallbackを登録できます。
アウグストカルモ2016年

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>

編集: Flingイベントの再利用はverticalScrollOffset、上からのアイテムの量ではなくに基づくようになりましたRecyclerView

EDIT2:ターゲットをScrollingViewインターフェースインスタンスとしてではなくとしてチェックしますRecyclerView。両方RecyclerViewNestedScrollingViewそれを実装します。


文字列型の取得は、layout_behaviorエラーでは許可されていません
Vaisakh N

私はそれをテストし、より良い人を動かします!しかし、TOP_CHILD_FLING_THRESHOLDの目的は何ですか?なぜそれが3なのか?
Julio_oa 2016

@Julio_oa TOP_CHILD_FLING_THRESHOLDは、位置がこのしきい値を下回る要素にリサイクラービューがスクロールされると、フリングイベントが再消費されることを意味します。ところで私verticalScrollOffsetは使用する答えをより一般的なものに更新しました。recyclerViewを上にスクロールすると、投げイベントが再消費されるようになりました。
キリル・ボヤルシノフ2016

こんにちは、どのようにappbarlayoutとNestedscrollviewと同じことを達成するために...事前のおかげで...
ハリー・シャルマ

2
@Hardeep target instanceof RecyclerViewをにtarget instanceof NestedScrollView、またはより一般的な場合はに変更しtarget instanceof ScrollingViewます。答えを更新しました。
Kirill Boyarshinov 16

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

投稿ありがとうございます。私はこのページのすべての答えを試しましたが、私の経験では、これが最も効果的な答えです。しかし、RecyclerViewを十分な力でスクロールしないと、レイアウトのRecylerViewがAppBarLayoutが画面からスクロールオフする前に内部的にスクロールします。言い換えると、RecyclerViewを十分な力でスクロールすると、AppBarLayoutが画面からスクロールする前に、RecyclerViewを十分に強制的にスクロールせずにRecyclerViewを内部からスクロールします。何が原因なのか知っていますか?
Micah Simmons

recyclerviewは引き続きタッチイベントを受信するため、スクロールが継続されます。onNestedFlingの動作はアニメーション化され、同時にappbarLayoutをスクロールします。これを変更するには、ビヘイビアーでonInterceptTouchをオーバーライドしてみてください。私には、現在の行動は私が見るものから受け入れられます。(同じものが表示されているかどうかは不明)
Mak Sing

@MakSingは、この最も待ち望まれているソリューションに非常に役立ちCoordinatorLayoutViewPagerセットアップに非常に感謝しています。他の開発者もGISTの恩恵を受けることができるように、GISTを作成してください。私もこのソリューションを共有しています。再度、感謝します。
Nitin Misra 2016年

1
@MakSingすべてのソリューションをオフにすると、これが私にとって最も効果的です。onNestedFlingに渡された速度を少し調整しました。速度は少し* 0.6f ...流れが良くなるようです。
セイバーライダー2016年

私のために働く。@MakSing onScrolledメソッドでは、RecyclerViewAppBarBehaviorではなくAppBarLayout.BehaviorのonNestedFlingを呼び出す必要がありますか?私には少し奇妙に思えます。
Anton Malmygin 16

13

サポートデザイン26.0.0以降、修正されています。

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

2
これは上に移動する必要があります。これは、誰かが詳細に興味がある場合に備えここで説明されています。
Chris Dinon 2017

1
ステータスバーに問題があるようです。下にスクロールすると、ステータスバーが少し下がってスクロールします。非常に迷惑です。
ボックス

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に基づいています回答ます。

完全ではありません。現時点では、奇妙な効果を回避するために有効速度を再計算する方法がわかりません。アプリバーはスクロール速度よりも速く拡大できます。しかし、appbarが展開され、リサイクルされたビューがスクロールされた状態には到達できません。

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

    }

}

あなたは、リフレクションを使用して(25.1.0のような)recyclerViewの現在の速度を得ることができます: 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());
ニコラス・

2

私の場合、私は RecyclerViewとスムーズにスクロールされず、スタックするという問題が発生しました。

何らかの理由で、これはだった私は私を入れていたことを忘れていましたRecyclerViewNestedScrollView

それは愚かな間違いですが、それを理解するのにしばらく時間がかかりました...


1

AppBarLayout内に1 dpの高さのビューを追加すると、はるかにうまく機能します。これが私のレイアウトです。

  <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も使用しています。その中に1 dpビューを配置しました。それはちょっとこのAppBarLayout-> CollapsingToolbarLayout-> Toolbar + 1dp view
Arthur

CollapsingToolbarLayoutでうまく機能するかどうかはわかりません。私はこのコードでのみテストしました。1dpビューをCollapsingToolbarLayoutの外側に配置しようとしましたか?
Jachumbelechao Unto Mantekilla 2015

はい。上にスクロールしても機能し、下にスクロールしてもツールバーは展開されません。
アーサー

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中にSwipeRefreshLayoutとがあったので、受け入れられた答えはうまくいきませんでした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が再び跳ね返ることがあります。

appbarのバウンス効果を削除するにはどうすればよいですか?

v26をサポートするように更新するときに同じ問題が発生した場合は、この回答の概要を以下に示します

解決策:AppBarのデフォルトの動作を拡張し、NestedScrollがまだ停止していないときにAppBarがタッチされると、AppBar.BehaviorのonNestedPreScroll()およびonNestedScroll()の呼び出しをブロックします。


0

ジュリアン・オスはその通りです。

recyclerviewがしきい値を下回ってスクロールしている場合、Manolo Garciaの回答は機能しません。アイテムの位置ではなく、offsetrecyclerviewとを比較する必要がありvelocity to the distanceます。

ジュリアンのコトリンコードを参考にして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));
    }

}

再愛することはできませんBaseApplication
ARR。's

@ ARR.s申し訳ありませんが、以下のようにコンテキストに置き換えるだけです。
'2017

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



0

上記の答えが私のニーズを完全に満たしていないか、うまく機能しなかったため、ここに別の答えを追加します。これは部分的にここで広まったアイデアに基づいています。

これは何をするのでしょうか?

シナリオの下方向へのフリング:AppBarLayoutが折りたたまれている場合、RecyclerViewは何もせずに単独でフリングできます。それ以外の場合は、AppBarLayoutを折りたたみ、RecyclerViewが実行できないようにします。(指定された速度が要求するポイントまで)折りたたまれた直後に、速度が残っている場合、RecyclerViewは、元の速度からAppBarLayoutが消費したものを差し引いた速度で飛ばされます。

シナリオ上向きフリング:RecyclerViewのスクロールオフセットがゼロでない場合、元の速度でフリングされます。それが終了するとすぐに、まだ速度が残っている場合(つまり、RecyclerViewが位置0にスクロールされた場合)、AppBarLayoutは、元の速度から消費されたばかりの需要が差し引かれるポイントまで拡張されます。それ以外の場合、AppBarLayoutは元の速度が要求するポイントまで拡張されます。

私の知る限り、これは意図された動作です。

多くの反射が含まれており、それはかなりカスタムです。まだ問題は見つかりませんでした。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 2017

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.