Deslizando la imagen con la Official Support Library 23.x. + bottomSheet como google maps
Actualizar
Quiero lograr el mismo comportamiento que google maps tiene con Support Library 23.x. + y sin ANY tercera biblioteca
NOTA: esto no es una pregunta duplicada porque:
- Añadir un encabezado pegajoso al desplazamiento de paralaje - android
- Efecto Paralax en el fondo de la aplicación
- ¿Cómo tener un efecto de paralaje entre 2 ViewPagers?
- Lista 2D con RecyclerView en HorizontalScrollView
- Efecto Parallax de Android y View Pager
- Quiero utilizar Comportamientos, Soporte de Biblioteca y sin CUALQUIER biblioteca de terceros (lo agregué en el título de la pregunta y la descripción anterior)
- Quise TODOS los comportamientos que usted ve en gif siguiente, las otras preguntas están pidiendo uno o dos comportamientos y usando CUALQUIER MANERA de alcanzarlo.
Ya tengo la Hoja de trabajo oficial funcionando (incluso dentro de una pestaña y ver paginador).
¿Qué es lo que me está volviendo loco es cómo lograr el comportamiento de la imagen que surgen de la Hoja de Fondo cuando se desliza con el fondo oficial? .
He intentado usar el ancla como FAB sin éxito.
He leído algo sobre el uso de un oyente de desplazamiento, pero ppl dijo que no es suave y más rápido como google maps.
Mi XML (no creo que vaya a ayudar pero de todos modos):
<?xml version="1.0" encoding="utf-8"?> <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" tools:context=".ui.MasterActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" app:layout_scrollFlags="scroll|enterAlways|snap"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/borderlessButtonStyle" android:text="Departure" android:layout_gravity="center" android:id="@+id/buttonToolBar" /> </android.support.v7.widget.Toolbar> <android.support.design.widget.TabLayout android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabBackground="@android:color/white" app:tabTextColor="@color/colorAccent" app:tabSelectedTextColor="@color/colorAccent"/> </android.support.design.widget.AppBarLayout> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> <android.support.v4.widget.NestedScrollView android:id="@+id/asdf" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" app:behavior_peekHeight="100dp" android:fitsSystemWindows="true" app:layout_behavior="android.support.design.widget.BottomSheetBehavior"> <LinearLayout android:id="@+id/qwert" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="16dp" android:background="@android:color/white" android:padding="15dp"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="BOOTOMSHEET TITLE" android:textAppearance="@style/TextAppearance.AppCompat.Title" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Button1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="text 2" android:layout_margin="10dp"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="text 3" android:layout_margin="10dp"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="text 4" android:layout_margin="10dp"/> <FrameLayout android:layout_width="match_parent" android:layout_height="320dp" android:background="@color/colorAccent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Your remaining content here" android:textColor="@android:color/white" /> </FrameLayout> </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/asdf" app:layout_anchorGravity="top|right|end" android:src="@drawable/abc_ic_search_api_mtrl_alpha_copy" android:layout_margin="@dimen/fab_margin" android:clickable="true"/> </android.support.design.widget.CoordinatorLayout>
- Android ViewPager con imagen de fondo de Parallax
- Efecto Parallax con la barra desplegable no funcionando, imagen en el encabezado aplastado
- Problema con CoordinatorLayout y ImageView que ajusta el ancho mientras se desplaza
- LibGDX movimiento no suave
- Efecto Parallax en ViewPager de Android
- Efecto Parallax en cada elemento en una vista de reciclador?
Si quieres lograrlo usando la Biblioteca de soporte 23.4.0 + Te diré cómo lo conseguí y cómo funciona.
Nota: mis disculpas por mi inglés, traté de dar una respuesta de programación (sólo corto y mezclado con código útil), pero parece que no era lo suficientemente bueno …
Hasta ahora puedo ver que actividad / fragmento tiene los siguientes comportamientos:
- 2 barras de herramientas con animaciones que responden a los movimientos de la hoja inferior.
- Un FAB que se esconde cuando está cerca de la "barra de herramientas modal" (la que aparece cuando se está deslizando hacia arriba).
- Una imagen de fondo detrás de la hoja inferior con algún tipo de efecto de paralaje.
- Un título (TextView) en la barra de herramientas que aparece cuando la hoja inferior lo alcanza.
- La barra de estado de notificación puede convertir su fondo en transparente o en color.
- Un comportamiento de hoja inferior personalizado con un estado de "ancla".
Nota2: Esta respuesta hablar de 6 cosas no sobre 1 o 2 como otra pregunta, ¿ puede ver la diferencia ahora?
Ok, ahora vamos a ver un bye uno:
Barras de herramientas
Cuando se abre esa vista en google maps u puede ver una barra de herramientas en donde se puede buscar, es el único que no estoy haciendo iguales como google maps, porque quería hacerlo más genérico. De todas formas esa AppBarLayout
ToolBar
está dentro de un AppBarLayout
y se ha ocultado cuando empieza a arrastrar la hoja de fondo y aparece de nuevo cuando la hoja de fondo alcanza el estado COLAPSED.
Para lograrlo es necesario:
- Crear un
Behavior
y extenderlo desdeAppBarLayout.ScrollingViewBehavior
-
onDependentViewChanged
métodoslayoutDependsOn
yonDependentViewChanged
. Haciéndolo usted escuchará los movimientos del fondo. - Crear algunos métodos para ocultar y mostrar la AppBarLayout / ToolBar con animaciones.
Así es como lo hice para la primera barra de herramientas o ActionBar:
@Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { return dependency instanceof NestedScrollView; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { if (mChild == null) { initValues(child, dependency); return false; } float dVerticalScroll = dependency.getY() - mPreviousY; mPreviousY = dependency.getY(); //going up if (dVerticalScroll <= 0 && !hidden) { dismissAppBar(child); return true; } return false; } private void initValues(final View child, View dependency) { mChild = child; mInitialY = child.getY(); BottomSheetBehaviorGoogleMapsLike bottomSheetBehavior = BottomSheetBehaviorGoogleMapsLike.from(dependency); bottomSheetBehavior.addBottomSheetCallback(new BottomSheetBehaviorGoogleMapsLike.BottomSheetCallback() { @Override public void onStateChanged(@NonNull View bottomSheet, @BottomSheetBehaviorGoogleMapsLike.State int newState) { if (newState == BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED || newState == BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN) showAppBar(child); } @Override public void onSlide(@NonNull View bottomSheet, float slideOffset) { } }); } private void dismissAppBar(View child){ hidden = true; AppBarLayout appBarLayout = (AppBarLayout)child; mToolbarAnimation = appBarLayout.animate().setDuration(mContext.getResources().getInteger(android.R.integer.config_shortAnimTime)); mToolbarAnimation.y(-(mChild.getHeight()+25)).start(); } private void showAppBar(View child) { hidden = false; AppBarLayout appBarLayout = (AppBarLayout)child; mToolbarAnimation = appBarLayout.animate().setDuration(mContext.getResources().getInteger(android.R.integer.config_mediumAnimTime)); mToolbarAnimation.y(mInitialY).start(); }
El archivo completo si lo necesita
La segunda barra de herramientas o barra de herramientas "Modal":
Tienes que anular los mismos métodos pero en éste debes tener cuidado con más comportamientos:
- Mostrar / ocultar la barra de herramientas con animaciones
- Cambiar el color de la barra de statur / fondo
- Mostrar / ocultar el título de la hoja de fondo en la barra de herramientas
- Cierre la hoja inferior o envíela al estado colapsado
El código para este es un poco extenso por lo que dejaré que el enlace
El FAB
Este es un comportamiento personalizado también, pero se extiende desde FloatingActionButton.Behavior
. En onDependentViewChanged
usted tiene que mirar cuando alcanza el "offSet" o el punto en donde usted quiere ocultarlo. En mi caso quiero ocultarlo cuando está cerca de la segunda barra de herramientas, así que cavar en FAB padre (un CoordiantorLayout) en busca de AppBarLayout que contiene la barra de herramientas, a continuación, utilizar la OffSet
herramientas como OffSet
:
@Override public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) { if (offset == 0) setOffsetValue(parent); if (dependency.getY() <=0) return false; if (child.getY() <= (offset + child.getHeight()) && child.getVisibility() == View.VISIBLE) child.hide(); else if (child.getY() > offset && child.getVisibility() != View.VISIBLE) child.show(); return false; }
Completa el vínculo Comportamiento FAB personalizado
La imagen detrás de la hoja inferior con efecto de paralaje :
Al igual que los demás su comportamiento personalizado, la única cosa "complicada" en este es el pequeño algoritmo que mantiene la imagen anclada a la hoja de fondo y evitar el colapso de la imagen como efecto de paralaje por defecto:
@Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { if (mYmultiplier == 0) { initValues(child, dependency); return true; } float dVerticalScroll = dependency.getY() - mPreviousY; mPreviousY = dependency.getY(); //going up if (dVerticalScroll <= 0 && child.getY() <= 0) { child.setY(0); return true; } //going down if (dVerticalScroll >= 0 && dependency.getY() <= mImageHeight) return false; child.setY( (int)(child.getY() + (dVerticalScroll * mYmultiplier) ) ); return true; }
Archivo completo para telón de fondo Imagen con efecto paralaje
Ahora para el final: El comportamiento personalizado de la hoja de fondo
Para lograr los 3 pasos primero debes entender que el valor por defecto BottomSheetBehavior tiene 5 estados: STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN
y para el comportamiento de Google Maps debes agregar un estado medio entre colapsado y expandido: STATE_ANCHOR_POINT
.
Traté de extender el bottomSheetBehavior por defecto sin éxito, por lo que sólo copiar pegar todo el código y modificar lo que necesito.
Para lograr lo que estoy hablando siga los siguientes pasos:
- Cree una clase Java y extiéndala desde
CoordinatorLayout.Behavior<V>
- Copie el código de la pasta desde el archivo
BottomSheetBehavior
predeterminado a su nuevo. -
Modifique el método
clampViewPositionVertical
con el siguiente código:@Override public int clampViewPositionVertical(View child, int top, int dy) { return constrain(top, mMinOffset, mHideable ? mParentHeight : mMaxOffset); } int constrain(int amount, int low, int high) { return amount < low ? low : (amount > high ? high : amount); }
-
Añadir un nuevo estado
Public static final int STATE_ANCHOR_POINT = X;
-
Modifique los métodos siguientes:
onLayoutChild
,onStopNestedScroll
,BottomSheetBehavior<V> from(V view)
ysetState
(opcional)
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) { // First let the parent lay it out if (mState != STATE_DRAGGING && mState != STATE_SETTLING) { if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) { ViewCompat.setFitsSystemWindows(child, true); } parent.onLayoutChild(child, layoutDirection); } // Offset the bottom sheet mParentHeight = parent.getHeight(); mMinOffset = Math.max(0, mParentHeight - child.getHeight()); mMaxOffset = Math.max(mParentHeight - mPeekHeight, mMinOffset); //if (mState == STATE_EXPANDED) { // ViewCompat.offsetTopAndBottom(child, mMinOffset); //} else if (mHideable && mState == STATE_HIDDEN... if (mState == STATE_ANCHOR_POINT) { ViewCompat.offsetTopAndBottom(child, mAnchorPoint); } else if (mState == STATE_EXPANDED) { ViewCompat.offsetTopAndBottom(child, mMinOffset); } else if (mHideable && mState == STATE_HIDDEN) { ViewCompat.offsetTopAndBottom(child, mParentHeight); } else if (mState == STATE_COLLAPSED) { ViewCompat.offsetTopAndBottom(child, mMaxOffset); } if (mViewDragHelper == null) { mViewDragHelper = ViewDragHelper.create(parent, mDragCallback); } mViewRef = new WeakReference<>(child); mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child)); return true; } public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) { if (child.getTop() == mMinOffset) { setStateInternal(STATE_EXPANDED); return; } if (target != mNestedScrollingChildRef.get() || !mNestedScrolled) { return; } int top; int targetState; if (mLastNestedScrollDy > 0) { //top = mMinOffset; //targetState = STATE_EXPANDED; int currentTop = child.getTop(); if (currentTop > mAnchorPoint) { top = mAnchorPoint; targetState = STATE_ANCHOR_POINT; } else { top = mMinOffset; targetState = STATE_EXPANDED; } } else if (mHideable && shouldHide(child, getYVelocity())) { top = mParentHeight; targetState = STATE_HIDDEN; } else if (mLastNestedScrollDy == 0) { int currentTop = child.getTop(); if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) { top = mMinOffset; targetState = STATE_EXPANDED; } else { top = mMaxOffset; targetState = STATE_COLLAPSED; } } else { //top = mMaxOffset; //targetState = STATE_COLLAPSED; int currentTop = child.getTop(); if (currentTop > mAnchorPoint) { top = mMaxOffset; targetState = STATE_COLLAPSED; } else { top = mAnchorPoint; targetState = STATE_ANCHOR_POINT; } } if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) { setStateInternal(STATE_SETTLING); ViewCompat.postOnAnimation(child, new SettleRunnable(child, targetState)); } else { setStateInternal(targetState); } mNestedScrolled = false; } public final void setState(@State int state) { if (state == mState) { return; } if (mViewRef == null) { // The view is not laid out yet; modify mState and let onLayoutChild handle it later /** * New behavior (added: state == STATE_ANCHOR_POINT ||) */ if (state == STATE_COLLAPSED || state == STATE_EXPANDED || state == STATE_ANCHOR_POINT || (mHideable && state == STATE_HIDDEN)) { mState = state; } return; } V child = mViewRef.get(); if (child == null) { return; } int top; if (state == STATE_COLLAPSED) { top = mMaxOffset; } else if (state == STATE_ANCHOR_POINT) { top = mAnchorPoint; } else if (state == STATE_EXPANDED) { top = mMinOffset; } else if (mHideable && state == STATE_HIDDEN) { top = mParentHeight; } else { throw new IllegalArgumentException("Illegal state argument: " + state); } setStateInternal(STATE_SETTLING); if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) { ViewCompat.postOnAnimation(child, new SettleRunnable(child, state)); } } public static <V extends View> BottomSheetBehaviorGoogleMapsLike<V> from(V view) { ViewGroup.LayoutParams params = view.getLayoutParams(); if (!(params instanceof CoordinatorLayout.LayoutParams)) { throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); } CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params) .getBehavior(); if (!(behavior instanceof BottomSheetBehaviorGoogleMapsLike)) { throw new IllegalArgumentException( "The view is not associated with BottomSheetBehaviorGoogleMapsLike"); } return (BottomSheetBehaviorGoogleMapsLike<V>) behavior; }
Enlace al proyecto del agujero en donde usted puede ver todos los comportamientos de encargo
Nota3: la próxima vez agrega un comentario preguntando de una manera educada para el cambio de la respuesta o pregunta por qué esta respuesta tiene ALGUNAS cosas iguales que otras respuestas de la mía sobre el mismo tema ANTES de cerrarlo o marcar como duplicado.
Y aquí es cómo se ve su aspecto:
[ ]
Puede lograr el efecto utilizando un comportamiento de disposición del coordinador. Tendrá que extender una clase CoordinatorLayout.Behaviour y escribir una dependencia sobre una de las vistas en el diseño del coordinador, manteniendo su imagen que contiene la vista como el niño. Haciendo que sea sencillo, debe adjuntar el comportamiento personalizado escrito a la imagen que contiene la vista . Para obtener ayuda sobre cómo escribir comportamientos personalizados, siga el enlace Escribir comportamientos personalizados