Flinging dengan RecyclerView + AppBarLayout


171

Saya menggunakan CoordinatorLayout baru dengan AppBarLayout dan CollapsingToolbarLayout. Di bawah AppBarLayout, saya memiliki RecyclerView dengan daftar konten.

Saya telah memverifikasi bahwa fling scrolling berfungsi pada RecyclerView ketika saya menggulir daftar ke atas dan ke bawah. Namun, saya juga ingin AppBarLayout untuk menggulir dengan lancar saat ekspansi.

Saat menggulir ke atas untuk memperluas CollaspingToolbarLayout, gulir segera berhenti begitu mengangkat jari Anda dari layar. Jika Anda menggulir ke atas dalam gerakan cepat, terkadang CollapsingToolbarLayout kembali runtuh juga. Perilaku ini dengan RecyclerView tampaknya berfungsi jauh berbeda dari ketika menggunakan NestedScrollView.

Saya sudah mencoba untuk mengatur properti gulir yang berbeda pada recyclerview tetapi saya belum dapat mengetahuinya.

Berikut adalah video yang menunjukkan beberapa masalah pengguliran. https://youtu.be/xMLKoJOsTAM

Berikut adalah contoh yang menunjukkan masalah dengan RecyclerView (CheeseDetailActivity). https://github.com/tylerjroach/cheesesquare

Ini adalah contoh asli yang menggunakan NestedScrollView dari Chris Banes. https://github.com/chrisbanes/cheesesquare


Saya mengalami masalah yang sama persis ini (Saya menggunakan dengan RecyclerView). Jika Anda melihat daftar google play store untuk aplikasi apa pun, sepertinya itu berlaku dengan benar, jadi pasti ada solusi di luar sana ...
Aneem

Hai Aneem, saya tahu ini bukan solusi terhebat tetapi saya mulai bereksperimen dengan perpustakaan ini: github.com/ksoichiro/Android-ObservableScrollView . Terutama pada kegiatan ini untuk mencapai hasil yang saya butuhkan: FlexibleSpaceWithImageRecyclerViewActivity.java. Maaf karena salah mengeja nama Anda sebelum diedit.
Koreksi otomatis

2
Masalah yang sama di sini, saya akhirnya menghindari AppBarLayout.
Renaud Cerrato

Ya. Saya akhirnya mendapatkan apa yang saya butuhkan dari perpustakaan OvservableScrollView. Saya yakin ini akan diperbaiki di versi mendatang.
tylerjroach

8
Persetan itu buggy, masalah telah diangkat (dan diterima).
Renaud Cerrato

Jawaban:


114

Jawaban Kirill Boyarshinov hampir benar.

Masalah utama adalah bahwa RecyclerView kadang-kadang memberikan arah yang salah, jadi jika Anda menambahkan kode berikut untuk jawabannya, ia berfungsi dengan benar:

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

Saya harap ini membantu.


Kamu menyelamatkan hariku! Tampaknya bekerja dengan sangat baik! Mengapa jawaban Anda tidak diterima?
Zordid

9
jika Anda menggunakan SwipeRefreshLayout sebagai induk dari recyclerview Anda, tambahkan saja kode ini: if (target instanceof SwipeRefreshLayout && velocityY < 0) { target = ((SwipeRefreshLayout) target).getChildAt(0); }before if (target instanceof RecyclerView && velocityY < 0) {
LucasFM

1
+1 Menganalisis perbaikan ini, saya tidak mengerti Mengapa Google belum memperbaikinya. Kode ini tampaknya cukup sederhana.
Gaston Flores

3
Halo cara mencapai hal yang sama dengan appbarlayout dan Nestedscrollview ... Terima kasih sebelumnya ..
Harry Sharma

1
Itu tidak bekerja untuk saya = / Omong-omong, Anda tidak perlu memindahkan kelas ke paket dukungan untuk mencapainya, Anda dapat mendaftarkan DragCallback di konstruktor.
Augusto Carmo

69

Tampaknya v23pembaruan belum memperbaikinya.

Saya telah menemukan semacam peretasan untuk memperbaikinya dengan melemparkan. Caranya adalah dengan mempertimbangkan kembali peristiwa melempar jika anak teratas ScrollingView dekat dengan awal data dalam Adaptor.

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

Gunakan dalam tata letak Anda seperti itu:

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

SUNTING : Pemrosesan ulang peristiwa fling sekarang didasarkan pada verticalScrollOffsetbukan jumlah item pada dari atas RecyclerView.

EDIT2: Periksa target sebagai ScrollingViewcontoh antarmuka bukan RecyclerView. Keduanya RecyclerViewdan NestedScrollingViewmengimplementasikannya.


Mendapatkan tipe string tidak diperbolehkan untuk kesalahan
layout_behavior

Saya mengujinya dan bekerja dengan lebih baik! tetapi apa tujuan dari TOP_CHILD_FLING_THRESHOLD? dan mengapa 3?
Julio_oa

@Julio_oa TOP_CHILD_FLING_THRESHOLD berarti bahwa peristiwa melempar akan dipertimbangkan kembali jika tampilan pendaur ulang digulir ke elemen yang posisinya di bawah nilai ambang batas ini. Btw saya memperbarui jawaban untuk menggunakan verticalScrollOffsetyang lebih umum. Sekarang acara melempar akan dipertimbangkan kembali ketika recyclerViewdigulir ke atas.
Kirill Boyarshinov

Halo cara mencapai hal yang sama dengan appbarlayout dan Nestedscrollview ... Terima kasih sebelumnya ..
Harry Sharma

2
@Hardeep ganti target instanceof RecyclerViewke target instanceof NestedScrollView, atau lebih untuk generik target instanceof ScrollingView. Saya memperbarui jawabannya.
Kirill Boyarshinov

15

Saya telah menemukan perbaikan dengan menerapkan OnScrollingListener ke recyclerView. sekarang ini bekerja dengan sangat baik. Masalahnya adalah bahwa recyclerview memberikan nilai yang dikonsumsi salah dan perilaku tidak tahu kapan recyclerview digulir ke atas.

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

Terima kasih atas kiriman Anda. Saya sudah mencoba semua jawaban di halaman ini & menurut pengalaman saya ini adalah jawaban yang paling efektif. Tapi, RecylerView di layout saya menggulir internal sebelum AppBarLayout menggulir layar jika saya tidak menggulir RecyclerView dengan kekuatan yang cukup. Dengan kata lain, ketika saya menggulir RecyclerView dengan kekuatan yang cukup, AppBar menggulir layar tanpa menggulir RecyclerView secara internal, tetapi ketika saya tidak menggulir RecyclerView dengan kekuatan yang cukup, RecyclerView menggulir secara internal sebelum AppbarLayout menggulir layar. Apakah Anda tahu apa penyebabnya?
Micah Simmons

Tampilan pendaur ulang masih menerima acara sentuh yang menyebabkannya tetap bergulir, perilaku diNestedFling akan menghidupkan gulir appbarLayout pada saat yang sama. Mungkin Anda dapat mencoba menimpa diInterceptTouch dalam perilaku untuk mengubah ini. Bagi saya perilaku saat ini dapat diterima dari apa yang saya lihat. (tidak yakin apakah kita melihat hal yang sama)
Mak Sing

@MakSing sangat membantu CoordinatorLayoutdan ViewPagermengatur terima kasih banyak untuk solusi yang paling ditunggu-tunggu ini. Tolong tuliskan GIST untuk hal yang sama sehingga pengembang lainnya juga dapat memperoleh manfaat darinya. Saya juga membagikan solusi ini. Terima kasih lagi.
Nitin Misra

1
@Membuat Mati semua solusi, ini bekerja paling baik untuk saya. Saya menyesuaikan kecepatan yang diserahkan ke onNestedFling sedikit kecepatan * 0.6f ... tampaknya memberikan aliran yang lebih bagus untuk itu.
saberrider

Bekerja untukku. @MakSing Apakah dalam metode onScroll Anda harus memanggil onNestedFling dari AppBarLayout.Behavior dan bukan dari RecyclerViewAppBarBehavior? Agak aneh bagi saya.
Anton Malmygin

13

Sudah diperbaiki sejak desain dukungan 26.0.0.

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

2
Ini perlu bergerak ke atas. Ini dijelaskan di sini jika ada yang tertarik dengan detailnya.
Chris Dinon

1
Sekarang tampaknya ada masalah dengan bilah status, di mana ketika Anda menggulir ke bawah bilah status turun sedikit dengan gulir ... itu sangat menjengkelkan!
kotak

2
@Xiaozou Saya menggunakan 26.1.0 dan masih memiliki masalah dengan melemparkan. Quick fling terkadang menghasilkan gerakan berlawanan (Kecepatan gerakan berlawanan / salah seperti yang dapat dilihat pada metode onNestedFling). Direproduksi dalam Xiaomi Redmi Note 3 dan Galaxy S3
dor506

@ dor506 stackoverflow.com/a/47298312/782870 Saya tidak yakin apakah kami memiliki masalah yang sama ketika Anda mengatakan hasil gerakan yang berlawanan. Tapi saya mengirim jawaban di sini. Semoga membantu :)
vida



2

Ini adalah Layout saya dan gulirnya berfungsi seperti seharusnya.

<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

Solusi saya sejauh ini, berdasarkan Mak Sing dan Manolo Garcia jawaban .

Itu tidak sepenuhnya sempurna. Untuk saat ini saya tidak tahu bagaimana cara menghitung ulang kecepatan valide untuk menghindari efek aneh: appbar dapat meluas lebih cepat dari kecepatan gulir. Tetapi status dengan appbar yang diperluas dan tampilan pendaur ulang yang digulir tidak dapat dijangkau.

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

    }

}

Anda dapat memperoleh kecepatan saat ini dari recyclerView (mulai 25.1.0) menggunakan refleksi: 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

Dalam kasus saya, saya mendapatkan masalah di mana si melemparkan RecyclerView tidak akan menggulirkannya dengan lancar, membuatnya macet.

Ini karena, untuk beberapa alasan, saya lupa bahwa saya telah memasukkan RecyclerViewaNestedScrollView .

Itu kesalahan konyol, tapi butuh beberapa saat untuk mengetahuinya ...


1

Saya menambahkan pandangan ketinggian 1dp di dalam AppBarLayout dan kemudian bekerja jauh lebih baik. Ini adalah tata letak saya.

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


Ini hanya berfungsi jika Anda menggulir ke atas. Tidak ketika Anda gulir ke bawah
Arthur

Bagi saya bekerja dengan baik di kedua arah. Apakah Anda menambahkan tampilan 1dp di dalam appbarlayout ?. Saya hanya mengujinya di android lollipop dan kitkat.
Jachumbelechao Unto Mantekilla

Yah, saya juga menggunakan CollapsingToolbarLayout yang membungkus bilah alat. Saya menempatkan tampilan 1dp di dalamnya. Ini agak seperti AppBarLayout ini -> CollapsingToolbarLayout -> Toolbar + 1dp view
Arthur

Saya tidak tahu apakah itu berfungsi baik dengan CollapsingToolbarLayout. Saya hanya menguji dengan kode ini. Apakah Anda mencoba meletakkan tampilan 1dp di luar CollapsingToolbarLayout?
Jachumbelechao Unto Mantekilla

Iya. Gulir ke atas bekerja, gulir ke bawah tidak memperluas bilah alat.
Arthur

1

Sudah beberapa solusi yang cukup populer di sini, tetapi setelah bermain dengan mereka, saya datang dengan solusi yang lebih sederhana yang bekerja dengan baik untuk saya. Solusi saya juga memastikan bahwa AppBarLayoutitu hanya diperluas ketika konten yang dapat digulir mencapai bagian atas, sebuah keunggulan dibandingkan solusi lain di sini.

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

apa itu mPrevDy
ARR.s

1

Jawaban yang diterima tidak berhasil untuk saya karena saya punya RecyclerViewdi dalam a SwipeRefreshLayoutdan a ViewPager. Ini adalah versi yang disempurnakan yang mencari RecyclerViewhierarki dan harus berfungsi untuk tata letak apa pun:

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

Jawaban: Ini diperbaiki di perpustakaan dukungan v26

tetapi v26 memiliki beberapa masalah dalam melemparkan. Terkadang, AppBar memantul kembali bahkan meskipun melempar tidak terlalu keras.

Bagaimana cara menghapus efek memantul pada appbar?

Jika Anda mengalami masalah yang sama saat memperbarui untuk mendukung v26, inilah ringkasan jawaban ini .

Solusi : Memperpanjang Perilaku default AppBar dan memblokir panggilan untuk AppBar.Behavi onNestedPreScroll () dan onNestedScroll () saat AppBar disentuh sementara NestedScroll belum berhenti.


0

Julian Os benar.

Jawaban Manolo Garcia tidak berfungsi jika recyclerview di bawah ambang batas dan gulir. Anda harus membandingkan tampilan offsetpendaur ulang dan velocity to the distance, bukan posisi item.

Saya membuat versi java dengan mengacu pada kode kotlin julian dan kurangi refleksi.

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

}

tidak bisa resloveBaseApplication
ARR.s

@ ARR. Maaf, Anda hanya menggantinya konteks Anda seperti di bawah ini.
정성민

YOUR_CONTEXT.getResources (). GetDisplayMetrics (). Density * 160.0f;
정성민


0

Dengan referensi ke pelacak masalah Google , telah diperbaiki dengan versi Android 26.0.0-beta2 perpustakaan dukungan

Harap perbarui perpustakaan dukungan Android Anda versi 26.0.0-beta2.

Jika ada masalah, laporkan di pelacak masalah Google mereka akan membuka kembali untuk memeriksa.


0

Menambahkan jawaban lain di sini sebagai yang di atas tidak memenuhi kebutuhan saya sepenuhnya atau tidak bekerja dengan baik. Yang ini sebagian didasarkan pada ide-ide yang tersebar di sini.

Jadi apa yang dilakukan orang ini?

Skenario ke bawah melemparkan: Jika AppBarLayout runtuh, itu memungkinkan RecyclerView melemparkan sendiri tanpa melakukan apa pun. Jika tidak, itu akan merobohkan AppBarLayout dan mencegah RecyclerView dari melakukan hubungan asmara. Segera setelah itu diciutkan (sampai pada titik yang diminta oleh kecepatan yang diberikan) dan jika ada kecepatan yang tersisa, RecyclerView akan terlempar dengan kecepatan asli minus apa yang dikonsumsi oleh AppBarLayout yang baru saja runtuh.

Skenario ke atas melempar: Jika gulir offset RecyclerView tidak nol, itu akan terlempar dengan kecepatan asli. Segera setelah itu selesai dan jika masih ada kecepatan yang tersisa (yaitu RecyclerView digulir ke posisi 0), AppBarLayout akan diperluas ke titik bahwa kecepatan asli dikurangi permintaan yang baru dikonsumsi. Jika tidak, AppBarLayout akan diperluas hingga ke titik yang dituntut oleh kecepatan asli.

AFAIK, ini adalah perilaku yang berubah-ubah.

Ada banyak refleksi yang terlibat, dan itu cukup khusus. Namun tidak ada masalah yang ditemukan. Itu juga ditulis di Kotlin, tetapi memahaminya seharusnya tidak ada masalah. Anda dapat menggunakan plugin IntelliJ Kotlin untuk mengkompilasinya ke bytecode -> dan mendekompilasinya kembali ke Jawa. Untuk menggunakannya, letakkan dalam paket android.support.v7.widget dan tetapkan sebagai AppatorbayL's CoordinatorLayout.LayoutParams perilaku dalam kode (atau tambahkan konstruktor xml yang berlaku atau sesuatu)

/*
 * 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
    }

}

Bagaimana cara mengaturnya?
ARR.s

0

ini solusi saya di proyek saya.
cukup hentikan mScroller saat mendapatkan Action_Down

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

untuk androidx,

Jika file manifes Anda memiliki baris android: hardwareAccelerated = "false", hapuslah.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.