Estoy tratando de implementar una horizontal recyclerview y cada elemento de la recyclerview será vertical recyclerview con un diseño de cuadrícula. El problema que estoy enfrentando es que cuando me tratan de desplazarse por el niño recyclerview verticalmente a veces los padres recyclerview toma el libro y comienza a desplazarse horizontalmente. Los enfoques he intentado solucionar esto,

  1. setNestedScrollingEnabled(false) en el padre recyclerview
  2. En el onTouch() del niño recyclerview puedo deshabilitar eventos de toque en el padre recyclerview llamado requestdisallowinterceptTouchevent(false)

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

InformationsquelleAutor nikhil | 2016-03-29

13 Comentarios

  1. 26

    El problema me pareció interesante. Así que trató de implementar y esto es lo que he logrado (también se puede ver la video aquí) que es bastante suave.

    Anidado RecyclerView. Cómo evitar que los padres RecyclerView de llegar desplaza mientras el niño RecyclerView es el desplazamiento?

    Así que usted puede intentar algo como esto:

    Definir CustomLinearLayoutManager la ampliación de LinearLayoutManager como este:

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

    y establecer esta CustomLinearLayoutManager a su padre RecyclerView.

    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 niño RecyclerView, definir CustomGridLayoutManager la ampliación de 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 establecer como layoutManger para el niño RecyclerView:

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

    Así que, básicamente, de los padres RecyclerView sólo está escuchando horizontal se desplaza y el niño RecyclerView sólo está escuchando vertical se desplaza.

    Junto con eso, si usted también quiere manejar diagonal pase el dedo (que es poco sesgada, ya sea vertical u horizontal), puede incluir un gesto de escucha en el padre RecylerView.

    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);
            }
    }

    Así ParentRecyclerView pide el niño de la vista (en nuestro caso, VerticalRecyclerView) para manejar el evento de desplazamiento. Si el niño controles de la vista, a continuación, el padre no hace nada más padre que finalmente controla el desplazamiento.

    • Ampliación de esquema lineal fue un gran consejo. Casi me cambió toda mi aplicación para NestedScrollList. Parece que me salvó de ese esfuerzo.
    • impresionante. 🙂
    • Increíble respuesta, en mi caso no tenía la necesidad de utilizar la totalidad de la solución, sólo la SimpleOnGestureListener parte y funciona perfectamente. Gracias.
  2. 8

    setNestedScrollingEnabled(falso) en el primario recyclerview

    Lo que podrías intentar es setNestedScrollingEnabled(false) en el niño RecyclerView, si los hubiere. RecyclerView ‘s nestedscroll-ness es la de un niño (por eso se implementa NestedScrollingChild).

    En el onTouch() del niño recyclerview puedo deshabilitar eventos de toque en
    el padre recyclerview por llamada
    requestdisallowinterceptTouchevent(falso)

    Esto debería funcionar, pero lo que usted debe hacer es requestDisallowInterceptTouchEvent(true), no false. Si usted subclase RecyclerView, puede reemplazar 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 toque de escucha 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);
        }
    });
  3. 7

    Me corrigió este problema en un proyecto similar, tomando el enfoque opuesto a usted (y todos los demás aquí).

    En lugar de permitir que el niño diga a los padres cuando dejar de mirar los acontecimientos, dejo que el de los padres a decidir cuándo ignorar (basado en la dirección). Este enfoque requiere una vista personalizada pesar de que puede ser un poco más de trabajo. A continuación es lo que he creado que sería utilizado como el Exterior/vista 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
    }
    }
    • Esto funcionó para mí para un escenario diferente horizontal de RV que contiene coordinador contiene el colapso de la barra de herramientas y anidado de desplazamiento de la vista. La única cosa que tenía que cambiar es establecer mLockDirection = null; en dispatchTouchEvent() porque en onInterceptTouchEvent() y onTouchEvent() no son llamados en todos los casos.
    • Aquí está el resultado: i.giphy.com/qKfMIz3TbJ3hK.gif
    • Gracias por estos valiosos del enfoque. El enfoque para manejar este tipo de cosas en los padres es mucho más fácil. He conseguido hacer una vertical dentro de una horizontal viewpager con la modificación de la lógica.
  4. 5

    Establece que el oyente anidada RecyclerView

     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);
    • ¿Cómo funciona esto? ¿Puede por favor explicar un poco?
    • Es no permitir la intersección de eventos de toque de los padres RecyclerView cuando anidada RecyclerView está siendo desplazado.
  5. 3

    Probar esta. Para mi caso de uso se ha trabajado:

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

    de intentar el siguiente código, en la esperanza de 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;
    }
    });
  7. 2

    Intentar código de abajo para desplazarse interior RecyclerView.

    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;
    }
    });
  8. 2

    De la OMI, puede probar lo siguiente en el interior del Adaptador de exterior RecyclerView:

        @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, usted puede también intentar innerRV.setOnScrollChangeListener porque setOnScrollListener está en desuso.

    ACTUALIZACIÓN:

    Otra opción es el uso de addOnScrollListener en lugar de setOnScrollListener

    Espero que ayude!

  9. 2

    Que debe hacer 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 los ayude.

    • si devuelve false en onInterceptTouchEvent, onTouchEvent nunca será llamada
  10. 1

    Utilizar este código para desactivar desplazamiento en recyclerview:

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

    El problema existe en Android la aplicación de la onInterceptTouchEvent() método para RecyclerView.
    Este blog se llama el problema y lo arregla como bien http://nerds.headout.com/fix-horizontal-scrolling-in-your-android-app/.
    La única diferencia es que el padre se desplaza vertical y el niño en posición horizontal. Pero la solución se tiene el cuidado que se debe trabajar para su situación.

    • Sólo en caso de que alguien se ha preguntado, el artículo enlazado ahora da un 404… pero que no cunda el pánico, ha sido reflejada en algún lugar de China : tuicool.com/articles/ENbiqi
  12. -1

    extender un administrador 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;
    }
    }

    Configurar el gestor de diseño para este «Administrador de diseño Personalizado»

Dejar respuesta

Please enter your comment!
Please enter your name here