studio recyclerview que hacer elementos ejemplo construyen como cardview android android-recyclerview nestedrecyclerview

android - hacer - RecyclerView anidado. ¿Cómo evitar que RecyclerView principal se desplace mientras que RecyclerView secundario se desplaza?



refresh recyclerview android studio (13)

Estoy intentando implementar una vista de recyclerview horizontal y cada elemento de la vista de recyclerview será una vista de recyclerview vertical con un diseño de cuadrícula. El problema al que me estoy enfrentando es que cuando intento desplazar verticalmente la recyclerview secundaria, a veces la recyclerview primaria toma la scroll y comienza a desplazarse horizontalmente. Los enfoques que intenté arreglar esto son,

  1. setNestedScrollingEnabled(false) en el recyclerview principal
  2. En el onTouch() de la recyclerview secundaria, desactivo los eventos táctiles en la recyclerview primaria mediante requestdisallowinterceptTouchevent(false)

Ninguna de las soluciones anteriores proporciona una solución perfecta para el problema. Cualquier ayuda es apreciada


setNestedScrollingEnabled (falso) en el recyclerview principal

Lo que podría probar es setNestedScrollingEnabled(false) en el RecyclerView secundario , si lo hubiera. El control de nidos de RecyclerView es el de un niño (es por eso que implementa NestedScrollingChild ).

En el onTouch () de la recyclerview secundaria, desactivo los eventos táctiles en la recyclerview primaria mediante requestdisallowinterceptTouchevent (false)

Esto debería funcionar, pero lo que debe hacer es requestDisallowInterceptTouchEvent(true) , no false . Si subclase RecyclerView, puede anular onTouchEvent :

@Override public boolean onTouchEvent(MotionEvent event) { if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP) { // ensure we release the disallow request when the finger is lifted getParent().requestDisallowInterceptTouchEvent(false); } else { getParent().requestDisallowInterceptTouchEvent(true); } // Call the super class to ensure touch handling return super.onTouchEvent(event); }

O, con un oyente táctil desde el exterior,

child.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (v.getId() == child.getId()) { if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP) { // ensure we release the disallow request when the finger is lifted child.getParent().requestDisallowInterceptTouchEvent(false); } else { child.getParent().requestDisallowInterceptTouchEvent(true); } } // Call the super class to ensure touch handling return super.onTouchEvent(event); } });


Ahora puedes probar android:nestedScrollingEnabled porque Google solucionó un problema con los usos de nestedScrollingEnabled (Número 197932)


Arreglé este problema en un proyecto similar tomando el enfoque opuesto a usted (y a todos los demás aquí).

En lugar de permitir que el niño le diga a los padres cuándo dejar de mirar los eventos, dejo que los padres decidan cuándo ignorar (según la dirección). Este enfoque requiere una vista personalizada, aunque puede ser un poco más de trabajo. A continuación se muestra lo que creé, que se usaría como vista externa / principal.

public class DirectionalRecyclerView extends RecyclerView { private static float LOCK_DIRECTION_THRESHOLD; //The slop private float startX; private float startY; private LockDirection mLockDirection = null; public DirectionalRecyclerView(Context context) { super(context); findThreshold(context); } public DirectionalRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); findThreshold(context) } public DirectionalRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); findThreshold(context); } private void findThreshold(Context context) { //last number is number of dp to move before deciding that''s a direction not a tap, you might want to tweak it LOCK_DIRECTION_THRESHOLD = context.getResources().getDisplayMetrics().density * 12; } //events start at the top of the tree and then pass down to //each child view until they reach where they were initiated //unless the parent (this) method returns true for this visitor @Override public boolean onInterceptTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = event.getX(); startY = event.getY(); break; case MotionEvent.ACTION_MOVE: if (mLockDirection == null) { float currentX = event.getX(); float currentY = event.getY(); float diffX = Math.abs(currentX - startX); float diffY = Math.abs(currentY - startY); if (diffX > LOCK_DIRECTION_THRESHOLD) { mLockDirection = LockDirection.HORIZONTAL; } else if (diffY > LOCK_DIRECTION_THRESHOLD) { mLockDirection = LockDirection.VERTICAL; } } else { //we have locked a direction, check whether we intercept //the future touches in this event chain //(returning true steals children''s events, otherwise we''ll // just let the event trickle down to the child as usual) return mLockDirection == LockDirection.HORIZONTAL; } break; case MotionEvent.ACTION_UP: mLockDirection = null; break; } //dispatch cancel, clicks etc. normally return super.onInterceptTouchEvent(event); } private enum LockDirection { HORIZONTAL, VERTICAL } }


El problema existe en la implementación de Android del método onInterceptTouchEvent () para RecyclerView. Este blog resuelve el problema y lo soluciona también - http://nerds.headout.com/fix-horizontal-scrolling-in-your-android-app/ . La única diferencia es que allí el padre se desplaza verticalmente y el niño horizontalmente. Pero la solución cuida de que también funcione para su situación.


El problema me pareció interesante. Así que traté de implementar y esto es lo que logré (también puede ver el video aquí ) que es bastante sencillo .

Así que puedes probar algo como esto:

Defina CustomLinearLayoutManager extendiendo LinearLayoutManager esta manera:

public class CustomLinearLayoutManager extends LinearLayoutManager { public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } @Override public boolean canScrollVertically() { return false; } }

y configure este CustomLinearLayoutManager en su RecyclerView principal.

RecyclerView parentRecyclerView = (RecyclerView)findViewById(R.id.parent_rv); CustomLinearLayoutManager customLayoutManager = new CustomLinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false); parentRecyclerView.setLayoutManager(customLayoutManager); parentRecyclerView.setAdapter(new ParentAdapter(this)); // some adapter

Ahora, para RecyclerView secundario, defina CustomGridLayoutManager personalizado extendiendo GridLayoutManager :

public class CustomGridLayoutManager extends GridLayoutManager { public CustomGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public CustomGridLayoutManager(Context context, int spanCount) { super(context, spanCount); } public CustomGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { super(context, spanCount, orientation, reverseLayout); } @Override public boolean canScrollHorizontally() { return false; } }

y configurarlo como layoutManger para el RecyclerView secundario:

childRecyclerView = (RecyclerView)itemView.findViewById(R.id.child_rv); childRecyclerView.setLayoutManager(new CustomGridLayoutManager(context, 3)); childRecyclerView.setAdapter(new ChildAdapter()); // some adapter

Entonces, básicamente, RecyclerView principal solo escucha los desplazamientos horizontales y RecyclerView secundario solo escucha los desplazamientos verticales.

Junto con eso, si también desea manejar el deslizamiento diagonal (que está poco inclinado hacia vertical u horizontal), puede incluir un detector de gestos en el RecylerView principal .

public class ParentRecyclerView extends RecyclerView { private GestureDetector mGestureDetector; public ParentRecyclerView(Context context) { super(context); mGestureDetector = new GestureDetector(this.getContext(), new XScrollDetector()); // do the same in other constructors } // and override onInterceptTouchEvent @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev); } }

Donde XScrollDetector es

class XScrollDetector extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return Math.abs(distanceY) < Math.abs(distanceX); } }

Por ParentRecyclerView tanto, ParentRecyclerView pide a la vista secundaria (en nuestro caso, VerticalRecyclerView) que maneje el evento de desplazamiento. Si la vista secundaria se maneja, el padre no hace nada más, el padre eventualmente maneja el desplazamiento.


En mi opinión, puede intentar lo siguiente dentro del Adaptador de RecyclerView externo:

@Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview, parent, false); RVAdapter2 recyclerViewAdapter2 = new RVAdapter2(); RecyclerView innerRV = (RecyclerView) v.findViewById(R.id.rv2); // Setup layout manager for items LinearLayoutManager layoutManager2 = new LinearLayoutManager(parent.getContext()); // Control orientation of the items layoutManager2.setOrientation(LinearLayoutManager.VERTICAL); innerRV.setLayoutManager(layoutManager2); innerRV.setAdapter(recyclerViewAdapter2); innerRV.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); recyclerView.getParent().requestDisallowInterceptTouchEvent(true); } }); return new MyViewHolder(v); }

Para API23, también puede probar innerRV.setOnScrollChangeListener porque setOnScrollListener está en desuso.

ACTUALIZAR:

Otra opción es usar addOnScrollListener lugar de setOnScrollListener

¡Espero eso ayude!


Establecer el oyente en RecyclerView anidado

View.OnTouchListener listener = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_MOVE ) { v.getParent().requestDisallowInterceptTouchEvent(true); } else { v.getParent().requestDisallowInterceptTouchEvent(false); } return false; } }; mRecyclerView.setOnTouchListener(listener);


Prueba esto. Para mi caso de uso ha funcionado:

nestedRecyclerView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return true; } });


Pruebe el siguiente código para desplazarse por RecyclerView interior.

innerRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { @Override public void onTouchEvent(RecyclerView recycler, MotionEvent event) { // Handle on touch events here int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: // Disallow Parent RecyclerView to intercept touch events. recycler.getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: // Allow Parent RecyclerView to intercept touch events. recycler.getParent().requestDisallowInterceptTouchEvent(false); break; } } @Override public boolean onInterceptTouchEvent(RecyclerView recycler, MotionEvent event) { return false; } });


Utilice este código para desactivar el desplazamiento en recyclerview :

recyclerView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return true; } });


extender un gestor de diseño personalizado como este

public class CustomLayoutManager extends LinearLayoutManager { private boolean isScrollEnabled = true; public CustomGridLayoutManager(Context context) { super(context); } @Override public boolean canScrollVertically() { return false; } }

Establezca el administrador de diseño en este "Administrador de diseño personalizado"


prueba el siguiente código, espero que funcione.

nestedRecyclerView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: // Disallow parent to intercept touch events. v.getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: // Allow parent to intercept touch events. v.getParent().requestDisallowInterceptTouchEvent(false); break; } // Handle inner(child) touch events. v.onTouchEvent(event); return true; } });


Deberías hacerlo de esta manera:

innerRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { @Override public void onTouchEvent(RecyclerView recycler, MotionEvent event) { // Handle on touch events here int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: recycler.getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: recycler.getParent().requestDisallowInterceptTouchEvent(false); break; case MotionEvent.ACTION_MOVE: recycler.getParent().requestDisallowInterceptTouchEvent(true); break; } } @Override public boolean onInterceptTouchEvent(RecyclerView recycler, MotionEvent event) { return false; } });

Espero que esto te ayude.