J'essaie d'implémenter une barre de recherche comme dans google maps Android:
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:
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
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!
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:
activity?.findViewById...
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.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>
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);
}
}
});
}