tools support notes library last change android android-layout android-design-library appcompat-v7-r22.2 android-appcompat

android - support - Lanzar con RecyclerView+AppBarLayout



com android support appcompat v7 23 (18)

Estoy usando el nuevo CoordinatorLayout con AppBarLayout y CollapsingToolbarLayout. Debajo de AppBarLayout, tengo un RecyclerView con una lista de contenido.

He verificado que el desplazamiento de volteo funciona en RecyclerView cuando me desplazo hacia arriba y hacia abajo en la lista. Sin embargo, también me gustaría que AppBarLayout se desplace suavemente durante la expansión.

Al desplazarse hacia arriba para expandir CollaspingToolbarLayout, el desplazamiento se detiene inmediatamente una vez que levanta el dedo de la pantalla. Si se desplaza hacia arriba en un movimiento rápido, a veces el CollapsingToolbarLayout vuelve a contraerse también. Este comportamiento con RecyclerView parece funcionar de manera muy diferente que cuando se usa un NestedScrollView.

Intenté establecer diferentes propiedades de desplazamiento en la vista del reciclador, pero no he podido resolver esto.

Aquí hay un video que muestra algunos de los problemas de desplazamiento. https://youtu.be/xMLKoJOsTAM

Aquí hay un ejemplo que muestra el problema con RecyclerView (CheeseDetailActivity). https://github.com/tylerjroach/cheesesquare

Aquí está el ejemplo original que usa un NestedScrollView de Chris Banes. https://github.com/chrisbanes/cheesesquare


Esta es mi solución en mi proyecto.
simplemente detenga el mScroller cuando obtenga 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


Agregar otra respuesta aquí ya que las anteriores no satisfacían completamente mis necesidades o no funcionaban muy bien. Este se basa parcialmente en ideas difundidas aquí.

Entonces, ¿qué hace este?

Escenario hacia abajo: si AppBarLayout está colapsado, permite que RecyclerView se ejecute por sí solo sin hacer nada. De lo contrario, colapsa AppBarLayout e impide que RecyclerView realice su lanzamiento. Tan pronto como se colapsa (hasta el punto que exige la velocidad dada) y si queda velocidad, el RecyclerView se arroja con la velocidad original menos lo que AppBarLayout acaba de consumir colapso.

Lanzamiento ascendente del escenario: si el desplazamiento de desplazamiento del RecyclerView no es cero, se lanza con la velocidad original. Tan pronto como termine eso y si aún queda velocidad (es decir, el RecyclerView se desplaza a la posición 0), el AppBarLayout se expande hasta el punto que la velocidad original menos las demandas recién consumidas. De lo contrario, AppBarLayout se expande hasta el punto que exige la velocidad original.

AFAIK, este es el comportamiento indended.

Hay mucha reflexión involucrada, y es bastante personalizada. Sin embargo, todavía no se encontraron problemas. También está escrito en Kotlin, pero entenderlo no debería ser un problema. Puede usar el complemento IntelliJ Kotlin para compilarlo en bytecode -> y descompilarlo de nuevo en Java. Para usarlo, colóquelo en el paquete android.support.v7.widget y configúrelo como el comportamiento de CoordinadorLayout.LayoutParams de AppBarLayout en el código (o agregue el constructor xml aplicable o algo así)

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


Agrego una vista de 1dp de altura dentro de AppBarLayout y luego funciona mucho mejor. Este es mi diseño.

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



En mi caso, estaba teniendo el problema de que el lanzamiento de RecyclerView no se desplazaba suavemente, por lo que se atascaba.

Esto se debió a que, por alguna razón, había olvidado que había puesto mi RecyclerView en un NestedScrollView .

Es un error tonto, pero me tomó un tiempo resolverlo ...



Esta es una versión fluida de Google Support Design AppBarLayout. Si está utilizando AppBarLayout, sabrá que tiene un problema con fling.

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

Ver Biblioteca aquí .. https://github.com/henrytao-me/smooth-app-bar-layout


Este es mi diseño y el desplazamiento está funcionando como debería.

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


He encontrado la solución aplicando OnScrollingListener al recyclerView. ahora funciona muy bien El problema es que la vista de reciclaje proporcionó el valor de consumo incorrecto y el comportamiento no sabe cuándo la vista de reciclaje se desplaza hacia arriba.

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



La respuesta aceptada no funcionó para mí porque tenía RecyclerView dentro de SwipeRefreshLayout y ViewPager . Esta es la versión mejorada que busca un RecyclerView en la jerarquía y debería funcionar para cualquier diseño:

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


La respuesta de Kirill Boyarshinov fue casi correcta.

El problema principal es que RecyclerView a veces da una dirección de lanzamiento incorrecta, por lo que si agrega el siguiente código a su respuesta, funciona correctamente:

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

Espero que esto ayude.


Mi solución hasta ahora, basada en las respuestas de Mak Sing y Manolo García .

No es totalmente perfecto. Por ahora no sé cómo volver a calcular una velocidad de valide para evitar un efecto extraño: la barra de aplicaciones puede expandirse más rápido que la velocidad de desplazamiento. Pero no se puede alcanzar el estado con una barra de aplicaciones expandida y una vista de reciclador desplazada.

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


Parece que la actualización v23 aún no lo solucionó.

He encontrado una especie de truco para solucionarlo lanzándome hacia abajo. El truco consiste en reanudar el evento de lanzamiento si el hijo superior de ScrollingView está cerca del comienzo de los datos en el Adaptador.

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

Úselo en su diseño así:

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

EDITAR: La reanudación de eventos de descarga ahora se basa en verticalScrollOffset lugar de la cantidad de elementos desde la parte superior de RecyclerView .

EDIT2: Verifique el destino como instancia de interfaz ScrollingView en lugar de RecyclerView . Tanto RecyclerView como NestedScrollingView implementan.


Se ha solucionado desde el diseño de soporte 26.0.0.

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


Ya hay algunas soluciones bastante populares aquí, pero después de jugar con ellas, encontré una solución más simple que funcionó bien para mí. Mi solución también garantiza que AppBarLayout solo se expanda cuando el contenido desplazable llega a la cima, una ventaja sobre otras soluciones aquí.

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


Julian Os tiene razón.

La respuesta de Manolo García no funciona si la vista del reciclador está por debajo del umbral y se desplaza. Debe comparar el offset de la vista del reciclador y la velocity to the distance , no con la posición del artículo.

Hice la versión de Java al referirme al código de Kotlin de Julian y restar la reflexión.

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


Respuesta: se solucionó en la biblioteca de soporte v26

pero v26 tiene algún problema al lanzar. A veces, AppBar se recupera de nuevo incluso si el lanzamiento no es demasiado difícil.

¿Cómo elimino el efecto de rebote en la barra de aplicaciones?

Si encuentra el mismo problema al actualizar para admitir v26, aquí está el resumen de esta .com/a/47298312/782870 .

Solución : Extienda el Comportamiento predeterminado de AppBar y bloquee la llamada a AppBar.Behavior''s onNestedPreScroll () y onNestedScroll () cuando se toca AppBar mientras NestedScroll aún no se ha detenido.