Uso de BottomSheetBehavior con un CoordinatorLayout interno
La biblioteca de soporte de diseño v. 23.2
introdujo BottomSheetBehavior
, que permite que los niños de un coordinador actúen como hojas inferiores (vistas arrastrables desde la parte inferior de la pantalla).
Lo que me gustaría hacer es tener, como una vista inferior de la hoja , la siguiente vista (el coordinador típico + material colapsado):
- SearchView on support.v7.appcompat problema de la biblioteca: el fondo de 9 parches predeterminado no se procesa correctamente
- (Diseño de la Biblioteca de Soporte) CollapsingToolbarLayout - Barra de herramientas no se queda en el colapso
- Soporte para Android (cardView, RecyclerView) en versiones anteriores con kitkat de destino
- PercentRelativeLayout es más performante?
- AnimatedVectorDrawable en la biblioteca de soporte y animación de "pathData"
<CoordinatorLayout app:layout_behavior=“@string/bottom_sheet_behavior”> <AppBarLayout> <CollapsingToolbarLayout> <ImageView /> </CollapsingToolbarLayout> </AppBarLayout> <NestedScrollView> <LinearLayout> < Content ... /> </LinearLayout> </NestedScrollView> </CoordinatorLayout>
Desafortunadamente, las vistas de la parte inferior de la hoja deben implementar el desplazamiento anidado o no obtendrán eventos de desplazamiento. Si intenta con una actividad principal y luego carga esta vista como una hoja inferior, verá que los eventos de desplazamiento sólo actúan en la "hoja" de papel, con algún comportamiento extraño, como puede ver si sigue leyendo.
Estoy bastante seguro de que esto puede ser manejado por subclase CoordinatorLayout
, o incluso mejor por subclase BottomSheetBehavior
. ¿Tiene alguna pista?
Algunos pensamientos
-
requestDisallowInterceptTouchEvent()
debe utilizar, para robar eventos del padre en algunas condiciones:- Cuando el desplazamiento
AppBarLayout
es> 0 - Cuando el desplazamiento de
AppBarLayout
es == 0, pero estamos desplazando hacia arriba (piense en ello por un segundo y verá)
- Cuando el desplazamiento
-
La primera condición se puede obtener estableciendo un
OnOffsetChanged
en la barra de aplicación interna; -
El segundo requiere un manejo de eventos, por ejemplo:
switch (MotionEventCompat.getActionMasked(event)) { case MotionEvent.ACTION_DOWN: startY = event.getY(); lastY = startY; userIsScrollingUp = false; break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: userIsScrollingUp = false; break; case MotionEvent.ACTION_MOVE: lastY = event.getY(); float yDeltaTotal = startY - lastY; if (yDeltaTotal > touchSlop) { // Moving the finger up. userIsScrollingUp = true; } break; }
Cuestiones
Huelga decir que no puedo hacer que esto funcione ahora mismo. No soy capaz de captar los acontecimientos cuando se cumplen las condiciones, y no capturarlos en otros casos. En la imagen de abajo se puede ver lo que sucede con un CoordinatorLayout estándar:
-
La hoja se descarta si se desplaza hacia abajo en la barra de aplicaciones, pero no si se desplaza hacia abajo en el contenido anidado. Parece que los eventos de desplazamiento anidados no se propagan al comportamiento del Coordinador;
-
También existe un problema con la appbar interna: el contenido de desplazamiento anidado no sigue la barra de aplicaciones cuando se está contrayendo.
He configurado un proyecto de ejemplo en github que muestra estos problemas.
Sólo para ser claro, el comportamiento deseado es:
-
Comportamiento correcto de appbars / scroll views dentro de la hoja;
-
Cuando la hoja se expande, puede colapsar hacia abajo, pero sólo si la appbar interna está completamente expandida . Ahora mismo se derrumba sin tener en cuenta el estado de la appbar, y sólo si arrastra la appbar;
-
Cuando la hoja se contrae, los gestos de desplazamiento hacia arriba lo expandirán (sin efecto en la barra de herramientas interna).
Un ejemplo de la aplicación de contactos (que probablemente no utiliza BottomSheetBehavior, pero esto es lo que quiero):
- Las versiones resueltas para la aplicación (22.0.0) y la aplicación de prueba (21.0.3) difieren
- Android-support-v4.jar no se importa correctamente en Eclipse
- Java.lang.IllegalStateException: No se puede realizar esta acción después de onSaveInstanceState con DialogFragment
- Android N se bloquea en TextAppearanceSpan
- Cambiar altura de pagerTabStrip tabIndicator
- Cómo configurar el recuento de notificaciones no leídas en NavigationView de DrawerLayout?
- Fragmento con ViewPager dentro de Fragmento y FragmentStatePagerAdapter resultados en Excepción (con ejemplo completo)
- Android InstantiationException con fragmento (es público)
Acabo de seguir la forma en que le hizo la pregunta anterior y llegar a la solución que podría necesitar más explicación. Por favor, siga su código de ejemplo e integrar la parte extra en su xml para que se comporten como BottomSheeet comportamiento
<CoordinatorLayout> <AppBarLayout> <Toolbar app:layout_collapseMode="pin"> </Toolbar> </AppBarLayout> <NestedScrollView app:layout_behavior=“@string/bottom_sheet_behavior” > <include layout="@layout/items" /> </NestedScrollView> <!-- Bottom Sheet --> <BottomSheetCoordinatorLayout> <AppBarLayout <CollapsingToolbarLayout"> <ImageView /> <Toolbar /> </CollapsingToolbarLayout> </AppBarLayout> <NestedScrollView"> <include layout="@layout/items" /> </NestedScrollView> </BottomSheetCoordinatorLayout> </CoordinatorLayout>
Nota: Solución que funcionó para mí ya explicada en el último comentario de tu pregunta
Mejor explicación: https://github.com/laenger/BottomSheetCoordinatorLayout
Por fin he liberado mi implementación. Encuentra en Github o directamente desde jcenter:
compile 'com.otaliastudios:bottomsheetcoordinatorlayout:1.0.0'
Todo lo que tienes que hacer es utilizar BottomSheetCoordinatorLayout
como la vista raíz de tu hoja inferior. Inflará automáticamente un comportamiento de trabajo por sí mismo, así que no te preocupes por ello.
He estado usando esto durante mucho tiempo y no debe tener problemas de desplazamiento, apoya arrastrando en la ABL, etc
Si el primer niño es nestedscroll
esto ocurrirá algunos otros problemas. Esta solución se soluciona mi problema espero también arreglar el suyo.
<CoordinatorLayout app:layout_behavior=“@string/bottom_sheet_behavior”> <AppBarLayout> <CollapsingToolbarLayout> <ImageView /> </CollapsingToolbarLayout> </AppBarLayout> </LinearLayout> <NestedScrollView> <LinearLayout> < Content ... /> </LinearLayout> </NestedScrollView> </LinearLayout> </CoordinatorLayout>
Trate de no utilizar NestedScrollView
con LinearLayout
, sino que ha estado causando problemas en mi aplicación también. Sólo utilice sólo LinearLayout
en LinearLayout
lugar, funciona bien para mí.
Pruebe lo siguiente:
<CoordinatorLayout app:layout_behavior=“@string/bottom_sheet_behavior”> <AppBarLayout> <CollapsingToolbarLayout> <ImageView /> </CollapsingToolbarLayout> </AppBarLayout> <LinearLayout> <!--don't forget to addd this line--> app:layout_behavior="@string/appbar_scrolling_view_behavior"> < Content ... /> </LinearLayout>
He seguido el proyecto de prueba github inicial de laenger sobre este tema, y me alegro de compartirte una solución para algunos de sus problemas, ya que necesitaba este comportamiento en mi aplicación también.
Esta es una solución a su problema: ❌ barra de herramientas a veces colapsa demasiado pronto
Para evitar esto, debe crear su AppBarLayout.Behavior
personalizado, ya que es cuando se desplaza hacia arriba mientras arrastra que AppBarLayout.behavior
obtiene el movimiento de desplazamiento. Necesitamos detectar si está en STATE_DRAGGING y simplemente regresar para evitar ocultar / contraer la barra de herramientas prematuramente.
public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior { private CoordinatorLayoutBottomSheetBehavior behavior; public CustomAppBarLayoutBehavior() { } public CustomAppBarLayoutBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) { behavior = CoordinatorLayoutBottomSheetBehavior.from(parent); return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes); } @Override public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) { if(behavior.getState() == CoordinatorLayoutBottomSheetBehavior.STATE_DRAGGING){ return; }else { super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); } } @Override public void setDragCallback(@Nullable DragCallback callback) { super.setDragCallback(callback); } }
Esto puede ser un buen comienzo en cómo resolver los otros problemas:
❌ la barra de herramientas no se puede colapsar a través de arrastra
❌ el diseño del coordinador principal consume algunos desplazamientos
En realidad no soy una buena persona de UI / animación, pero hardwork vale la pena entender el código a veces, encontrar la función de devolución de llamada derecha / reemplazar a implementar.
Establezca esto como comportamiento para appbarlayout
<android.support.design.widget.AppBarLayout android:id="@+id/bottom_sheet_appbar" style="@style/BottomSheetAppBarStyle" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_behavior="your.package.CustomAppBarLayoutBehavior">
El diseño de la pantalla completa del diseño de appbar es el siguiente:
<android.support.design.widget.AppBarLayout android:id="@+id/appbar" android:layout_width="match_parent" android:layout_height="@dimen/detail_backdrop_height" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" android:fitsSystemWindows="true"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_scrollFlags="scroll|exitUntilCollapsed" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:expandedTitleMarginStart="48dp" app:expandedTitleMarginEnd="64dp"> <ImageView android:id="@+id/backdrop" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:fitsSystemWindows="true" app:layout_collapseMode="parallax" /> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:layout_collapseMode="pin" /> </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" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingTop="24dp"> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/card_margin"> <LinearLayout style="@style/Widget.CardContent" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Info" android:textAppearance="@style/TextAppearance.AppCompat.Title" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/cheese_ipsum" /> </LinearLayout> </android.support.v7.widget.CardView> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/card_margin" android:layout_marginLeft="@dimen/card_margin" android:layout_marginRight="@dimen/card_margin"> <LinearLayout style="@style/Widget.CardContent" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Friends" android:textAppearance="@style/TextAppearance.AppCompat.Title" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/cheese_ipsum" /> </LinearLayout> </android.support.v7.widget.CardView> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/card_margin" android:layout_marginLeft="@dimen/card_margin" android:layout_marginRight="@dimen/card_margin"> <LinearLayout style="@style/Widget.CardContent" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Related" android:textAppearance="@style/TextAppearance.AppCompat.Title" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/cheese_ipsum" /> </LinearLayout> </android.support.v7.widget.CardView> </LinearLayout> </android.support.v4.widget.NestedScrollView> <android.support.design.widget.FloatingActionButton android:layout_height="wrap_content" android:layout_width="wrap_content" app:layout_anchor="@id/appbar" app:layout_anchorGravity="bottom|right|end" android:src="@drawable/ic_discuss" android:layout_margin="@dimen/fab_margin" android:clickable="true"/>
Y después de eso debe implementar AppBarLayout.OnOffsetChangedListener en su clase y establecer el desplazamiento de la pantalla.