web-dev-qa-db-fra.com

Lancer avec RecyclerView + AppBarLayout

J'utilise le nouveau CoordinatorLayout avec AppBarLayout et CollapsingToolbarLayout. Sous AppBarLayout, j'ai un RecyclerView avec une liste de contenu.

J'ai vérifié que le défilement par fling fonctionnait sur le RecyclerView lorsque je parcourais la liste. Cependant, je voudrais aussi que AppBarLayout défile en douceur pendant l’extension.

Lors du défilement vers le haut pour développer CollaspingToolbarLayout, le défilement cesse immédiatement une fois que vous avez levé votre doigt de l'écran. Si vous faites rapidement défiler vers le haut, parfois CollapsingToolbarLayout se rétracte également. Ce comportement avec RecyclerView semble fonctionner très différemment que lors de l'utilisation d'un NestedScrollView.

J'ai essayé de définir différentes propriétés de défilement sur la vue de recyclage, mais je n'ai pas été en mesure de le comprendre.

Voici une vidéo montrant certains des problèmes de défilement . https://youtu.be/xMLKoJOsTAM

Voici un exemple illustrant le problème rencontré avec RecyclerView (CheeseDetailActivity) . https://github.com/tylerjroach/cheesesquare

Voici l'exemple original qui utilise un NestedScrollView de Chris Banes . https://github.com/chrisbanes/cheesesquare

169
tylerjroach

La réponse de Kirill Boyarshinov était presque correcte. 

Le problème principal est que RecyclerView donne parfois une direction de projection incorrecte. Par conséquent, si vous ajoutez le code suivant à sa réponse, il fonctionne correctement:

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

J'espère que ca aide.

113
Manolo Garcia

Il semble que la mise à jour v23 ne l’ait pas encore corrigée.

J'ai trouvé une sorte de bidouille pour résoudre ce problème en se jetant à terre. L'astuce consiste à reprendre l'événement fling si le premier enfant de ScrollingView est proche du début des données dans Adapter.

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

Utilisez-le dans votre mise en page comme ça:

 <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: La reconsommation d’événement Fling est maintenant basée sur verticalScrollOffset au lieu du nombre d’éléments du haut de RecyclerView.

EDIT2: Vérifier la cible comme étant une instance d'interface ScrollingView au lieu de RecyclerView. RecyclerView et NestedScrollingView l'implémentent.

69
Kirill Boyarshinov

J'ai trouvé le correctif en appliquant OnScrollingListener à recyclerView. maintenant cela fonctionne très bien. Le problème est que recyclerview a fourni la mauvaise valeur consommée et que le comportement ne sait pas quand défiler vers le haut.

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

Il a été corrigé depuis la conception du support 26.0.0. 

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

Ceci est une version fluide de Google Support Design AppBarLayout. Si vous utilisez AppBarLayout, vous saurez qu'il y a un problème avec fling.

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

Voir Bibliothèque ici .. https://github.com/henrytao-me/smooth-app-bar-layout

5
Mansukh Ahir

C'est un bug de recyclerview. C'est censé être corrigé dans v23.1.0.

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

 enter image description here

4
dupengtao

Ceci est ma mise en page et le parchemin Il fonctionne comme il se doit.

<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

Dans mon cas, je pensais que lancer le RecyclerView ne le ferait pas défiler sans heurts, ce qui le coincerait.

C’est parce que, pour une raison quelconque, j’avais oublié d’avoir mis ma RecyclerView dans une NestedScrollView.

C'est une erreur stupide, mais il m'a fallu un certain temps pour le comprendre ...

2

Ma solution à ce jour, basée sur Mak Sing et Manolo Garcia répond.

Ce n'est pas totalement parfait. Pour le moment, je ne sais pas comment recalculer une vitesse valide pour éviter un effet étrange: la barre d’application peut s’étendre plus rapidement que la vitesse de défilement. Mais l’état avec une barre d’application développée et une vue de recycleur défilant ne peut pas être atteint.

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: C'est corrigé dans la bibliothèque de support v26

mais v26 a quelques problèmes à lancer. Parfois, AppBar rebondit même si le lancer n’est pas trop difficile. 

Comment supprimer l'effet de rebond sur la barre d'outils?

Si vous rencontrez le même problème lors de la mise à jour pour prendre en charge la version 26, voici le résumé de cette answer

Solution: Étend le comportement par défaut de AppBar et bloque l'appel pour OnNestedPreScroll () et onNestedScroll () lorsque AppBar D'AppBar.Behavior est touché alors que NestedScroll n'est pas encore arrêté.

1
vida

La réponse acceptée ne fonctionnait pas pour moi car j’avais RecyclerView dans une SwipeRefreshLayout et une ViewPager. Ceci est la version améliorée qui cherche une RecyclerView dans la hiérarchie et devrait fonctionner pour n'importe quelle mise en page:

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

Déjà quelques solutions très populaires ici, mais après avoir joué avec elles, je suis parvenu à une solution un peu plus simple qui a bien fonctionné pour moi. Ma solution garantit également que la variable AppBarLayout n'est étendue que lorsque le contenu défilant atteint le sommet, un avantage par rapport aux autres solutions proposées ici.

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

J'ajoute une vue de la hauteur de 1dp à l'intérieur de l'AppBarLayout puis cela fonctionne beaucoup mieux. Ceci est ma mise en page.

  <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 a raison.

La réponse de Manolo Garcia ne fonctionne pas si la vue recyclée est inférieure au seuil et défile. Vous devez comparer la offset de la vue recyclée et le velocity to the distance, pas la position de l'article.

J'ai créé la version Java en me référant au code kotlin de julian et en soustrayant la réflexion.

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
정성민

J'ai trouvé le correctif de Eniz Bilginhttps://stackoverflow.com/a/45090239/7639018

Le problème a été résolu avec les bibliothèques de ce référentiel.

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

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

Ajouter une autre réponse ici car les réponses ci-dessus ne répondaient pas complètement à mes besoins ou ne fonctionnaient pas très bien. Celui-ci est partiellement basé sur des idées répandues ici. 

Alors, que fait celui-ci?

Scénario vers le bas: Si AppBarLayout est réduit, il permet à RecyclerView de se lancer sans rien faire. Sinon, il réduit l'AppBarLayout et empêche le RecyclerView de se lancer. Dès qu'il est réduit (jusqu'à ce que la vitesse requise l'exige) et s'il reste de la vitesse, le RecyclerView est projeté avec la vitesse d'origine moins ce que l'AppBarLayout a consommé lors de son effondrement.

Scénario vers le haut: Si le décalage de défilement de RecyclerView n'est pas à zéro, il est lancé avec la vélocité d'origine. Dès que cela est terminé et s'il reste encore de la vélocité (c'est-à-dire que RecyclerView a défilé jusqu'à la position 0), AppBarLayout est développé jusqu'à ce que la vélocité d'origine moins la demande consommée juste . Sinon, AppBarLayout est développé jusqu'à ce que la vitesse d'origine exige.

Autant que je sache, c'est le comportement indended.

Il y a beaucoup de réflexion impliquée, et c'est assez coutume. Aucun problème trouvé pour le moment . Il est également écrit en Kotlin, mais comprendre ne devrait pas poser de problème . Vous pouvez utiliser le plugin IntelliJ Kotlin pour le compiler en bytecode -> et le décompiler à nouveau en Java .Pour l'utiliser, placez-le dans le package Android.support.v7.widget et définissez-le comme comportement du code Coordinator d'AppBarLayout de CoordinatorLayout.LayoutParams (ou ajoutez le constructeur xml applicable ou quelque chose du genre).

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

En référence à suivi des problèmes de Google , il a été corrigé avec la version Android 26.0.0-beta2 de la bibliothèque de support.

Veuillez mettre à jour votre version de la bibliothèque de support Android 26.0.0-beta2.

Si un problème persiste, signalez-le à l'adresse { suivi des problèmes de Google }. Ils seront rouverts pour examen.

0
Prags

c'est ma solution dans mon projet.
arrête juste le mScroller quand obtenir Action_Down 

xml:

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/smooth_app_bar_layout"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:background="@color/white"
        app:elevation="0dp"
        app:layout_behavior="com.sogou.groupwenwen.view.topic.FixAppBarLayoutBehavior">

FixAppBarLayoutBehavior.Java: 

    public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
        if (ev.getAction() == ACTION_DOWN) {
            Object scroller = getSuperSuperField(this, "mScroller");
            if (scroller != null && scroller instanceof OverScroller) {
                OverScroller overScroller = (OverScroller) scroller;
                overScroller.abortAnimation();
            }
        }

        return super.onInterceptTouchEvent(parent, child, ev);
    }

    private Object getSuperSuperField(Object paramClass, String paramString) {
        Field field = null;
        Object object = null;
        try {
            field = paramClass.getClass().getSuperclass().getSuperclass().getDeclaredField(paramString);
            field.setAccessible(true);
            object = field.get(paramClass);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return object;
    }

//or check the raw file:
//https://github.com/shaopx/CoordinatorLayoutExample/blob/master/app/src/main/Java/com/spx/coordinatorlayoutexample/util/FixAppBarLayoutBehavior.Java
0
shaopx