RecyclerView + AppBarLayout ile fırlatma


171

AppBarLayout ve CollapsingToolbarLayout ile yeni CoordinatorLayout'u kullanıyorum. AppBarLayout altında, bir içerik listesi ile bir RecyclerView var.

Listede yukarı ve aşağı kaydırdığımda fırlatma kaydırmanın RecyclerView üzerinde çalıştığını doğruladım. Ancak, aynı zamanda AppBarLayout genişleme sırasında sorunsuz kaydırma istiyorum.

CollaspingToolbarLayout'u genişletmek için yukarı kaydırırken, parmağınızı ekrandan kaldırdığınızda kaydırma hemen durur. Hızlı bir hareketle yukarı kaydırırsanız, bazen CollapsingToolbarLayout da yeniden daraltılır. RecyclerView ile bu davranış, bir NestedScrollView kullanmaktan çok farklı bir işlev gibi görünüyor.

Geri dönüşüm görünümünde farklı kaydırma özellikleri ayarlamaya çalıştım, ancak bunu anlayamadım.

Bazı kaydırma sorunlarını gösteren bir video. https://youtu.be/xMLKoJOsTAM

İşte RecyclerView (CheeseDetailActivity) ile ilgili sorunu gösteren bir örnek. https://github.com/tylerjroach/cheesesquare

İşte Chris Banes'ten bir NestedScrollView kullanan orijinal örnek. https://github.com/chrisbanes/cheesesquare


Aynı sorunu yaşıyorum (RecyclerView ile kullanıyorum). Herhangi bir uygulama için bir google oyun mağazası listesine bakarsanız, doğru davranıyor gibi görünüyor, bu yüzden orada kesinlikle bir çözüm var ...
Aneem

Hey Aneem, bunun en büyük çözüm olmadığını biliyorum, ancak şu kütüphaneyi denemeye başladım : github.com/ksoichiro/Android-ObservableScrollView . Özellikle sonuçlara ulaşmak için bu aktivitede ihtiyacım vardı: FlexibleSpaceWithImageRecyclerViewActivity.java. Düzenlemeden önce adınızı yanlış yazdığınız için üzgünüz. Otomatik
düzeltme

2
Burada aynı sorun, AppBarLayout kaçınarak sona erdi.
Renaud Cerrato

Evet. OvservableScrollView kütüphanesinden tam olarak neye ihtiyacım olduğunu elde ettim. Eminim gelecekteki sürümlerde düzeltilecektir.
tylerjroach

8
Fırlatma arabası, bir sorun gündeme geldi (ve kabul edildi).
Renaud Cerrato

Yanıtlar:


114

Kirill Boyarshinov'un cevabı neredeyse doğruydu.

Ana sorun RecyclerView bazen yanlış atış yönü veriyor olmasıdır, bu yüzden cevabına aşağıdaki kodu eklerseniz doğru çalışır:

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

Umarım bu yardımcı olur.


Günümü kurtardın! Kesinlikle iyi çalışıyor gibi görünüyor! Cevabınız neden kabul edilmiyor?
Zordid

9
geri dönüşüm görünümünüzün üst öğesi olarak bir SwipeRefreshLayout kullanıyorsanız, şu kodu ekleyin: if (target instanceof SwipeRefreshLayout && velocityY < 0) { target = ((SwipeRefreshLayout) target).getChildAt(0); }before if (target instanceof RecyclerView && velocityY < 0) {
LucasFM

1
+ 1 Bu düzeltmeyi incelerken, Google'ın bunu neden henüz çözmediğini anlamıyorum. Kod oldukça basit görünüyor.
Gaston Flores

3
Merhaba, appbarlayout ve Nestedscrollview ile aynı şeyi nasıl elde edersiniz ... Şimdiden teşekkürler ..
Harry Sharma

1
Benim için işe yaramadı = / Bu arada, bunu elde etmek için sınıfı destek paketine taşımanıza gerek yok, yapıcıya bir DragCallback kaydedebilirsiniz.
Augusto Carmo

69

O görünüyor v23güncelleme henüz sorunu çözmedi.

Aşağı fırlatıp düzeltmek için bir çeşit kesmek buldum. Püf noktası, ScrollingView'ın üst alt öğesi Bağdaştırıcıdaki verilerin başlangıcına yakınsa fling olayını yeniden kullanmaktır.

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

Düzeninizde şu şekilde kullanın:

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

DÜZENLEME: Fling olayı yeniden kullanımı verticalScrollOffset, üstten öğelerin miktarı yerine artık dayanmaktadır RecyclerView.

EDIT2: Hedefi ScrollingViewyerine arabirim örneği olarak denetleyin RecyclerView. Hem RecyclerViewve NestedScrollingViewonu yerine getirirler.


Layout_behavior hatası için dize türlerinin alınmasına izin verilmiyor
Vaisakh N

Test ettim ve daha iyi çalışıyorum adamım! ancak TOP_CHILD_FLING_THRESHOLD'un amacı nedir? ve neden 3?
Julio_oa

@Julio_oa TOP_CHILD_FLING_THRESHOLD, geri dönüşüm cihazı görünümü bu eşik değerinin altında olan öğeye kaydırıldığında fling olayının yeniden kullanılacağı anlamına gelir. Btw Ben verticalScrollOffsetdaha genel olan kullanmak için cevap güncelledim . Şimdi yukarı recyclerViewkaydırıldığında fling olayı tekrar kullanılacak .
Kirill Boyarshinov

Merhaba, appbarlayout ve Nestedscrollview ile aynı şeyi nasıl elde edersiniz ... Şimdiden teşekkürler ..
Harry Sharma

2
@Hardeep değişim target instanceof RecyclerViewiçin target instanceof NestedScrollView, veya daha fazlasına jenerik durum için target instanceof ScrollingView. Cevabı güncelledim.
Kirill Boyarshinov

15

Düzeltmeyi recyclerView OnScrollingListener uygulayarak buldum. şimdi çok iyi çalışıyor. Sorun, geri dönüşüm görünümünün yanlış tüketilen değeri sağlaması ve geri dönüşüm görünümünün en üste kaydırıldığında davranışın bilinmemesidir.

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

Gönderiniz için teşekkürler. Bu sayfadaki tüm cevapları denedim ve tecrübelerime göre bu en etkili cevap. Ancak, RecyclerView'ı yeterince kuvvetle kaydırmazsam, düzenimdeki RecylerView, AppBarLayout'un ekranı kaydırmadan önce dahili olarak kayar. Başka bir deyişle, RecyclerView'ı yeterince kuvvetle kaydırdığımda AppBar, RecyclerView dahili olarak kaydırma yapmadan ekrandan çıkar, ancak RecyclerView'i yeterince kuvvetle kaydırmazsam, RecyclerView AppbarLayout ekrandan çıkmadan önce dahili olarak kayar. Buna neyin sebep olduğunu biliyor musunuz?
Micah Simmons

Geri dönüşüm görünümü yine de dokunma olaylarını almaya devam ediyor, bu yüzden hala kaydırma yapıyor, nestedFling'deki davranış, appbarLayout'u aynı anda kaydırmak için canlandırılacak. Belki bunu değiştirmek için davranışta onInterceptTouch geçersiz kılmayı deneyebilirsiniz. Bana göre şu anki davranış gördüğüm şeyden kabul edilebilir. (Aynı şeyi görüp görmediğimizden emin değilim)
Mak Sing

@MakSing , bu en çok beklenen çözüm için gerçekten çok yardımcı oluyor CoordinatorLayoutve ViewPagerkurulum yapıyor. Diğer geliştiricilerin de bundan yararlanabilmesi için lütfen bir GIST yazın. Ben de bu çözümü paylaşıyorum. Tekrar teşekkürler.
Nitin Misra

1
@ Tüm çözümler için bu benim için en iyisi. OnNestedFling'e verilen hızı ayarladım biraz hız * 0.6f ... ona daha güzel bir akış veriyor gibi görünüyor.
saberrider

Benim için çalışıyor. @MakSing onScrolled yönteminde RecyclerViewAppBarBehavior değil, AppBarLayout.Behaviour'un onNestedFling'ini çağırmanız gerekir mi? Bana biraz garip geliyor.
Anton Malmygin

13

Destek tasarımı 26.0.0'dan beri düzeltildi.

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

2
Bunun yukarı doğru gitmesi gerekiyor. Bu, herkesin ayrıntılarla ilgilenmesi durumunda burada açıklanmaktadır .
Chris Dinon

1
Şimdi durum çubuğunda aşağı kaydırdığınızda durum çubuğuyla biraz aşağı iner ... süper can sıkıcı!
kutu

2
@Xiaozou 26.1.0 kullanıyorum ve hala fırlatmayla ilgili sorunlar yaşıyorum. Hızlı atış bazen zıt harekete neden olur (Hareketin hızı, onNestedFling yönteminde görüldüğü gibi zıt / yanlıştır). Xiaomi Redmi Note 3 ve Galaxy S3'te çoğaltıldı
Dor506

@ dor506 stackoverflow.com/a/47298312/782870 Zıt hareket sonucu dediğinizde aynı sorunun olup olmadığından emin değilim. Ama burada bir cevap gönderdim. Umarım yardımcı olur :)
vida



2

Bu benim Mizanpajım ve kaydırma gerektiği gibi çalışıyor.

<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

Şimdiye kadarki çözümüm Mak Sing ve Manolo Garcia'nın cevaplarına dayanıyor .

Tamamen mükemmel değil. Şimdilik, garip bir etkiyi önlemek için valide hızını nasıl yeniden hesaplayacağımı bilmiyorum: appbar kaydırma hızından daha hızlı genişleyebilir. Ancak genişletilmiş bir uygulama çubuğuna ve kaydırılmış geri dönüşüm görünümüne sahip duruma erişilemiyor.

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

    }

}

Yansıma kullanarak bir recyclerView (25.1.0 itibariyle) mevcut hızını elde edebilirsiniz: 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

Benim durumumda, fırlatmanın RecyclerViewsorunsuz bir şekilde kaydırmayacağı ve sıkışmasını sağlayan sorunu alıyordum .

Nedense, çünkü bu oldu benim koymak unutmuştu RecyclerViewbir yerNestedScrollView .

Aptalca bir hata, ama çözmem biraz zaman aldı ...


1

Ben AppBarLayout içine 1dp yükseklikte bir görünüm ekleyin ve o zaman çok daha iyi çalışır. Bu benim düzenim.

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


Yalnızca yukarı kaydırdığınızda çalışır. Yine de aşağı
Arthur

Benim için her iki yönde de iyi çalışıyor. 1dp görünümünü appbarlayout'a eklediniz mi? Sadece android lolipop ve kitkat'te test ettim.
Jachumbelechao Mantıkilla'ya

Eh, ben de araç çubuğunu sarar CollapsingToolbarLayout kullanıyorum. 1dp görüntüsünü bunun içine koydum. Bunun gibi AppBarLayout -> Çöken Araç ÇubuğuLayout -> Araç Çubuğu + 1dp görünümü
Arthur

CollapsingToolbarLayout ile iyi çalışıp çalışmadığını bilmiyorum. Sadece bu kodla test ettim. 1dp görünümünü CollapsingToolbarLayout'un dışına koymaya çalıştınız mı?
Jachumbelechao Unto Mantekilla

Evet. Yukarı kaydırma çalışır, aşağı kaydırma araç çubuğunu genişletmez.
Arthur

1

Zaten burada oldukça popüler çözümler var ama onlarla oynadıktan sonra benim için iyi çalışan daha basit bir çözüm buldum. Benim çözümüm aynı zamanda AppBarLayoutsadece kaydırılabilir içerik zirveye ulaştığında genişlemesini sağlıyor, buradaki diğer çözümlere göre bir avantaj.

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 nedir
ARR.s

1

Kabul edilen cevap benim için işe yaramadı çünkü RecyclerViewa SwipeRefreshLayoutve a'nın içinde bulundum ViewPager. Bu, RecyclerViewhiyerarşide bir arayan ve herhangi bir düzen için çalışması gereken geliştirilmiş sürümdür :

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

Cevap: Destek kitaplığı v26'da düzeltildi

ancak v26'nın kaçmakla ilgili bir sorunu var. Bazen, fırlatma çok zor olmasa bile AppBar tekrar geri döner.

Appbar'daki sıçrayan efekti nasıl kaldırabilirim?

V26'yı desteklemek için güncellerken aynı sorunla karşılaşırsanız, işte bu cevabın özeti .

Çözüm : AppBar'ın varsayılan Davranışını genişletin ve NestedScroll henüz durdurulmamışken AppBar'a dokunulduğunda AppBar.Behavior'un onNestedPreScroll () ve onNestedScroll () çağrısını engelleyin.


0

Julian Os haklı.

Geri dönüşüm görünümü eşiğin altındaysa ve kaydırılırsa Manolo Garcia'nın cevabı çalışmaz. Geri offsetdönüşüm görünümünün ve velocity to the distanceöğe konumunu karşılaştırmamalısınız.

Julian'ın kotlin koduna atıfta bulunarak yansıma yaptım ve yansımayı çıkarttım.

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

}

reslove olamazBaseApplication
ARR.s

@ ARR.s üzgünüm, sadece aşağıdaki gibi bağlamınızı değiştirin.
정성민

YOUR_CONTEXT.getResources (). GetDisplayMetrics (). Yoğunluk * 160.0f;
정성민


0

Google sorun izleyicisine referansla , destek kütüphanesinin Android 26.0.0-beta2 sürümü ile düzeltildi

Lütfen Android destek kitaplığı sürüm 26.0.0-beta2'yi güncelleyin .

Herhangi bir sorun devam ederse, lütfen Google sorun izleyicisine incelemek üzere yeniden açılacaklarını bildirin.


0

Yukarıdaki yanıtlar olarak buraya başka bir cevap eklemek ya ihtiyaçlarımı tam olarak karşılayamadı ya da çok iyi çalışmadı. Bu kısmen burada yayılan fikirlere dayanmaktadır.

Peki bu ne yapıyor?

Senaryo aşağı doğru fırlatılır: AppBarLayout daraltılırsa, RecyclerView uygulamasının hiçbir şey yapmadan kendi kendine fırlamasını sağlar. Aksi takdirde, AppBarLayout öğesini daraltır ve RecyclerView öğesinin atmasını engeller. Daraltılır kesilmez (verilen hızın talep ettiği noktaya kadar) ve kalan hız varsa, RecyclerView, orijinal hız eksi AppBarLayout'un daralttığı harcanan eksi ile atılır.

Senaryo yukarı doğru fırlıyor: RecyclerView öğesinin kaydırma ofseti sıfır değilse, orijinal hızda fırlatılır. Bu işlem biter bitmez ve hala hız kalırsa (yani RecyclerView 0 konumuna kaydırılır), AppBarLayout orijinal hızın sadece tüketilen talepleri eksi noktaya kadar genişletilir. Aksi takdirde, AppBarLayout orijinal hızın istediği noktaya kadar genişletilir.

AFAIK, bu amaçlanan davranış.

Çok fazla yansıma var ve oldukça özel. Henüz bir sorun bulunamadı. Aynı zamanda Kotlin dilinde de yazılmıştır, ancak bunu anlamak sorun olmamalı. IntelliJ Kotlin eklentisini bytecode -> olarak derlemek ve Java'ya geri dönüştürmek için kullanabilirsiniz. Kullanmak için android.support.v7.widget paketine yerleştirin ve AppBarLayout'un CoordinatorLayout.LayoutParams kodundaki davranışı olarak ayarlayın (veya xml uygulanabilir kurucusunu veya başka bir şeyi ekleyin)

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

}

Nasıl ayarlanır?
ARR.s

0

bu benim projemdeki çözümüm.
Action_Down'ı aldığınızda mScroller'ı durdurmanız yeterlidir

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 için,

Manifest dosyanızda android: hardwareAccelerated = "false" satırı varsa silin.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.