android android-recyclerview android-collapsingtoolbarlayout

android - ¿Cómo evitar el bloqueo del desplazamiento en sí cuando se utiliza setNestedScrollingEnabled(false)?



android-recyclerview android-collapsingtoolbarlayout (8)

Como @Moinkhan señala, podría intentar envolver RecyclerView y los siguientes elementos en un NestedScrollView como este, esto debería resolver su problema de desplazamiento junto con el diseño de la barra de herramientas contraída:

<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:fitsSystemWindows="true" tools:context="com.example.user.myapplication.ScrollingActivity"> <android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="@dimen/app_bar_height" android:fitsSystemWindows="true" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" app:popupTheme="@style/AppTheme.PopupOverlay"/> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="fill_vertical" android:fillViewport="true" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/nestedView" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> </RelativeLayout> </android.support.v4.widget.NestedScrollView> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_anchor="@id/app_bar" app:layout_anchorGravity="bottom|end"> <Button android:id="@+id/disableNestedScrollingButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="disable"/> <Button android:id="@+id/enableNestedScrollingButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="enable" /> </LinearLayout> </android.support.design.widget.CoordinatorLayout>

En caso de que no se muestre el contenido de recyclerview, puede seguir este hilo para resolver ese problema ¿Cómo usar RecyclerView dentro de NestedScrollView? .

Espero eso ayude.

Fondo

Tenemos un diseño bastante complejo que tiene CollapsingToolbarLayout en él, junto con un RecyclerView en la parte inferior.

En ciertos casos, deshabilitamos temporalmente la expansión / contracción de CollapsingToolbarLayout, llamando a setNestedScrollingEnabled (boolean) en RecyclerView.

El problema

Esto generalmente funciona bien.

Sin embargo, en algunos casos (poco frecuentes), el desplazamiento lento en RecyclerView se vuelve semibloqueado, lo que significa que intenta retroceder cuando se desplaza hacia abajo. Es como si tuviera 2 desplazamientos que luchan entre sí (desplazarse hacia arriba y hacia abajo):

El código para desencadenar esto es como tal:

res / layout / activity_scrolling.xml

<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:fitsSystemWindows="true" tools:context="com.example.user.myapplication.ScrollingActivity"> <android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="@dimen/app_bar_height" android:fitsSystemWindows="true" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" app:popupTheme="@style/AppTheme.PopupOverlay"/> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView android:id="@+id/nestedView" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_anchor="@id/app_bar" app:layout_anchorGravity="bottom|end"> <Button android:id="@+id/disableNestedScrollingButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="disable"/> <Button android:id="@+id/enableNestedScrollingButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="enable" /> </LinearLayout> </android.support.design.widget.CoordinatorLayout>

ScrollingActivity.java

public class ScrollingActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_scrolling); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); final RecyclerView nestedView = (RecyclerView) findViewById(R.id.nestedView); findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() { @Override public void onClick(final View v) { nestedView.setNestedScrollingEnabled(false); } }); findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() { @Override public void onClick(final View v) { nestedView.setNestedScrollingEnabled(true); } }); nestedView.setLayoutManager(new LinearLayoutManager(this)); nestedView.setAdapter(new Adapter() { @Override public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate( android.R.layout.simple_list_item_1, parent, false)) { }; } @Override public void onBindViewHolder(final ViewHolder holder, final int position) { ((TextView) holder.itemView.findViewById(android.R.id.text1)).setText("item " + position); } @Override public int getItemCount() { return 100; } }); } }

Lo que he intentado

Al principio pensé que se debía a otra cosa (pensé que era una combinación extraña con DrawerLayout), pero luego encontré una muestra mínima para mostrarla, y es justo como pensaba: todo se debe a que setNestedScrollingEnabled.

Traté de informar sobre esto en el sitio web de Google ( here ), esperando que se solucione si es un error real. Si desea probarlo o ver los videos del problema, vaya allí, ya que no puedo cargarlos todos aquí (demasiado grandes y demasiados archivos).

También he intentado usar banderas especiales como se indica en otras publicaciones (ejemplos: here , here , here , here y here ), pero ninguna ayudó. De hecho, cada uno de ellos tuvo un problema, ya sea si se mantiene en modo expandido o si se desplaza de forma diferente a como lo hago yo.

Las preguntas

  1. ¿Es este un problema conocido? ¿Por que sucede?

  2. ¿Hay una manera de superar esto?

  3. ¿Hay tal vez una alternativa a llamar a esta función de setNestedScrollingEnabled? ¿Uno sin problemas de desplazamiento o bloqueo del estado de CollapsingToolbarLayout?


Creo que este problema está relacionado con el hecho de que la barra de herramientas colapsada se coloque en su lugar (ya sea cerrada o abierta) y deje una variable de compensación vertical ( mScrollOffset[1] en RecyclerView ) con un valor distinto de cero que posteriormente sesga el desplazamiento - desacelera o invierte el Desplázate en una dirección y acelera en la otra. Esta variable solo parece estar configurada en NestedScrollingChildHelper si el desplazamiento anidado está habilitado. Por lo tanto, cualquier valor que mScrollOffset[1] se mScrollOffset[1] una vez que se deshabilita el desplazamiento del nido.

Para reproducir de manera confiable este problema, puede hacer que la barra de herramientas se ajuste en su lugar y luego haga clic inmediatamente en deshabilitar. Vea este video para una demostración. Creo que la magnitud del problema varía según la cantidad de "ajustes" que se produzcan.

Si arrastro la barra de herramientas a la posición completamente abierta o cerrada y no dejo que se "ajuste", entonces no he podido reproducir este problema y mScrollOffset[1] se establece en cero, lo que creo que es el valor correcto. También he reproducido el problema eliminando snap de layout_scrollFlags de la barra de herramientas que se layout_scrollFlags en el diseño y colocando la barra de herramientas en un estado parcialmente abierto.

Si quieres jugar con esto, puedes poner tu aplicación de demostración en modo de depuración y observar el valor de mScrollOffset[1] en RecyclerView#onTouchEvent . NestedScrollingChildHelper los métodos dispatchNestedScroll y dispatchNestedPreScroll NestedScrollingChildHelper para ver cómo se establece el desplazamiento solo cuando está habilitado el desplazamiento anidado.

Entonces, ¿cómo solucionar esto? mScrollOffset es privado a RecyclerView y no es inmediatamente obvio cómo mScrollOffset[1] subclases para cambiar el valor de mScrollOffset[1] . Eso dejaría la reflexión, pero eso puede no ser deseable para ti. Tal vez otro lector tenga una idea sobre cómo abordar esto o sepa de alguna salsa secreta. Repostaré si algo me ocurre.

Edición: He proporcionado una nueva clase ScrollingActivity.java que supera este problema. Utiliza la reflexión y aplica un parche para establecer mScrollOffset[1] de RecyclerView en cero cuando se presiona el botón de desplazamiento de inhabilitación y la barra de aplicaciones está inactiva. He hecho algunas pruebas preliminares y está funcionando. Aquí está la gist . (Ver la esencia actualizada a continuación.)

Segunda edición: pude hacer que la barra de herramientas se ajustara de forma divertida y se atascara en el medio sin el parche, por lo que no parece que el parche esté causando ese problema en particular. Puedo hacer que la barra de herramientas rebote de totalmente abierta a colapsada al desplazarme hacia abajo lo suficientemente rápido en la aplicación sin parchear.

También eché otro vistazo a lo que está haciendo el parche y creo que se comportará solo: la variable es privada y se hace referencia solo en un lugar después de que se desactiva el desplazamiento. Con el desplazamiento habilitado, la variable siempre se restablece antes del uso. La verdadera respuesta es que Google solucione este problema. Hasta que lo hagan, creo que esto puede ser lo más cerca que pueda llegar a un trabajo aceptable con este diseño en particular. (He publicado una información actualizada que aborda los problemas potenciales con un clic rápido que deja a los conmutadores en un estado potencialmente inadecuado).

En cualquier caso, el problema subyacente ha sido identificado y usted tiene una manera confiable de reproducir el problema, por lo que puede verificar más fácilmente otras soluciones propuestas.

Espero que esto ayude.


Dentro de la vista del reciclador, para desplazarse suavemente

android:nestedScrollingEnabled="false"

para superponer el cardView en la barra de herramientas

app:behavior_overlapTop = "24dp"

Pruebe este código para CollapsingToolbar:

<android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/background" android:fitsSystemWindows="true"> <android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="@dimen/app_bar_height" android:fitsSystemWindows="true" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" app:popupTheme="@style/AppTheme.PopupOverlay" app:title="Title" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:background="@android:color/transparent" app:behavior_overlapTop="@dimen/behavior_overlap_top" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:id="@+id/linearLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/text_min_padding" android:nestedScrollingEnabled="false" android:scrollbarSize="2dp" android:scrollbarStyle="outsideInset" android:scrollbarThumbVertical="@color/colorAccent" android:scrollbars="vertical" /> </LinearLayout> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout>

Screenshot


En realidad, podrías estar mirando el problema de la manera incorrecta.

Lo único que necesitas es establecer los indicadores de la Toolbar consecuencia. Realmente no hace nada más, así que diría que su diseño debería simplificarse a:

<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:fitsSystemWindows="true" tools:context="com.example.user.myapplication.ScrollingActivity"> <android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="@dimen/app_bar_height" android:fitsSystemWindows="true" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_scrollFlags="scroll|enterAlways" app:popupTheme="@style/AppTheme.PopupOverlay" app:title="Title" /> </android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView android:id="@+id/nestedView" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_anchor="@id/app_bar" app:layout_anchorGravity="bottom|end"> <Button android:id="@+id/disableNestedScrollingButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="disable"/> <Button android:id="@+id/enableNestedScrollingButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="enable" /> </LinearLayout> </android.support.design.widget.CoordinatorLayout>

Luego, cuando desee deshabilitar el colapso, simplemente configure los indicadores de la barra de herramientas:

// To disable collapsing AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams(); params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP); toolbar.setLayoutParams(params);

Y habilitar

// To enable collapsing AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams(); params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL|AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS); toolbar.setLayoutParams(params);

Mantenga una referencia a los parámetros de diseño si está cambiando en lugar de obtenerlo todo el tiempo.

Si necesita que CollapsingToolbarLayout obtenga y configure LayoutParams en esa View , actualice las LayoutParams de la misma manera pero ahora agregue appBarLayout.setExpanded(true/false)

Nota: el uso de setScrollFlags borra todas las setScrollFlags anteriores, así que tenga cuidado y establezca todas las banderas necesarias cuando use este método.


Este es un enfoque alternativo para lograr el mismo objetivo que esta respuesta . Si bien esa respuesta utilizó Reflexión, esta respuesta no, pero el razonamiento sigue siendo el mismo.

¿Por qué está pasando esto?

El problema es que RecyclerView veces usa un valor obsoleto para la variable miembro mScrollOffset . mScrollOffset se establece solo en dos lugares en RecyclerView : dispatchNestedPreScroll y dispatchNestedScroll . Solo nos preocupa el dispatchNestedPreScroll . RecyclerView#onTouchEvent invoca este método cuando maneja eventos MotionEvent.ACTION_MOVE .

Lo siguiente es de la documentación para dispatchNestedPreScroll .

dispatchNestedPreScroll

boolean dispatchNestedPreScroll (int dx, int dy, int [] consumido, int [] offsetInWindow)

Envíe un paso de un desplazamiento anidado en curso antes de que esta vista consuma cualquier parte de él.

Los eventos de desplazamiento previo anidados son eventos de desplazamiento anidados, lo que la intercepción táctil es tocar. dispatchNestedPreScroll ofrece una oportunidad para que la vista principal en una operación de desplazamiento anidada consuma parte o la totalidad de la operación de desplazamiento antes de que la vista secundaria la consuma.

...

offsetInWindow int: Opcional. Si no es nulo, en retorno contendrá el desplazamiento en las coordenadas de la vista local de esta vista desde antes de esta operación hasta después de que se complete. Las implementaciones de vista pueden usar esto para ajustar el seguimiento de coordenadas de entrada esperado.

offsetInWindow es en realidad un int[2] con el segundo índice que representa el cambio y que se aplicará a RecyclerView debido al desplazamiento anidado.

RecyclerView#DispatchNestedPrescroll resuelve en un método con el mismo nombre en NestedScrollingChildHelper .

Cuando RecyclerView llama a dispatchNestedPreScroll , mScrollOffset se usa como el argumento offsetInWindow . Por lo tanto, cualquier cambio realizado en offsetInWindow actualiza directamente mScrollOffset . dispatchNestedPreScroll actualiza mScrollOffset siempre que el desplazamiento anidado esté vigente . Si el desplazamiento anidado no está en efecto, entonces mScrollOffset no se actualiza y continúa con el valor que se estableció por última vez por dispatchNestedPreScroll . Por lo tanto, cuando el desplazamiento anidado se desactiva, el valor de mScrollOffset se mScrollOffset inmediatamente, pero RecyclerView continúa mScrollOffset .

El valor correcto de mScrollOffset[1] al regresar de dispatchNestedPreScroll es la cantidad a ajustar para input coordinate tracking (ver arriba). En RecyclerView las siguientes líneas ajustan la coordenada y toque:

mLastTouchY = y - mScrollOffset[1];

Si mScrollOffset[1] es, digamos, -30 (porque está obsoleto y debería ser cero), mLastTouchY estará desactivado en +30 píxeles (--30 = + 30). El efecto de este error de cálculo es que parecerá que el toque ocurrió más abajo en la pantalla de lo que realmente ocurrió. Por lo tanto, un desplazamiento lento hacia abajo realmente se desplazará hacia arriba y un desplazamiento hacia arriba se desplazará más rápido. (Si un desplazamiento hacia abajo es lo suficientemente rápido para superar esta barrera de 30px , entonces el desplazamiento hacia abajo ocurrirá pero más lentamente de lo que debería). El desplazamiento hacia arriba será demasiado rápido ya que la aplicación cree que se ha cubierto más espacio.

mScrollOffset continuará como una variable obsoleta hasta que se active el desplazamiento anidado y dispatchNestedPreScroll una vez más informa del valor correcto en mScrollOffset .

Enfoque

Dado que mScrollOffset[1] tiene un valor obsoleto en ciertas circunstancias, el objetivo es establecerlo en el valor correcto en esas circunstancias. Este valor debe ser cero cuando no se realiza el desplazamiento anidado, es decir, cuando la barra de aplicaciones se expande o contrae. Desafortunadamente, mScrollOffset es local para RecyclerView y no hay ningún configurador para ello. Para obtener acceso a mScrollOffset sin recurrir a Reflection, se crea un RecyclerView personalizado que anula dispatchNestedPreScroll . El cuarto instrumento es offsetInWindow que es la variable que necesitamos cambiar.

Se produce un mScrollOffset cuando se deshabilita el desplazamiento anidado para RecyclerView . Una condición adicional que impondremos es que la Barra de Aplicaciones debe estar inactiva para que podamos decir con seguridad que mScrollOffset[1] debe ser cero. Esto no es un problema ya que CollapsingToolbarLayout especifica snap en las banderas de desplazamiento.

En la aplicación de ejemplo, ScrollingActivity se ha modificado para grabar cuando la barra de aplicaciones se expande y se cierra. También se ha creado una devolución de llamada ( clampPrescrollOffsetListener ) que devolverá true cuando se true nuestras dos condiciones. Nuestro dispatchNestedPreScroll anulado invocará esta devolución de llamada y mScrollOffset[1] a cero en una respuesta true .

El archivo fuente actualizado para ScrollingActivity se presenta a continuación, al igual que el RecyclerView - MyRecyclerView personalizado RecyclerView - MyRecyclerView . El archivo de diseño XML debe cambiarse para reflejar el MyRecyclerView personalizado.

Desplazamiento de actividad

public class ScrollingActivity extends AppCompatActivity implements MyRecyclerView.OnClampPrescrollOffsetListener { private CollapsingToolbarLayout mCollapsingToolbarLayout; private AppBarLayout mAppBarLayout; private MyRecyclerView mNestedView; // This variable will be true when the app bar is completely open or completely collapsed. private boolean mAppBarIdle = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_scrolling); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); mNestedView = (MyRecyclerView) findViewById(R.id.nestedView); mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar); mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout); // Set the listener for the patch code. mNestedView.setOnClampPrescrollOffsetListener(this); // Listener to determine when the app bar is collapsed or fully open (idle). mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { @Override public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { mAppBarIdle = verticalOffset == 0 || verticalOffset <= appBarLayout.getTotalScrollRange(); } }); findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() { @Override public void onClick(final View v) { // If the AppBar is fully expanded or fully collapsed (idle), then disable // expansion and apply the patch; otherwise, set a flag to disable the expansion // and apply the patch when the AppBar is idle. setExpandEnabled(false); } }); findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() { @Override public void onClick(final View v) { setExpandEnabled(true); } }); mNestedView.setLayoutManager(new LinearLayoutManager(this)); mNestedView.setAdapter(new Adapter() { @Override public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate( android.R.layout.simple_list_item_1, parent, false)) { }; } @Override public void onBindViewHolder(final ViewHolder holder, final int position) { ((TextView) holder.itemView.findViewById(android.R.id.text1)).setText("item " + position); } @Override public int getItemCount() { return 100; } }); } private void setExpandEnabled(boolean enabled) { mNestedView.setNestedScrollingEnabled(enabled); } // Return "true" when the app bar is idle and nested scrolling is disabled. This is a signal // to the custom RecyclerView to clamp the y prescroll offset to zero. @Override public boolean clampPrescrollOffsetListener() { return mAppBarIdle && !mNestedView.isNestedScrollingEnabled(); } private static final String TAG = "ScrollingActivity"; }

MyRecyclerView

public class MyRecyclerView extends RecyclerView { private OnClampPrescrollOffsetListener mPatchListener; public MyRecyclerView(Context context) { super(context); } public MyRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); } public MyRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } // Just a call to super plus code to force offsetInWindow[1] to zero if the patchlistener // instructs it. @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { boolean returnValue; int currentOffset; returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); currentOffset = offsetInWindow[1]; Log.d(TAG, "<<<<dispatchNestedPreScroll: " + currentOffset); if (mPatchListener.clampPrescrollOffsetListener() && offsetInWindow[1] != 0) { Log.d(TAG, "<<<<dispatchNestedPreScroll: " + currentOffset + " -> 0"); offsetInWindow[1] = 0; } return returnValue; } public void setOnClampPrescrollOffsetListener(OnClampPrescrollOffsetListener patchListener) { mPatchListener = patchListener; } public interface OnClampPrescrollOffsetListener { boolean clampPrescrollOffsetListener(); } private static final String TAG = "MyRecyclerView"; }


Quiero presentar una buena alternativa, basada principalmente en la de here :

AppBarLayoutEx.kt

class AppBarLayoutEx : AppBarLayout { private var isAppBarExpanded = true private val behavior = AppBarLayoutBehavior() private var onStateChangedListener: (Boolean) -> Unit = {} var enableExpandAndCollapseByDraggingToolbar: Boolean get() = behavior.canDrag set(value) { behavior.canDrag = value } var enableExpandAndCollapseByDraggingContent: Boolean get() = behavior.acceptsNestedScroll set(value) { behavior.acceptsNestedScroll = value } constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet) : super(context, attrs) init { addOnOffsetChangedListener( AppBarLayout.OnOffsetChangedListener { _, verticalOffset -> isAppBarExpanded = verticalOffset == 0 onStateChangedListener(isAppBarExpanded) }) } override fun setLayoutParams(params: ViewGroup.LayoutParams?) { super.setLayoutParams(params) (params as CoordinatorLayout.LayoutParams).behavior = behavior } fun toggleExpandedState() { setExpanded(!isAppBarExpanded, true) } fun setOnExpandAndCollapseListener(onStateChangedListener: (Boolean) -> Unit) { this.onStateChangedListener = onStateChangedListener } private class AppBarLayoutBehavior : AppBarLayout.Behavior() { var canDrag = true var acceptsNestedScroll = true init { setDragCallback(object : AppBarLayout.Behavior.DragCallback() { override fun canDrag(appBarLayout: AppBarLayout) = canDrag }) } override fun onStartNestedScroll(parent: CoordinatorLayout, child: AppBarLayout, directTargetChild: View, target: View, nestedScrollAxes: Int, type: Int) = acceptsNestedScroll } }

Uso: además de usarlo en el archivo XML de diseño, puede deshabilitar / habilitar su expansión usando:

appBarLayout.enableExpandAndCollapseByDraggingToolbar = true/false appBarLayout.enableExpandAndCollapseByDraggingContent = true/false


Tuve que resolver un problema similar y lo hice usando un comportamiento personalizado en AppBarLayout . Todo funciona muy bien. Al anular onStartNestedScroll en el comportamiento personalizado, es posible bloquear o colapsar el diseño de la barra de herramientas para que no se expanda o se contraiga mientras se mantiene la vista de desplazamiento ( NestedScrollView ) en mi caso, funcionando como se esperaba. He explicado los detalles here , espero que ayude.

private class AppBarLayoutBehavior : AppBarLayout.Behavior() { var canDrag = true var acceptsNestedScroll = true init { setDragCallback(object : AppBarLayout.Behavior.DragCallback() { override fun canDrag(appBarLayout: AppBarLayout): Boolean { // Allow/Do not allow dragging down/up to expand/collapse the layout return canDrag } }) } override fun onStartNestedScroll(parent: CoordinatorLayout, child: AppBarLayout, directTargetChild: View, target: View, nestedScrollAxes: Int, type: Int): Boolean { // Refuse/Accept any nested scroll event return acceptsNestedScroll }}


Utilice el siguiente código, funciona bien para mí:

lockAppBarClosed(); ViewCompat.setNestedScrollingEnabled(recyclerView, false); // to lock the CollapsingToolbarLayout

e implementar los siguientes métodos:

private void setAppBarDragging(final boolean isEnabled) { CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams(); AppBarLayout.Behavior behavior = new AppBarLayout.Behavior(); behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() { @Override public boolean canDrag(AppBarLayout appBarLayout) { return isEnabled; } }); params.setBehavior(behavior); } public void unlockAppBarOpen() { appBarLayout.setExpanded(true, false); appBarLayout.setActivated(true); setAppBarDragging(false); } public void lockAppBarClosed() { appBarLayout.setExpanded(false, false); appBarLayout.setActivated(false); setAppBarDragging(false); }