Si vous voulez le réaliser en utilisant Support Library 23.4.0.+, je vous dirai comment je l'ai obtenu et comment il fonctionne.
D'après ce que je peux voir, cette activité/fragment a les comportements suivants :
- 2 barres d'outils avec des animations qui répondent aux mouvements de la feuille de fond.
- Un FAB qui se cache lorsqu'il est près de la "barre d'outils modale". (celle qui apparaît lorsque vous faites un glissement vers le haut).
- Une image de fond derrière la feuille de fond avec une sorte d'effet de parallaxe.
- Un titre (TextView) dans la barre d'outils qui apparaît lorsque la feuille inférieure l'atteint.
- L'arrière-plan de la barre d'état des notifications peut être transparent ou en couleur.
- Un comportement personnalisé de la feuille de fond avec un état "d'ancrage".
note2 : Cette réponse parle de 6 choses et non de 1 ou 2 comme les autres questions, Vous voyez la différence maintenant ?
Ok, maintenant vérifions un bye one :
Barres d'outils
Lorsque vous ouvrez cette vue dans google maps vous pouvez voir une barre d'outils où vous pouvez faire des recherches, c'est la seule que je ne fais pas égale à google maps parce que je voulais la faire plus générique. Quoi qu'il en soit, cette ToolBar
est à l'intérieur d'un AppBarLayout
et elle est masquée lorsque vous commencez à faire glisser la feuille inférieure et elle réapparaît lorsque la feuille inférieure atteint la limite de l'espace libre. COLLAPSED
l'État.
Pour y parvenir, vous avez besoin :
- créer un
Behavior
et l'étendre de AppBarLayout.ScrollingViewBehavior
- contourner
layoutDependsOn
et onDependentViewChanged
méthodes. En le faisant, vous écouterez les mouvements de l'écran de fond.
- créer quelques méthodes pour masquer et démasquer l'AppBarLayout/ToolBar avec des animations.
C'est ainsi que j'ai procédé pour la première barre d'outils ou 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();
}
le dossier complet si vous en avez besoin
La deuxième barre d'outils ou barre d'outils "modale" :
Vous devez surcharger certaines méthodes, mais dans celle-ci, vous devez vous occuper de plus de comportements :
- afficher/masquer la barre d'outils avec des animations
- changer la couleur/le fond de la barre d'état
- Afficher/masquer le titre de la feuille inférieure dans la barre d'outils.
- fermer la feuille de fond ou la faire passer à l'état plié
Le code pour celui-ci est un peu long, donc je vais laisser le lien
Le FAB
Il s'agit également d'un comportement personnalisé, mais il s'étend à partir de l'élément suivant FloatingActionButton.Behavior
. Sur onDependentViewChanged
vous devez regarder quand il atteint le "offSet" ou le point dans lequel vous voulez le cacher. Dans mon cas, je veux la cacher lorsqu'elle est proche de la deuxième barre d'outils, donc je creuse dans le parent FAB (un CoordiantorLayout) à la recherche du AppBarLayout qui contient la ToolBar, puis j'utilise la position de la ToolBar comme suit 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;
}
Lien complet sur le comportement du FAB personnalisé
L'image derrière le BottomSheet avec effet de parallaxe :
Comme les autres, il s'agit d'un comportement personnalisé, la seule chose "compliquée" dans celui-ci est le petit algorithme qui maintient l'image ancrée au BottomSheet et évite l'effondrement de l'image comme l'effet de parallaxe par défaut :
@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;
}
[fichier complet pour la toile de fond Image avec effet de parallaxe] [4]
Maintenant pour la fin : Le comportement personnalisé de BottomSheet
Pour réaliser ces 3 étapes, vous devez d'abord comprendre que le comportement par défaut de BottomSheetBehavior a 5 états : STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN
et pour le comportement de Google Maps, vous devez ajouter un état intermédiaire entre les états "collapsed" et "expanded" : STATE_ANCHOR_POINT
.
J'ai essayé d'étendre le comportement par défaut de bottomSheetBehavior sans succès, donc j'ai juste copié-collé tout le code et modifié ce dont j'ai besoin.
Pour réaliser ce dont je parle, suivez les étapes suivantes :
-
Créez une classe Java et étendez-la à partir de CoordinatorLayout.Behavior<V>
-
Copier-coller le code de la version par défaut BottomSheetBehavior
à votre nouveau fichier.
-
Modifier la méthode clampViewPositionVertical
avec le code suivant :
@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);
}
-
Ajouter un nouvel état
public static final int STATE_ANCHOR_POINT = X ;
-
Modifiez les méthodes suivantes : onLayoutChild
, onStopNestedScroll
, BottomSheetBehavior<V> from(V view)
et setState
(facultatif)
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;
}
lien vers le projet "trou dans lequel vous pouvez voir tous les comportements personnalisés
note3 : la prochaine fois, ajoutez un commentaire demandant de manière polie un changement de réponse ou demandez pourquoi cette réponse a QUELQUES choses d'égales que d'autres de mes réponses sur le même sujet AVANT de la fermer ou de la marquer comme dupliquée.
Et voici à quoi cela ressemble
[ ]
0 votes
Je vais juste demander la première chose qui m'est venue à l'esprit - avez-vous essayé d'utiliser
android:animateLayoutChanges="true"
? Si cela a un sens.0 votes
Non, je n'ai pas essayé. Demain, je vais jeter un coup d'oeil à ce que vous avez dit et je vous donnerai un feed-back.
0 votes
@McAwesomville il semble que vous devriez l'utiliser dans la liste, encore je n'ai aucune idée (personne d'ailleurs) s'ils le réalisent en utilisant la mise en page Collapsing et Coordinateur ou s'ils ont utilisé ce que vous suggérez. La seule chose que j'ai compris, c'est que ce n'est pas une image unique, mais un AppBarLayout avec un coordinateur et une image à l'intérieur.
0 votes
Haha, je me demandais la même chose (le glissement de l'image vers le haut - j'avais bottomsheet + coordinatorlayout avec CollapsingToolbarLayout qui fait un peu de parallaxe mais d'une autre manière que le cas de google maps) - puis j'ai trouvé votre code sur github qui fonctionne, et cette question