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:

  1. 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)
  2. 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.

    Como se puede ver en este gif

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> 

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:

  1. 2 barras de herramientas con animaciones que responden a los movimientos de la hoja inferior.
  2. Un FAB que se esconde cuando está cerca de la "barra de herramientas modal" (la que aparece cuando se está deslizando hacia arriba).
  3. Una imagen de fondo detrás de la hoja inferior con algún tipo de efecto de paralaje.
  4. Un título (TextView) en la barra de herramientas que aparece cuando la hoja inferior lo alcanza.
  5. La barra de estado de notificación puede convertir su fondo en transparente o en color.
  6. 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 desde AppBarLayout.ScrollingViewBehavior
  • onDependentViewChanged métodos layoutDependsOn y onDependentViewChanged . 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:

  1. Cree una clase Java y extiéndala desde CoordinatorLayout.Behavior<V>
  2. Copie el código de la pasta desde el archivo BottomSheetBehavior predeterminado a su nuevo.
  3. 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); } 
  4. Añadir un nuevo estado

    Public static final int STATE_ANCHOR_POINT = X;

  5. Modifique los métodos siguientes: onLayoutChild , onStopNestedScroll , BottomSheetBehavior<V> from(V view) y setState (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:
[ CustomBottomSheetBehavior ]

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

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.