android - street - fotos de google maps
Imagen deslizante con Official Support Library 23.x.+bottomSheet como google maps (2)
Puede lograr el efecto utilizando un comportamiento de diseño de 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. Haciéndolo simple, necesita adjuntar el comportamiento escrito personalizado a la imagen que contiene la vista . Para obtener ayuda sobre la escritura de comportamientos personalizados, siga el enlace Escribir comportamientos personalizados
Actualizar
Quiero lograr el mismo comportamiento que google maps tiene con Support Library 23.x. + y sin CUALQUIER tercera biblioteca
NOTA: esta no es una pregunta duplicada porque:
- Deseo utilizar Behaviors, Support Library y sin CUALQUIER biblioteca de terceros (la agregué en el título de la pregunta y en la descripción anterior)
- Quería TODOS los comportamientos que ves en el siguiente gif, las otras preguntas están pidiendo uno o dos comportamientos y usar CUALQUIER MANERA para lograrlo.
Ya he trabajado en la Hoja inferior oficial (incluso dentro de una pestaña y en el buscapersonas).
Lo que me está volviendo loco es cómo lograr el comportamiento de la imagen que surge de la Hoja Inferior cuando se desliza hacia arriba usando la hoja inferior oficial. .
He intentado usar el ancla como FAB sin éxito.
Leí algo sobre el uso de un oyente de desplazamiento, pero la persona dijo que no es tan fácil y rápido como los mapas de Google.
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 desea lograrlo usando Support Library 23.4.0. + Le contaré cómo lo conseguí y cómo funciona.
nota: me disculpo por mi inglés, traté de dar una respuesta de programación (solo corta y mezclada con un código útil) pero parece que no fue lo suficientemente bueno ...
Hasta donde puedo ver esa 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 oculta cuando está cerca de la "barra de herramientas modal" (la que aparece cuando se desliza 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 satus de notificación puede convertir su fondo en transparente o a todo color.
- Un comportamiento de hoja inferior personalizado con un estado "ancla".
nota 2: Esta respuesta habla de 6 cosas, no de 1 o 2 como otras preguntas, ¿puedes ver la diferencia ahora?
Ok, ahora revisemos un bye uno:
ToolBars
Cuando abres esa vista en google maps puedes ver una barra de herramientas en donde puedes buscar, es la única que no estoy haciendo igual a google maps, porque quería hacerlo de forma más genérica. De todos modos, ToolBar
está dentro de un AppBarLayout
y se ocultó cuando comenzó a arrastrar BottomSheet y aparece de nuevo cuando BottomSheet alcanza el estado COLLAPSED
.
Para lograrlo necesitas:
- crear un
Behavior
y extenderlo desdeAppBarLayout.ScrollingViewBehavior
- anula los métodos
layoutDependsOn
yonDependentViewChanged
. Al hacerlo, escucharás los movimientos de la hoja inferior. - crea algunos métodos para ocultar y mostrar el 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 necesitas
La segunda barra de herramientas o barra de herramientas "modal":
Tienes que anular los mismos métodos, pero en este debes tener más comportamientos:
- mostrar / ocultar la barra de herramientas con animaciones
- cambiar el color / fondo de la barra de estadísticas
- mostrar / ocultar el título de la hoja inferior en la barra de herramientas
- cierre la hoja inferior o envíela al estado contraído
El código para este es un poco extenso, así que dejaré que el enlace
El FAB
Esto también es un Comportamiento personalizado, pero se extiende desde FloatingActionButton.Behavior
. En onDependentViewChanged
tienes que mirar cuando llega al "offSet" o señalar dónde quieres esconderlo. En mi caso, quiero ocultarlo cuando esté cerca de la segunda barra de herramientas, así que busco en el padre FAB (un CoordiantorLayout) buscando el AppBarLayout que contiene la barra de herramientas, luego uso la posición ToolBar 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;
}
Completo enlace personalizado de comportamiento FAB
La imagen detrás de la hoja inferior con efecto de paralaje :
Al igual que los demás, es un comportamiento personalizado, lo único "complicado" en este caso es el pequeño algoritmo que mantiene la Imagen anclada en el Fondo y evita el colapso de la imagen como el 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 de la hoja inferior personalizada
Para lograr los 3 pasos primero, debe comprender que el valor predeterminado de BottomSheetBehavior es de 5 estados: STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN
y para el comportamiento de Google Maps necesita agregar un estado intermedio entre colapsada y expandida: STATE_ANCHOR_POINT
.
Intenté extender el bottomSheetBehavior por defecto sin éxito, así que simplemente copié y pegué todo el código y modifiqué lo que necesitaba.
Para lograr lo que estoy diciendo siga los próximos pasos:
- Cree una clase Java y extiéndala desde
CoordinatorLayout.Behavior<V>
- Copie y pegue el código del archivo
BottomSheetBehavior
predeterminado en su nuevo archivo. 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); }
Agregar un nuevo estado
public static final int STATE_ANCHOR_POINT = X;
Modifique los siguientes métodos:
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 de hoyos donde puedes ver todos los comportamientos personalizados
nota 3: la próxima vez agregue un comentario preguntando de forma educada por el cambio de la respuesta o pregunte por qué esta respuesta tiene ALGUNA cantidad igual que otras respuestas mías sobre el mismo tema ANTES de cerrarlo o marcar como duplicado.
Y así es como se ve:
[ ]