55 votes

Faire glisser une image vers le haut avec Official Support Library 23.x.+ bottomSheet comme google maps

Mise à jour
Je veux obtenir le même comportement que celui de Google Maps. avec la bibliothèque de support 23.x.+ et sans AUCUNE 3ème bibliothèque

NOTE : il ne s'agit pas d'une question dupliquée car :

  1. Je veux utiliser les comportements, la bibliothèque de support et sans AUCUNE bibliothèque tierce (je l'ai ajouté dans le titre de la question et dans la description ci-dessus).

  2. Je voulais TOUS les comportements que vous voyez dans le prochain gif, les autres questions demandent un ou deux comportements et utilisent de toute façon pour y parvenir.

    like you can see in this gif

J'ai déjà fait fonctionner l'Official bottomSheet (même à l'intérieur d'un onglet et d'un pager de vue).

Ce qui me rend fou Comment obtenir le comportement de l'image qui remonte de la feuille de fond lors du glissement vers le haut en utilisant la feuille de fond officielle ? .

J'ai essayé d'utiliser une ancre comme FAB sans succès.
J'ai lu quelque chose sur l'utilisation d'un écouteur de défilement mais les gens ont dit que ce n'était pas aussi fluide et rapide que Google Maps.

Mon XML (je ne pense pas que ça va aider mais quand même) :

<?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>

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.

93voto

MiguelHincapieC Points 3049

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 :

  1. 2 barres d'outils avec des animations qui répondent aux mouvements de la feuille de fond.
  2. 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).
  3. Une image de fond derrière la feuille de fond avec une sorte d'effet de parallaxe.
  4. Un titre (TextView) dans la barre d'outils qui apparaît lorsque la feuille inférieure l'atteint.
  5. L'arrière-plan de la barre d'état des notifications peut être transparent ou en couleur.
  6. 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 :

  1. Créez une classe Java et étendez-la à partir de CoordinatorLayout.Behavior<V>

  2. Copier-coller le code de la version par défaut BottomSheetBehavior à votre nouveau fichier.

  3. 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);
    }
  4. Ajouter un nouvel état

    public static final int STATE_ANCHOR_POINT = X ;

  5. 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
[ CustomBottomSheetBehavior ]

0 votes

Parfois, j'ai une barre de notification transparente, et c'est affreux. Une idée de la façon de la réparer ? Capture d'écran : imgur.com/a/ng311

1 votes

Nous travaillons à le corriger (j'ai le même problème). Restez à l'écoute sur github.

0 votes

Bonjour, votre lien vers complete file for backdrop Image with parallax effect ne fonctionne pas. Pourriez-vous le réparer ? Merci !

2voto

Vous pouvez obtenir cet effet en utilisant un comportement de mise en page du coordinateur. Vous devrez étendre une classe CoordinatorLayout.Behaviour et écrire une dépendance sur l'une des vues de la mise en page du coordinateur, en gardant votre vue contenant l'image comme enfant. Pour simplifier les choses, vous devez attacher le comportement personnalisé à la vue contenant l'image. Pour obtenir de l'aide sur l'écriture de comportements personnalisés, veuillez suivre le lien suivant Écriture de comportements personnalisés

0 votes

Il semble que tu sois sur la bonne voie, j'ai juste jeté un coup d'œil rapide au lien, demain je le lirai en profondeur. Mais si vous voulez la "réponse acceptée", vous devriez donner un exemple bien expliqué ou un extrait de code ou compléter ce que j'ai :)

1 votes

Le lien fourni a beaucoup plus et de nombreux extraits de code expliquant tout en détail . Jetez-y un coup d'œil, c'est facile à comprendre.

0 votes

Oui mais comme SO rulez l'a dit, nous devrions exposer une réponse complète au lieu d'un lien, parce qu'un lien peut mourir éventuellement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X