wake-up-neo.com

Flinging mit RecyclerView + AppBarLayout

Ich benutze das neue CoordinatorLayout mit AppBarLayout und CollapsingToolbarLayout. Unter AppBarLayout habe ich eine RecyclerView mit einer Liste von Inhalten.

Ich habe bestätigt, dass Flingscrolling auf RecyclerView funktioniert, wenn ich in der Liste nach oben und unten scrolle. Ich möchte jedoch auch, dass AppBarLayout während der Erweiterung reibungslos scrollt.

Wenn Sie nach oben scrollen, um das CollaspingToolbarLayout zu erweitern, stoppt das Scrollen sofort, sobald Sie den Finger vom Bildschirm nehmen. Wenn Sie in einer schnellen Bewegung nach oben scrollen, wird CollapsingToolbarLayout manchmal auch wieder reduziert. Dieses Verhalten mit der RecyclerView scheint anders zu funktionieren als bei Verwendung einer NestedScrollView.

Ich habe versucht, verschiedene Scroll-Eigenschaften im Recycling-Bereich festzulegen, konnte dies jedoch nicht herausfinden.

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

Hier ist ein Beispiel, das das Problem mit der 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

169
tylerjroach

Die Antwort von Kirill Boyarshinov war fast richtig. 

Das Hauptproblem ist, dass die RecyclerView manchmal eine falsche Fling-Richtung gibt. Wenn Sie der Antwort den folgenden Code hinzufügen, funktioniert sie richtig:

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, dass das hilft.

113
Manolo Garcia

Es scheint, dass v23 update es noch nicht behoben hat.

Ich habe eine Art Hack gefunden, um es mit einem Abwurf zu beheben. Der Trick besteht darin, das Fling-Ereignis erneut zu starten, wenn sich das oberste untergeordnete Element von ScrollingView nahe am Anfang der Daten 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 so:

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

EDIT: Die Wiederaufnahme des Fling-Ereignisses basiert jetzt auf verticalScrollOffset anstelle der Anzahl der Elemente auf RecyclerView.

EDIT2: Ziel als ScrollingView Schnittstelleninstanz prüfen statt RecyclerView. Sowohl RecyclerView als auch NestedScrollingView implementieren es.

69

Ich habe das Update gefunden, indem ich OnScrollingListener auf die RecyclerView angewendet habe. jetzt funktioniert es sehr gut. Das Problem ist, dass der Recycling-Bericht den falschen Verbrauchswert angegeben hat und das Verhalten nicht weiß, wann das Recycling-Fenster nach oben gerollt 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);
            }
        }
    }
}
15
Mak Sing

Es wurde seit dem Supportdesign 26.0.0 behoben. 

compile 'com.Android.support:design:26.0.0'
13
Xiaozou

Dies ist eine reibungslose Version von Google Support Design AppBarLayout. Wenn Sie AppBarLayout verwenden, wissen Sie, dass es ein Problem mit Fling gibt.

compile "me.henrytao:smooth-app-bar-layout:<latest-version>"

Siehe Bibliothek hier .. https://github.com/henrytao-me/smooth-app-bar-layout

5
Mansukh Ahir

Es ist ein Fehler im Recycler. Es soll in Version 23.1.0 behoben sein.

look https://code.google.com/p/Android/issues/detail?id=177729

 enter image description here

4
dupengtao

Dies ist mein Layout und die Schriftrolle. Es funktioniert wie es soll.

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

In meinem Fall bekam ich das Problem, dass das Schleudern der RecyclerView es nicht reibungslos scrollen würde, sodass es feststeckte.

Dies war, weil aus irgendeinem Grund ich hatte vergessen, dass ich meine RecyclerView in eine NestedScrollView gegeben hatte.

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

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

Es ist nicht vollkommen perfekt. Im Moment weiß ich nicht, wie ich eine Validengeschwindigkeit neu berechnen kann, um einen seltsamen Effekt zu vermeiden: Die Appbar kann sich schneller als die Bildlaufgeschwindigkeit ausdehnen. Der Status mit einer erweiterten Appbar 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;
        }

    }

}
2
Pierre

Answer: In Support Library V26 ist ein Problem behoben

v26 hat jedoch ein problem beim schleudern. AppBar springt manchmal wieder zurück, auch wenn das Fling nicht zu hart ist. 

Wie entferne ich den Bouncing-Effekt in Appbar?

Wenn dasselbe Problem auftritt, wenn Sie ein Update auf die Unterstützung von v26 durchführen, finden Sie hier die Zusammenfassung dieser Antwort

Solution: Erweitern Sie das Standardverhalten von AppBar und blockieren Sie den Aufruf für AppBar.Behavior's onNestedPreScroll () und onNestedScroll (), wenn AppBar wird berührt, während NestedScroll noch nicht aufgehört hat.

1
vida

Die akzeptierte Antwort funktionierte nicht für mich, da ich RecyclerView in einer SwipeRefreshLayout und einer ViewPager hatte. Dies ist die verbesserte Version, die eine RecyclerView in 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
Dmide

Hier sind schon einige ziemlich beliebte Lösungen, aber nachdem ich mit ihnen gespielt habe, habe ich eine etwas einfachere Lösung gefunden, die für mich gut funktionierte. Meine Lösung stellt auch sicher, dass die AppBarLayout nur erweitert wird, wenn der scrollbare Inhalt die Spitze erreicht. Dies ist ein Vorteil gegenüber anderen Lösungen.

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

Ich füge eine Ansicht von 1dp Höhe im AppBarLayout hinzu und funktioniert dann 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" />

Julian Os ist richtig.

Manolo Garcias Antwort funktioniert nicht, wenn die Recyclingübersicht unter dem Schwellenwert liegt und scrollt. Sie müssen die offset des Recyclingblatts und den velocity to the distance vergleichen, nicht die Artikelposition.

Ich habe Java-Version erstellt, indem ich mich auf julians Kotlin-Code bezog und die Reflexion subtrahiere.

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

}
0
정성민

Ich habe das Update von Eniz Bilgin _ ​​ https://stackoverflow.com/a/45090239/7639018 gefunden. _

Das Problem wurde mit den Bibliotheken in diesem Repository behoben.

( https://developer.Android.com/topic/libraries/support-library/setup.html )

allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    }
}
0
ARR.s

Eine weitere Antwort hier hinzuzufügen, da die oben genannten meine Anforderungen nicht vollständig erfüllt haben oder nicht gut funktionierten. Dieses basiert zum Teil auf den hier verbreiteten Ideen. 

Also, was macht dieser?

Abwärtsszenario des Szenarios: Wenn das AppBarLayout reduziert ist, kann RecyclerView von sich aus gestartet werden, ohne etwas zu tun. Andernfalls wird das AppBarLayout ausgeblendet und der RecyclerView wird nicht ausgeführt. Sobald es zusammengebrochen ist (bis zu dem Punkt, an dem die vorgegebene Geschwindigkeit erforderlich ist), und wenn noch Geschwindigkeit vorhanden ist, wird RecyclerView mit der ursprünglichen Geschwindigkeit abzüglich des Werts geworfen, den das AppBarLayout gerade verbraucht hat.

Szenario nach oben: Wenn der Scroll-Offset der RecyclerView nicht Null ist, wird er mit der Originalgeschwindigkeit geworfen. Sobald dies beendet ist und noch Geschwindigkeit vorhanden ist (dh die RecyclerView wurde auf Position 0 gescrollt), wird das AppBarLayout bis zu dem Punkt erweitert, an dem die ursprüngliche Geschwindigkeit abzüglich der gerade verbrauchten Anforderungen erforderlich ist. __ Ansonsten wird das AppBarLayout erweitert bis zu dem Punkt, den die ursprüngliche Geschwindigkeit erfordert.

AFAIK, das ist das eigensinnige Verhalten.

Es gibt viele Überlegungen, und es ist ziemlich üblich. Es wurden jedoch noch keine Probleme gefunden . Es ist auch in Kotlin geschrieben, aber das Verständnis sollte kein Problem sein ..__ Sie können das IntelliJ Kotlin-Plugin verwenden, um es zu Bytecode -> zu kompilieren und es zurück nach Java zu dekompilieren. Um es zu verwenden, platzieren Sie es im Paket Android.support.v7.widget und legen Sie es als CoordinatorLayout.LayoutParams-Verhalten von AppBarLayout im Code fest (oder fügen Sie den anwendbaren XML-Konstruktor oder etwas anderes 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) {
                                    [email protected](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
    }

}
0
Julian Os

In Bezug auf Google Problem-Tracker wurde es mit der Android 26.0.0-beta2-Version der Support-Bibliothek behoben

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

Wenn ein Problem weiterhin besteht, melden Sie sich unter Google Problem-Tracker es wird erneut geöffnet, um es zu überprüfen.

0
Prags

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
shaopx