web-dev-qa-db-fra.com

Glissement vers le haut de l'image avec la bibliothèque officielle de support 23.x.

Mettre à jour
Je veux accomplir le même comportement que Google Maps a avec Support Library 23.x. + et sans AUCUNE 3ème bibliothèque

NOTE: ceci n'est pas une question dupliquée parce que:

  1. Je veux utiliser Behaviors, Support Library et sans TOUT bibliothèque tierce (je l'ai ajouté au titre de la question et à la description ci-dessus)
  2. Je voulais TOUS les comportements que vous voyez dans le gif suivant, les autres questions demandent un ou deux comportements et utilisent TOUT MOYEN pour y parvenir.

     like you can see in this gif

J'ai déjà la feuille officielle de travail (même dans un onglet et voir un pager). 

Ce qui me rend folle comment obtenir le comportement d'image résultant de BottomSheet lorsque vous glissez vers le haut à l'aide de bottomSheet officiel?.

J'ai essayé d'utiliser l'ancre comme FAB sans succès.
J'ai lu quelque chose sur l’utilisation d’un écouteur à défilement, mais ppl a dit que ce n’était ni lisse ni rapide, comme Google Maps .

Mon XML (je ne pense pas que ça va aider mais de toute façon):

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

Si vous souhaitez y parvenir avec Support Library 23.4.0. +, Je vous dirai comment je l’ai obtenue et comment cela fonctionne.

note: je m'excuse pour mon anglais, j'ai essayé de donner une réponse de programmation (juste brève et mélangée avec du code utile) mais j'ai l'impression que ce n'était pas assez bon.

Pour autant que je puisse voir que cette activité/ce fragment a les comportements suivants:

  1. 2 barres d'outils avec des animations qui répondent aux mouvements de la feuille inférieure.
  2. Un FAB qui se cache quand il est proche de la "barre d'outils modale" (celle qui apparaît lorsque vous glissez vers le haut).
  3. Une image de fond derrière la feuille inférieure avec une sorte d’effet de parallaxe.
  4. Un titre (TextView) dans la barre d’outils qui apparaît lorsque la feuille du bas l’atteint.
  5. La barre de notification satus peut transformer son arrière-plan en couleur transparente ou en couleur.
  6. Un comportement de feuille de fond personnalisé avec un état "ancre".

note2: Cette réponse parle de 6 choses pas de 1 ou 2 comme autre question, pouvez-vous voir la différence maintenant?

Ok, maintenant vérifions-en un sur un:

Barres d'outils
Lorsque vous ouvrez cette vue dans Google Maps, vous pouvez voir une barre d’outils dans laquelle vous pouvez effectuer une recherche, c’est le seul qui ne me ressemble pas, comme Google Maps, car je voulais le faire de manière plus générique. Quoi qu'il en soit, cette ToolBar est dans une AppBarLayout et elle est cachée lorsque vous commencez à faire glisser la feuille inférieure et elle réapparaît lorsque la feuille inférieure atteint l'état COLLAPSED.
Pour y parvenir, il vous faut:

  • créer une Behavior et l'étendre à partir de AppBarLayout.ScrollingViewBehavior
  • redéfinissez les méthodes layoutDependsOn et onDependentViewChanged. Ce faisant, vous écouterez les mouvements de la feuille de fond.
  • créer des méthodes pour masquer et afficher le AppBarLayout/ToolBar avec des animations.

Voici comment je l'ai fait 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 fichier complet si vous en avez besoin

La deuxième barre d'outils ou la barre d'outils "Modal":
Vous devez remplacer les mêmes méthodes mais dans celle-ci, vous devez vous préoccuper de plus de comportements:

  • afficher/masquer la barre d'outils avec des animations
  • changer la couleur/le fond de la barre
  • afficher/masquer le titre de BottomSheet dans la barre d'outils
  • fermez la feuille de fond ou envoyez-la à l'état réduit

Le code pour celui-ci est un peu volumineux alors je vais laisser le lien

Le FAB

Ceci est un comportement personnalisé aussi, mais s'étend de FloatingActionButton.Behavior. Dans onDependentViewChanged, vous devez regarder quand il atteint le "décalage" ou le point où vous voulez le cacher. Dans mon cas, je veux le masquer quand il est proche de la deuxième barre d'outils. Je creuse donc dans FAB parent (un CoordiantorLayout) à la recherche du AppBarLayout contenant la barre d'outils, puis j'utilise la position de la barre d'outils comme 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;
}

Complétez le lien Custom FAB Behavior

L'image derrière la feuille inférieure 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 dans la feuille de fond et évite que l’image ne s’effondre comme un 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 toile de fond Image avec effet de parallaxe

Maintenant pour la fin: Le comportement personnalisé de BottomSheet
Pour réaliser les 3 étapes, vous devez d'abord comprendre que BottomSheetBehavior par défaut 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 replié et développé: STATE_ANCHOR_POINT.
J'ai essayé d'étendre le bottomSheetBehavior par défaut sans succès, alors il me suffit de copier coller tout le code et de modifier 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. Copiez le code de collage du fichier par défaut BottomSheetBehavior dans votre nouveau.
  3. Modifiez 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 de trou dans où vous pouvez voir tous les comportements personnalisés

note3: la prochaine fois, ajoutez un commentaire demandant de manière polie de changer la réponse ou demandez-lui pourquoi cette réponse a une substance qui est égale à celle des autres sur le même sujet AVANT de le fermer ou de marquer comme dupliqué.

Et voici à quoi ça ressemble:
[CustomBottomSheetBehavior]

69
MiguelHincapieC

Vous pouvez obtenir l'effet en utilisant un comportement de présentation de coordinateur. Vous devrez étendre une classe CoordinatorLayout.Behaviour et écrire une dépendance sur l'une des vues de la présentation du coordinateur, en conservant votre vue contenant l'image comme enfant .. Pour simplifier, vous devez associer le comportement écrit personnalisé à la vue contenant l'image . Pour obtenir de l'aide sur la rédaction de comportements personnalisés, veuillez suivre le lien Écrire des comportements personnalisés

1