Voici l'application que j'essaie de créer avec tous les éléments décrits ci-dessous:
Tout fonctionne, cependant, je souhaite que la vue de recyclage horizontale interne ne capture aucun des parchemins verticaux. Tous les défilements verticaux doivent aller vers la vue de recyclage verticale externe, et non pas vers l'horizontale, pour que le défilement vertical permette à la barre d'outils de sortir de la vue en fonction de son scrollFlag.
Lorsque je mets le doigt sur la partie "StrawBerry Plant" de la vue de recyclage et que je fais défiler vers le haut, la barre d'outils défile:
Si je mets le doigt sur la vue de défilement horizontale et que je fais défiler vers le haut, la barre d’outils ne disparaît pas du tout.
Ce qui suit est mon code de mise en page XML jusqu'à présent.
La mise en page Activity xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:id="@+id/fragment_container"
Android:clipChildren="false">
<Android.support.design.widget.CoordinatorLayout
Android:orientation="vertical"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:id="@+id/container"
>
<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:minHeight="?attr/actionBarSize"
Android:background="?attr/colorPrimary"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways">
</Android.support.v7.widget.Toolbar>
<Android.support.design.widget.TabLayout
Android:id="@+id/sliding_tabs"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:background="?attr/colorPrimary"
style="@style/CustomTabLayout"
/>
</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.design.widget.CoordinatorLayout>
</FrameLayout>
La disposition "Fruits" fragment xml (qui est le code du fragment - le fragment est étiqueté dans l'image ci-dessus):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:orientation="vertical"
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<ProgressBar
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:id="@+id/progressBar"
Android:visibility="gone"
Android:layout_centerInParent="true"
Android:indeterminate="true"/>
<!-- <Android.support.v7.widget.RecyclerView-->
<com.example.simon.customshapes.VerticallyScrollRecyclerView
Android:id="@+id/main_recyclerview"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
/>
</RelativeLayout>
J'ai utilisé une classe personnalisée appelée VerticallyScrollRecyclerView qui suit l'exemple de Google concernant la gestion des événements tactiles dans un groupe de vues. Son but est d'intercepter et de consommer tous les événements de défilement vertical afin qu'il puisse faire défiler la barre d'outils: http://developer.Android.com/training/gestures/viewgroup.html
Le code pour VerticallyScrollRecyclerView est ci-dessous:
public class VerticallyScrollRecyclerView extends RecyclerView {
public VerticallyScrollRecyclerView(Context context) {
super(context);
}
public VerticallyScrollRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VerticallyScrollRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
ViewConfiguration vc = ViewConfiguration.get(this.getContext());
private int mTouchSlop = vc.getScaledTouchSlop();
private boolean mIsScrolling;
private float startY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
// Always handle the case of the touch gesture being complete.
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
// Release the scroll.
mIsScrolling = false;
startY = ev.getY();
return super.onInterceptTouchEvent(ev); // Do not intercept touch event, let the child handle it
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
Log.e("VRecView", "its moving");
if (mIsScrolling) {
// We're currently scrolling, so yes, intercept the
// touch event!
return true;
}
// If the user has dragged her finger horizontally more than
// the touch slop, start the scroll
// left as an exercise for the reader
final float yDiff = calculateDistanceY(ev.getY());
Log.e("yDiff ", ""+yDiff);
// Touch slop should be calculated using ViewConfiguration
// constants.
if (Math.abs(yDiff) > 5) {
// Start scrolling!
Log.e("Scroll", "we are scrolling vertically");
mIsScrolling = true;
return true;
}
break;
}
}
return super.onInterceptTouchEvent(ev);
}
private float calculateDistanceY(float endY) {
return startY - endY;
}
}
La mise en page "Favori" qui est la vue de recyclage dans la vue de recyclage verticale:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
Android:layout_width="match_parent"
Android:layout_height="match_parent"
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:background="@color/white"
xmlns:app="http://schemas.Android.com/apk/res-auto">
<TextView
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:text="Favourite"
Android:layout_marginTop="8dp"
Android:layout_marginBottom="8dp"
Android:layout_marginLeft="16dp"
Android:id="@+id/header_fav"/>
<Android.support.v7.widget.RecyclerView
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:orientation="horizontal"
Android:layout_below="@+id/header_fav"
Android:id="@+id/recyclerview_fav">
</Android.support.v7.widget.RecyclerView>
</RelativeLayout>
Cela me dérange depuis un moment et je n'ai pas réussi à trouver une solution. Est-ce que quelqu'un sait comment résoudre ce problème?
5 points à Griffindor pour la réponse correcte et bien sûr, des points de réputation sur SO.
Solution testée:
Tout ce dont vous avez besoin est d’appeler mInnerRecycler.setNestedScrollingEnabled(false);
sur votre RecyclerView
s intérieur
Explication :
RecyclerView
prend en charge le défilement imbriqué introduit dans API 21
via l'implémentation de l'interface NestedScrollingChild
. Il s'agit d'une fonctionnalité intéressante lorsque vous avez une vue de défilement à l'intérieur d'une autre qui défile dans la même direction et que vous souhaitez faire défiler la View
intérieure uniquement lorsqu'elle est activée.
Dans tous les cas, RecyclerView
par défaut appelle RecyclerView.setNestedScrollingEnabled(true);
sur lui-même lors de l'initialisation. Maintenant, revenons au problème, puisque vos deux RecyclerView
s sont dans le même ViewPager
qui a le AppBarBehavior
, le CoordinateLayout
doit décider quel parchemin doit répondre lorsque vous faites défiler votre RecyclerView
intérieur; lorsque le défilement imbriqué de votre RecyclerView
intérieur est activé, il obtient le focus du défilement et le CoordinateLayout
choisit de répondre à son défilement par-dessus le défilement de RecyclerView
extérieur. Le fait est que, puisque vos RecyclerView
s internes ne défilent pas verticalement, il n'y a pas de changement de défilement vertical (du point de vue de CoordinateLayout
), et s'il n'y a pas de changement, le AppBarLayout
ne change pas non plus.
Dans votre cas, étant donné que vos RecyclerView
s internes défilent dans une direction différente, vous pouvez le désactiver, ce qui permet au CoordinateLayout
de ne pas tenir compte de son défilement et de répondre au défilement de RecyclerView
extérieur.
Remarquer :
L'attribut xml Android:nestedScrollingEnabled="boolean"
n'est pas destiné à être utilisé avec RecyclerView
et toute tentative d'utilisation de Android:nestedScrollingEnabled="false"
aboutira à un Java.lang.NullPointerException
. Par conséquent, vous devrez au moins le faire en code.
Je suis un peu en retard mais cela fonctionnera certainement pour les autres personnes confrontées au même problème
mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
int action = e.getAction();
// Toast.makeText(getActivity(),"HERE",Toast.LENGTH_SHORT).show();
switch (action) {
case MotionEvent.ACTION_POINTER_UP:
rv.getParent().requestDisallowInterceptTouchEvent(true);
break;
}
return false;
}
Solution testée, utilisez une NestedScrollView()
personnalisée.
Code:
public class CustomNestedScrollView extends NestedScrollView {
public CustomNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// Explicitly call computeScroll() to make the Scroller compute itself
computeScroll();
}
return super.onInterceptTouchEvent(ev);
}
}
essayer
public OuterRecyclerViewAdapter(List<Item> items) {
//Constructor stuff
viewPool = new RecyclerView.RecycledViewPool();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//Create viewHolder etc
holder.innerRecyclerView.setRecycledViewPool(viewPool);
}
recylerview interne utilisera le même viewpool et ce sera plus lisse
Je vous suggère d'ajouter la vue de recyclage horizontale à l'intérieur de fragments tels que l'application Google.
si quelqu'un cherche toujours, essayez ceci:
private val Y_BUFFER = 10
private var preX = 0f
private var preY = 0f
mView.rv.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
override fun onTouchEvent(p0: RecyclerView, p1: MotionEvent) {
}
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
when (e.action) {
MotionEvent.ACTION_DOWN -> rv.parent.requestDisallowInterceptTouchEvent(true)
MotionEvent.ACTION_MOVE -> {
if (Math.abs(e.x - preX) > Math.abs(e.y - preY)) {
rv.parent.requestDisallowInterceptTouchEvent(true)
} else if (Math.abs(e.y - preY) > Y_BUFFER) {
rv.parent.requestDisallowInterceptTouchEvent(false)
}
}
}
preX = e.x
preY = e.y
return false
}
override fun onRequestDisallowInterceptTouchEvent(p0: Boolean) {
}
})
il vérifie si le défilement horizontal est en cours, puis n'autorise pas le parent à gérer l'événement