recyclerview item android listview scroll android-recyclerview user-experience

android - item - Recycler horizontalVer dentro de ListView no desplazarse muy suave



recyclerview android (4)

Tengo un ListView , cada uno de cuyos elementos es un Horizontal RecyclerView . El problema que estoy enfrentando es que el desplazamiento horizontal no es muy suave. Si me desplazo / deslizo en una dirección perfectamente horizontal, funciona bien, pero si el desplazamiento es incluso ligeramente horizontal (por ejemplo, en un ángulo de 10 grados con respecto a la horizontal), se considera desplazamiento arriba / abajo en lugar de izquierda / derecha, debido al ListView padre. Intenté este método:

recyclerView.setOnTouchListener(new FlingRecyclerView.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: // Disallow ScrollView to intercept touch events. v.getParent().requestDisallowInterceptTouchEvent(true); Log.i("Scroll down", "Intercept true"); break; case MotionEvent.ACTION_UP: // Allow ScrollView to intercept touch events. v.getParent().requestDisallowInterceptTouchEvent(false); break; case MotionEvent.ACTION_MOVE: // Allow ScrollView to intercept touch events. v.getParent().requestDisallowInterceptTouchEvent(true); break; } // Handle HorizontalScrollView touch events. v.onTouchEvent(event); return true; } });

Pero no fue muy efectivo. De hecho, no pude notar mucha mejoría. Eso es porque se llama a este motionEvent solo cuando el movimiento es perfectamente horizontal, ese caso ya está funcionando bien. Entonces traté de hacer esto:

class MyGestureDetector extends GestureDetector.SimpleOnGestureListener { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { try { Log.i("TAG", "VelocityX " + Float.toString(velocityX) + "VelocityY " + Float.toString(velocityY)); Log.i("TAG", "e1X " + Float.toString(e1.getX()) + "e1Y " + Float.toString(e1.getY()) + "e2X " + Float.toString(e2.getX()) + "e2Y " + Float.toString(e2.getY())); // downward swipe if (e2.getY() - e1.getY() > SWIPE_MAX_OFF_PATH && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) { Toast.makeText(TestActivity.this, "Downward Swipe", Toast.LENGTH_SHORT).show(); Log.i("TAG", "Downward swipe"); } else if (e1.getY() - e2.getY() > SWIPE_MAX_OFF_PATH && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) { Toast.makeText(TestActivity.this, "Upward Swipe", Toast.LENGTH_SHORT).show(); Log.i("TAG", "Upward swipe"); } // right to left swipe else if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { Toast.makeText(TestActivity.this, "Left Swipe", Toast.LENGTH_SHORT).show(); Log.i("TAG", "Left swipe"); } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { Toast.makeText(TestActivity.this, "Right Swipe", Toast.LENGTH_SHORT).show(); Log.i("TAG", "Right swipe"); } } catch (Exception e) { // nothing } return false; } }

Y noté que registra los eventos solo cuando lanzo mi dedo, no cuando comienzo a deslizar. No funciona tan pronto como toco el RecyclerView . Y esto también, no produjo ninguna mejora notable. También me di cuenta de que estos métodos también dependen de la presión. Cuando hice un desliz con una ligera presión, no pasó nada. Y lo mismo hecho en el mismo camino, con mayor presión produce scroll.

¿Hay alguna manera de hacer que el desplazamiento sea uniforme, incluso si la ruta del deslizamiento no es horizontalmente recta o incluso si el movimiento es circular? Cualquier ayuda sería muy apreciada.


IIRC puede anular el método InterceptTouchEvent en ListView

private Point movementStart; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean superResult = super.onInterceptTouchEvent(ev); if(movementStart == null) { movementStart = new Point((int)ev.getX(), (int)ev.getY()); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: return false; case MotionEvent.ACTION_MOVE: //if movement is horizontal than don''t intercept it if(Math.abs(movementStart.x - ev.getX()) > Math.abs(movementStart.y - ev.getY())){ return false; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: movementStart = null; break; } return superResult; }

Puede extender el código anterior de esta manera:

private Point movementStart; private boolean disallowIntercept = false; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean superResult = super.onInterceptTouchEvent(ev); if(movementStart == null) { movementStart = new Point((int)ev.getX(), (int)ev.getY()); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: return false; case MotionEvent.ACTION_MOVE: //if movement is horizontal than don''t intercept it if(Math.abs(movementStart.x - ev.getX()) > Math.abs(movementStart.y - ev.getY())){ disallowIntercept = true; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: movementStart = null; disallowIntercept = false; break; } if(disallowIntercept) { return false; } else { return superResult; } }


¿Has probado verticalRow.setNestedScrollingEnabled(false); // Aquí verticalRow es una instancia de recylerView


Es probable que los eventos táctiles no estén seguros de dónde ir. Es probable que desee utilizar en Interceptar EventoTest junto con TouchSlop para determinar si debe robar eventos táctiles de las filas secundarias (vistas recicladoras).

El evento onInterceptTouchEvent es importante para que ListView pueda decidir si debe robar eventos táctiles de niños.

El deslizamiento táctil es importante porque define cuánto movimiento se requiere antes de que debamos actuar (en este caso para robar eventos del niño). Esto se debe a que tan solo tocar la pantalla con el dedo (sin movimiento obvio / intencionado) generará eventos MotionEvent.ACTION_MOVE (Puede verificar esto usted mismo agregando el registro en MOTION_MOVE para imprimir los eventos getX y getY.

Si aún tiene problemas, verifique que los niños de la vista de reciclado no estén esperando eventos de movimiento también. Si esos niños son, es posible que necesiten usar requestDisallowInterceptTouchEvent para evitar que la vista del reciclador y la vista de lista intercepten sus eventos.

Este código se basó en el código que se encuentra en los documentos de Android y puede ver el original y leer más sobre los eventos táctiles aquí .

MotionEvent mOriginal; mIsScrolling = false; // put these two lines in your constructor ViewConfiguration vc = ViewConfiguration.get(view.getContext()); mTouchSlop = vc.getScaledTouchSlop(); @Override public boolean onInterceptTouchEvent(MotionEvent ev) { /* * This method JUST determines whether we want to intercept the motion. * If we return true, onTouchEvent will be called and we do the actual * scrolling there. */ 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; return false; // Do not intercept touch event, let the child handle it } switch (action) { case MotionEvent.ACTION_DOWN: { mIsScrolling = false; setOriginalMotionEvent(ev); } case MotionEvent.ACTION_MOVE: { 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 final int xDiff = calculateDistanceX(ev); final int yDiff = calculateDistanceY(ev); // Touch slop should be calculated using ViewConfiguration // constants. if (yDiff > mTouchSlop && yDiff > xDiff) { // Start scrolling! mIsScrolling = true; return true; } break; } ... } // In general, we don''t want to intercept touch events. They should be // handled by the child view. Be safe and leave it up to the original definition return super.onInterceptTouchEvent(ev); } public void setOriginalMotionEvent(MotionEvent ev){ mOriginal = ev.clone(); } public int calculateDistanceX(ev){ int distance = Math.abs(mOriginal.getX() - ev.getX()); return distance; } public int calculateDistanceY(ev){ int distance = Math.abs(mOriginal.getY() - ev.getY()); return distance; }


Jim Baca me puso en el camino correcto, mi problema era que la vista principal (desplazamiento vertical) estaba robando el desplazamiento de las vistas secundarias (desplazamiento horizontal). Esto funcionó para mí:

/** * This class is a workaround for when a parent RecyclerView with vertical scroll * is a bit too sensitive and steals onTouchEvents from horizontally scrolling child views */ public class NestedScrollingParentRecyclerView extends RecyclerView { private boolean mChildIsScrolling = false; private int mTouchSlop; private float mOriginalX; private float mOriginalY; public NestedScrollingParentRecyclerView(Context context) { super(context); init(context); } public NestedScrollingParentRecyclerView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context); } public NestedScrollingParentRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { ViewConfiguration vc = ViewConfiguration.get(context); mTouchSlop = vc.getScaledTouchSlop(); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { // Release the scroll mChildIsScrolling = false; return false; // Let child handle touch event } switch (action) { case MotionEvent.ACTION_DOWN: { mChildIsScrolling = false; setOriginalMotionEvent(ev); } case MotionEvent.ACTION_MOVE: { if (mChildIsScrolling) { // Child is scrolling so let child handle touch event return false; } // If the user has dragged her finger horizontally more than // the touch slop, then child view is scrolling final int xDiff = calculateDistanceX(ev); final int yDiff = calculateDistanceY(ev); // Touch slop should be calculated using ViewConfiguration // constants. if (xDiff > mTouchSlop && xDiff > yDiff) { mChildIsScrolling = true; return false; } } } // In general, we don''t want to intercept touch events. They should be // handled by the child view. Be safe and leave it up to the original definition return super.onInterceptTouchEvent(ev); } public void setOriginalMotionEvent(MotionEvent ev) { mOriginalX = ev.getX(); mOriginalY = ev.getY(); } public int calculateDistanceX(MotionEvent ev) { return (int) Math.abs(mOriginalX - ev.getX()); } public int calculateDistanceY(MotionEvent ev) { return (int) Math.abs(mOriginalY - ev.getY()); } }