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
¿Es este un problema conocido? ¿Por que sucede?
¿Hay una manera de superar esto?
¿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>
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);
}