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
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.
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.
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);
}
}
}
}
Il a été corrigé depuis la conception du support 26.0.0.
compile 'com.Android.support:design:26.0.0'
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
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
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>
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 ...
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;
}
}
}
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é.
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;
}
}
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;
});
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));
}
}
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"
}
}
}
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
}
}
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.
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