Schleudern mit RecyclerView + AppBarLayout


171

Ich verwende das neue CoordinatorLayout mit AppBarLayout und CollapsingToolbarLayout. Unter AppBarLayout befindet sich eine RecyclerView mit einer Inhaltsliste.

Ich habe überprüft, ob das Scrollen in der RecyclerView funktioniert, wenn ich in der Liste nach oben und unten scrolle. Ich möchte jedoch auch, dass das AppBarLayout während der Erweiterung reibungslos scrollt.

Wenn Sie nach oben scrollen, um das CollaspingToolbarLayout zu erweitern, wird der Bildlauf sofort beendet, sobald Sie Ihren Finger vom Bildschirm heben. Wenn Sie in einer schnellen Bewegung nach oben scrollen, wird das CollapsingToolbarLayout manchmal auch wieder reduziert. Dieses Verhalten mit RecyclerView scheint ganz anders zu funktionieren als bei Verwendung von NestedScrollView.

Ich habe versucht, verschiedene Bildlaufeigenschaften in der Recyclingansicht festzulegen, konnte dies jedoch nicht herausfinden.

Hier ist ein Video, das einige Probleme beim Scrollen zeigt. https://youtu.be/xMLKoJOsTAM

Hier ist ein Beispiel, das das Problem mit RecyclerView (CheeseDetailActivity) zeigt. https://github.com/tylerjroach/cheesesquare

Hier ist das ursprüngliche Beispiel, das eine NestedScrollView von Chris Banes verwendet. https://github.com/chrisbanes/cheesesquare


Ich habe genau das gleiche Problem (ich verwende es mit einem RecyclerView). Wenn Sie sich eine Google Play Store-Liste für eine App ansehen, scheint sie sich korrekt zu verhalten, sodass es definitiv eine Lösung gibt ...
Aneem

Hey Aneem, ich weiß, dass dies nicht die beste Lösung ist, aber ich habe angefangen, mit dieser Bibliothek zu experimentieren: github.com/ksoichiro/Android-ObservableScrollView . Besonders bei dieser Aktivität, um die Ergebnisse zu erzielen, die ich brauchte: FlexibleSpaceWithImageRecyclerViewActivity.java. Es tut uns leid, dass Sie Ihren Namen vor der Bearbeitung falsch geschrieben haben. Autokorrektur ..
tylerjroach

2
Das gleiche Problem hier, ich habe AppBarLayout vermieden.
Renaud Cerrato

Ja. Am Ende bekam ich genau das, was ich brauchte, aus der OvservableScrollView-Bibliothek. Ich bin sicher, dass es in zukünftigen Versionen behoben wird.
Tylerjroach

8
Der Fling ist fehlerhaft, ein Problem wurde angesprochen (und akzeptiert).
Renaud Cerrato

Antworten:


114

Die Antwort von Kirill Boyarshinov war fast richtig.

Das Hauptproblem besteht darin, dass die RecyclerView manchmal eine falsche Schleuderrichtung angibt. Wenn Sie also den folgenden Code zu seiner Antwort hinzufügen, funktioniert dies ordnungsgemäß:

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

Ich hoffe das hilft.


Du hast meinen Tag gerettet! Scheint absolut gut zu funktionieren! Warum wird Ihre Antwort nicht akzeptiert?
Zordid

9
Wenn Sie ein SwipeRefreshLayout als übergeordnetes Element Ihrer Recycling-Ansicht verwenden, fügen Sie einfach diesen Code hinzu: if (target instanceof SwipeRefreshLayout && velocityY < 0) { target = ((SwipeRefreshLayout) target).getChildAt(0); }before if (target instanceof RecyclerView && velocityY < 0) {
LucasFM

1
+ 1 Bei der Analyse dieses Fixes verstehe ich nicht, warum Google dies noch nicht behoben hat. Der Code scheint recht einfach zu sein.
Gaston Flores

3
Hallo, wie man das gleiche mit Appbarlayout und Nestedscrollview erreicht ... Danke im Voraus ..
Harry Sharma

1
Bei mir hat es nicht funktioniert = / Übrigens müssen Sie die Klasse nicht in das Support Package verschieben, um dies zu erreichen. Sie können einen DragCallback im Konstruktor registrieren.
Augusto Carmo

69

Scheint, dass das v23Update es noch nicht behoben hat.

Ich habe eine Art Hack gefunden, um das Problem zu beheben. Der Trick besteht darin, das Fling-Ereignis wieder aufzunehmen, wenn sich das oberste untergeordnete Element von ScrollingView nahe am Datenanfang in Adapter befindet.

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

Verwenden Sie es in Ihrem Layout wie folgt:

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

BEARBEITEN : Die Wiederaufnahme des Fling-Ereignisses basiert jetzt auf verticalScrollOffsetder Anzahl der Elemente von oben auf RecyclerView.

EDIT2: Überprüfen Sie das Ziel als ScrollingViewSchnittstelleninstanz anstelle von RecyclerView. Beides RecyclerViewund NestedScrollingViewimplementieren.


Das Abrufen von Zeichenfolgentypen ist für den Fehler
layout_behavior

Ich habe es getestet und arbeitet besser Mann! aber was ist der Zweck des TOP_CHILD_FLING_THRESHOLD? und warum ist es 3?
Julio_oa

@Julio_oa TOP_CHILD_FLING_THRESHOLD bedeutet, dass das Schleuderereignis wieder aufgenommen wird, wenn die Recycler-Ansicht zu dem Element gescrollt wird, dessen Position unter diesem Schwellenwert liegt. Übrigens habe ich die Antwort aktualisiert, um verticalScrollOffsetsie allgemeiner zu verwenden. Jetzt wird das Schleuderereignis wieder aufgenommen, wenn recyclerViewnach oben gescrollt wird.
Kirill Boyarshinov

Hallo, wie man das gleiche mit Appbarlayout und Nestedscrollview erreicht ... Danke im Voraus ..
Harry Sharma

2
@Hardeep ändern target instanceof RecyclerViewzu target instanceof NestedScrollViewoder mehr für generischen Fall zu target instanceof ScrollingView. Ich habe die Antwort aktualisiert.
Kirill Boyarshinov

15

Ich habe das Update gefunden, indem ich OnScrollingListener auf die recyclerView angewendet habe. jetzt funktioniert es sehr gut. Das Problem ist, dass recyclerview den falschen verbrauchten Wert bereitgestellt hat und das Verhalten nicht weiß, wann die recyclerview nach oben gescrollt wird.

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

Danke für deinen Beitrag. Ich habe alle Antworten auf dieser Seite ausprobiert und meiner Erfahrung nach ist dies die effektivste Antwort. Die RecylerView in meinem Layout wird jedoch intern gescrollt, bevor das AppBarLayout vom Bildschirm gescrollt wurde, wenn ich die RecyclerView nicht mit ausreichender Kraft scrolle. Mit anderen Worten, wenn ich die RecyclerView mit ausreichender Kraft scrolle, wird die AppBar vom Bildschirm gescrollt, ohne dass die RecyclerView intern gescrollt wird. Wenn ich die RecyclerView jedoch nicht mit ausreichender Kraft scrolle, scrollt die RecyclerView intern, bevor das AppbarLayout vom Bildschirm gescrollt hat. Wissen Sie, was das verursacht?
Micah Simmons

Die Recycling-Ansicht empfängt weiterhin Berührungsereignisse, daher wird immer noch gescrollt. Das Verhalten von NestedFling würde dazu anregen, gleichzeitig durch das AppbarLayout zu scrollen. Vielleicht können Sie versuchen, onInterceptTouch im Verhalten zu überschreiben, um dies zu ändern. Für mich ist das aktuelle Verhalten von dem, was ich sehe, akzeptabel. (nicht sicher, ob wir das gleiche sehen)
Mak Sing

@MakSing ist sehr hilfreich bei CoordinatorLayoutund ViewPagerSetup, vielen Dank für diese am meisten erwartete Lösung. Bitte schreiben Sie ein GIST dafür, damit auch andere Entwickler davon profitieren können. Ich teile diese Lösung auch. Danke noch einmal.
Nitin Misra

1
@MakSing Bei allen Lösungen funktioniert dies am besten für mich. Ich habe die Geschwindigkeit, die dem onNestedFling übergeben wurde, ein wenig angepasst. * 0.6f ... scheint ihm einen schöneren Fluss zu geben.
Saberrider

Funktioniert bei mir. @MakSing Müssen Sie in der onScrolled-Methode onNestedFling von AppBarLayout.Behavior und nicht von RecyclerViewAppBarBehavior aufrufen? Kommt mir etwas komisch vor.
Anton Malmygin

13

Es wurde seit dem Support-Design 26.0.0 behoben.

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

2
Dies muss nach oben gehen. Dies wird hier beschrieben , falls sich jemand für die Details interessiert.
Chris Dinon

1
Jetzt scheint es ein Problem mit der Statusleiste zu geben, bei dem beim Scrollen nach unten die Statusleiste mit dem Scrollen etwas nach unten geht ... es ist super ärgerlich!
Box

2
@ Xiaozou Ich benutze 26.1.0 und habe immer noch Probleme mit dem Schleudern. Schnelles Schleudern führt manchmal zu einer entgegengesetzten Bewegung (Die Geschwindigkeit der Bewegung ist entgegengesetzt / falsch, wie bei der onNestedFling-Methode zu sehen ist). Reproduzierte es in Xiaomi Redmi Note 3 und Galaxy S3
dor506

@ dor506 stackoverflow.com/a/47298312/782870 Ich bin mir nicht sicher, ob wir das gleiche Problem haben, wenn Sie das Ergebnis einer entgegengesetzten Bewegung sagen. Aber ich habe hier eine Antwort gepostet. Hoffe es hilft :)
Vida



2

Dies ist mein Layout und die Schriftrolle. Es funktioniert so, wie es sollte.

<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

Meine bisherige Lösung basiert auf den Antworten von Mak Sing und Manolo Garcia .

Es ist nicht ganz perfekt. Im Moment weiß ich nicht, wie ich eine Validgeschwindigkeit neu berechnen soll, um einen seltsamen Effekt zu vermeiden: Die App-Leiste kann schneller als die Bildlaufgeschwindigkeit erweitert werden. Der Status mit einer erweiterten App-Leiste und einer gescrollten Recycler-Ansicht kann jedoch nicht erreicht werden.

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

    }

}

Sie können die aktuelle Geschwindigkeit eines recyclerView (Stand 25.1.0) mithilfe von Reflection ermitteln: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

In meinem Fall bekam ich das Problem, wo das Schleudern der RecyclerView nicht reibungslos und es stecken blieb.

Das lag daran, dass ichRecyclerViewNestedScrollView aus irgendeinem Grund vergessen hatte, dass ich meine in eine .

Es ist ein dummer Fehler, aber ich habe eine Weile gebraucht, um es herauszufinden ...


1

Ich füge eine Ansicht mit einer Höhe von 1 dp im AppBarLayout hinzu und dann funktioniert es viel besser. Das ist mein Layout.

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


Es funktioniert nur, wenn Sie nach oben scrollen. Nicht, wenn Sie nach unten scrollen
Arthur

Für mich funktioniert gut in beide Richtungen. Haben Sie die 1dp-Ansicht im Appbarlayout hinzugefügt?. Ich habe es nur in Android Lollipop und Kitkat getestet.
Jachumbelechao bis Mantekilla

Nun, ich verwende auch CollapsingToolbarLayout, das die Symbolleiste umschließt. Ich habe die 1dp-Ansicht darin eingefügt. Es ist ein bisschen wie dieses AppBarLayout -> CollapsingToolbarLayout -> Symbolleiste + 1dp Ansicht
Arthur

Ich weiß nicht, ob es mit dem CollapsingToolbarLayout gut funktioniert. Ich habe nur mit diesem Code getestet. Haben Sie versucht, die 1dp-Ansicht außerhalb des CollapsingToolbarLayout zu platzieren?
Jachumbelechao bis Mantekilla

Ja. Scrollen nach oben funktioniert, Scrollen nach unten erweitert die Symbolleiste nicht.
Arthur

1

Hier gibt es bereits einige ziemlich beliebte Lösungen, aber nachdem ich mit ihnen gespielt habe, habe ich eine einfachere Lösung gefunden, die für mich gut funktioniert hat. Meine Lösung stellt auch sicher, dass die AppBarLayoutnur erweitert wird, wenn der scrollbare Inhalt die Spitze erreicht, ein Vorteil gegenüber anderen Lösungen hier.

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

Was ist mPrevDy
ARR.s

1

Die akzeptierte Antwort funktionierte nicht für mich, weil ich RecyclerViewin a SwipeRefreshLayoutund a hatte ViewPager. Dies ist die verbesserte Version, die eine RecyclerViewin der Hierarchie sucht und für jedes Layout funktionieren sollte:

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

Antwort: Es ist in der Support-Bibliothek v26 behoben

aber v26 hat ein Problem beim Schleudern. Manchmal springt AppBar wieder zurück, auch wenn das Schleudern nicht zu schwer ist.

Wie entferne ich den Bouncing-Effekt in der Appbar?

Wenn beim Aktualisieren auf Support 26 das gleiche Problem auftritt, finden Sie hier eine Zusammenfassung dieser Antwort .

Lösung : Erweitern Sie das Standardverhalten von AppBar und blockieren Sie den Aufruf von AppNar.Behavior's onNestedPreScroll () und onNestedScroll (), wenn AppBar berührt wird, während NestedScroll noch nicht gestoppt wurde.


0

Julian Os hat recht.

Die Antwort von Manolo Garcia funktioniert nicht, wenn die Recycling-Ansicht unter dem Schwellenwert liegt und gescrollt wird. Sie müssen die Position offsetdes Recyclerview und die velocity to the distancePosition des Artikels vergleichen.

Ich habe eine Java-Version erstellt, indem ich mich auf Julians Kotlin-Code bezogen und die Reflexion subtrahiert habe.

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

}

kann nicht resloveBaseApplication
ARR.s

@ ARR.s sorry, du ersetzst einfach deinen Kontext wie unten.
13.

YOUR_CONTEXT.getResources (). GetDisplayMetrics (). Density * 160.0f;
13.


0

In Bezug auf den Google Issue Tracker wurde dies mit der Android 26.0.0-Beta2-Version der Support-Bibliothek behoben

Bitte aktualisieren Sie Ihre Android-Support-Bibliothek Version 26.0.0-beta2.

Wenn ein Problem weiterhin besteht, melden Sie es bitte im Google Issue Tracker , der zur Überprüfung erneut geöffnet wird.


0

Das Hinzufügen einer weiteren Antwort hier, da die oben genannten entweder meine Anforderungen nicht vollständig erfüllten oder nicht sehr gut funktionierten. Dieser basiert teilweise auf Ideen, die hier verbreitet werden.

Also, was macht dieser?

Szenario nach unten schleudern: Wenn das AppBarLayout reduziert ist, kann das RecyclerView von selbst geschleudert werden, ohne etwas zu tun. Andernfalls wird das AppBarLayout reduziert und verhindert, dass RecyclerView seinen Fling ausführt. Sobald es reduziert ist (bis zu dem Punkt, den die angegebene Geschwindigkeit erfordert) und wenn noch Geschwindigkeit übrig ist, wird das RecyclerView mit der ursprünglichen Geschwindigkeit abzüglich dessen, was das AppBarLayout gerade verbraucht hat, geschleudert.

Szenario nach oben schleudern: Wenn der Bildlaufversatz des RecyclerView nicht Null ist, wird er mit der ursprünglichen Geschwindigkeit geschleudert. Sobald dies abgeschlossen ist und noch Geschwindigkeit vorhanden ist (dh die RecyclerView wird auf Position 0 gescrollt), wird das AppBarLayout bis zu dem Punkt erweitert, an dem die ursprüngliche Geschwindigkeit abzüglich der gerade verbrauchten Anforderungen. Andernfalls wird das AppBarLayout bis zu dem Punkt erweitert, den die ursprüngliche Geschwindigkeit erfordert.

AFAIK, das ist das indended Verhalten.

Es gibt viel Nachdenken und es ist ziemlich üblich. Es wurden jedoch noch keine Probleme gefunden. Es ist auch in Kotlin geschrieben, aber es sollte kein Problem sein, es zu verstehen. Sie können das IntelliJ Kotlin-Plugin verwenden, um es zu Bytecode zu kompilieren -> und es zurück zu Java zu dekompilieren. Um es zu verwenden, platzieren Sie es im android.support.v7.widget-Paket und legen Sie es als CoordinatorLayout.LayoutParams-Verhalten von AppBarLayout im Code fest (oder fügen Sie den entsprechenden XML-Konstruktor oder etwas hinzu).

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

}

Wie stelle ich es ein?
ARR.s

0

Das ist meine Lösung in meinem Projekt.
Stoppen Sie einfach den mScroller, wenn Sie Action_Down erhalten

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

für androidx,

Wenn Ihre Manifestdatei eine Zeile für android: hardwareAccelerated = "false" enthält, löschen Sie diese.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.