web-dev-qa-db-fra.com

Android élévation de la barre d'outils lors du défilement

J'essaie d'implémenter une barre de recherche comme dans google maps Android:

enter image description here

Lorsque la vue du recycleur est dans son état initial, la barre d'outils n'a pas d'élévation. Ce n'est que lorsque les utilisateurs commencent à faire défiler que l'élévation devient visible. Et la barre de recherche (barre d'outils) ne s'effondre jamais. Voici ce que j'ai essayé de reproduire ceci:

<Android.support.design.widget.CoordinatorLayout 
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:fitsSystemWindows="true">

    <Android.support.v7.widget.RecyclerView
        Android:id="@+id/recyclerView"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent" />

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/appBarLayout"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content">

        <Android.support.v7.widget.Toolbar
            Android:id="@+id/toolbar"
            Android:layout_width="match_parent"
            Android:layout_height="64dp">

            <!-- content -->

        </Android.support.v7.widget.Toolbar>

    </Android.support.design.widget.AppBarLayout>

</Android.support.design.widget.CoordinatorLayout>

Et ici, vous pouvez voir le résultat:

enter image description here

Le problème avec ma solution est donc que l'élévation de la barre d'outils est toujours visible. Mais je veux qu'il n'apparaisse que lorsque la vue du recycleur défile derrière. Y a-t-il quelque chose dans la bibliothèque de support de conception qui permet un tel comportement comme vu dans l'application google maps?

J'utilise

com.Android.support:appcompat-v7:23.2.0
com.Android.support:design:23.2.0
20
Upvote

Que vous utilisiez un CoordinatorLayout ou non, un RecyclerView.OnScrollListener Semble être la bonne voie à suivre en ce qui concerne l'élévation. Cependant, d'après mon expérience, recyclerview.getChild(0).getTop() n'est pas fiable et devrait pas être utilisé pour déterminer l'état de défilement. Au lieu de cela, c'est ce qui fonctionne:

private static final int SCROLL_DIRECTION_UP = -1;
// ...
// Put this into your RecyclerView.OnScrollListener > onScrolled() method
if (recyclerview.canScrollVertically(SCROLL_DIRECTION_UP) {
   // Remove elevation
} else {
   // Show elevation
}

Assurez-vous d'attribuer un LayoutManager à votre RecyclerView ou l'appel de canScrollVertically peut provoquer un crash!

26
Daniel Veihelmann

C'est une bonne question mais aucune des réponses existantes n'est assez bonne. Appeler getTop() n'est absolument pas recommandé car il est très peu fiable. Si vous regardez les nouvelles versions des applications Google qui suivent les directives Material Design Refresh (2018), elles masquent l'élévation au début et l'ajoutent immédiatement lorsque l'utilisateur défile vers le bas et la masquent à nouveau lorsque l'utilisateur défile et atteint à nouveau le sommet.

J'ai réussi à obtenir le même effet en utilisant ce qui suit:

val toolbar: Android.support.v7.widget.Toolbar? = activity?.findViewById(R.id.toolbar);

recyclerView?.addOnScrollListener(object: RecyclerView.OnScrollListener() {
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy);

        if(toolbar == null) {
            return;
        }

        if(!recyclerView.canScrollVertically(-1)) {
            // we have reached the top of the list
            toolbar.elevation = 0f
        } else {
            // we are not at the top yet
            toolbar.elevation = 50f
        }
    }
});

Cela fonctionne parfaitement avec les vues verticales du recycleur (même avec une vue à onglet ou d'autres vues de recycleur à l'intérieur);

Quelques notes importantes:

  • Ici, je fais cela à l'intérieur d'un fragment d'où activity?.findViewById...
  • Si votre barre d'outils est imbriquée dans un AppBarLayout, au lieu d'appliquer l'élévation à la barre d'outils, vous devez l'appliquer à l'AppBarLayout.
  • Vous devez ajouter Android:elevation="0dp" et app:elevation="0dp" attributs à votre barre d'outils ou AppBarLayout afin que la vue du recycleur n'ait pas d'élévation au début.
4
Vahid Amiri

J'ai trouvé ceci quand page lorsque je voulais faire quelque chose de similaire, mais pour une hiérarchie de vues plus complexe.

Après quelques recherches, j'ai pu obtenir le même effet en utilisant un comportement personnalisé. Cela fonctionne pour n'importe quelle vue dans une disposition de coordinateur (étant donné qu'il existe un élément de défilement imbriqué tel que RecyclerView ou NestedScrollView)

Remarque: Cela ne fonctionne que sur l'API 21 et au-dessus, car ViewCompat.setElevation ne semble pas avoir d'effet avant Lollipop et AppBarLayout # setTargetElevation est obsolète

ShadowScrollBehavior.Java

public class ShadowScrollBehavior extends AppBarLayout.ScrollingViewBehavior
        implements View.OnLayoutChangeListener {

    int totalDy = 0;
    boolean isElevated;
    View child;

    public ShadowScrollBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child,
                                   View dependency) {
        parent.addOnLayoutChangeListener(this);
        this.child = child;
        return super.layoutDependsOn(parent, child, dependency);
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                       @NonNull View child, @NonNull View directTargetChild,
                                       @NonNull View target, int axes, int type) {
        // Ensure we react to vertical scrolling
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL ||
                super.onStartNestedScroll(coordinatorLayout, child, directTargetChild,
                        target, axes, type);
    }

    @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                  @NonNull View child, @NonNull View target,
                                  int dx, int dy, @NonNull int[] consumed, int type) {
        totalDy += dy;
        if (totalDy <= 0) {
            if (isElevated) {
                ViewGroup parent = (ViewGroup) child.getParent();
                if (parent != null) {
                    TransitionManager.beginDelayedTransition(parent);
                    ViewCompat.setElevation(child, 0);
                }
            }
            totalDy = 0;
            isElevated = false;
        } else {
            if (!isElevated) {
                ViewGroup parent = (ViewGroup) child.getParent();
                if (parent != null) {
                    TransitionManager.beginDelayedTransition(parent);
                    ViewCompat.setElevation(child, dp2px(child.getContext(), 4));
                }
            }
            if (totalDy > target.getBottom())
                totalDy = target.getBottom();
            isElevated = true;
        }
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
    }


    private float dp2px(Context context, int dp) {
        Resources r = context.getResources();
        float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
        return px;
    }


    @Override
    public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
        totalDy = 0;
        isElevated = false;
        ViewCompat.setElevation(child, 0);
    }
}

my_activity_layout.xml

<Android.support.design.widget.CoordinatorLayout
    Android:fitsSystemWindows="true"
    Android:layout_height="match_parent"
    Android:layout_width="match_parent">

    <Android.support.v7.widget.RecyclerView
        Android:id="@+id/recyclerView"
        Android:layout_height="match_parent"
        Android:layout_width="match_parent" />

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/appBarLayout"
        Android:layout_height="wrap_content"
        Android:layout_width="match_parent"
        app:layout_behavior="com.myapp.ShadowScrollBehavior">


        <Android.support.v7.widget.Toolbar
            Android:id="@+id/toolbar"
            Android:layout_height="64dp"
            Android:layout_width="match_parent">

            <!-- content -->

        </Android.support.v7.widget.Toolbar>

    </Android.support.design.widget.AppBarLayout>

</Android.support.design.widget.CoordinatorLayout>
3
user3425867

J'ai un RecyclerView dans mon fragment. Je pourrais obtenir un effet similaire en utilisant le code ci-dessous:

Ce n'est pas la manière la plus intelligente et vous pouvez attendre de meilleures réponses.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    // Initial Elevation
    final Toolbar toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
    if(toolbar!= null)
        toolbar.setElevation(0);

    // get initial position
    final int initialTopPosition = mRecyclerView.getTop();

    // Set a listener to scroll view
    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            if(toolbar!= null && mRecyclerView.getChildAt(0).getTop() < initialTopPosition ) {
                toolbar.setElevation(50);
            } else {
                toolbar.setElevation(0);
            }
        }
    });
}
3
W0rmH0le