tutorial support studio recyclerview recycler developer cardview card android android-layout listview ontouchlistener android-cardview

support - lista de visualización de Android dentro de cardview onTouch conflicto de escucha/sensibilidad



recycler view cardview (2)

Aquí hay un proyecto de prueba que muestra el enlace de dropbox de problema: https://www.dropbox.com/sh/8s3v9ydcj6jvpl8/AACZ2VRP2N9R1ec7pxrsAn0ga?dl=0

Esta es una continuación de la pregunta que tuve aquí que fue respondida, pero ahora estoy preguntando sobre la sensibilidad / conflicto de onTouch: Android CardView con ListView dentro - onTouchListener en CardView no funciona

Tengo un cardview con una vista de lista adentro. Voy a necesitar que el desplazamiento y el elemento del clic en la vista de lista también funcionen y también quiero poder mover toda la vista de tarjeta con el ontouchlistener. He configurado unTouchListener en la vista de tarjeta, pero no funciona correctamente, ya que el desplazamiento de la vista de lista está en conflicto con el movimiento de la vista de tarjeta.

He podido trabajar perfectamente en iOS a la perfección, por lo que también debería poder funcionar en Android.

Código:

Pon esto en build.gradle:

compile ''com.android.support:cardview-v7:22.0+''

Actividad principal:

import android.animation.Animator; import android.graphics.PointF; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.CardView; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.RelativeLayout; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private ListView mylistview; private CustomCardView mycardview; PointF lastLocation; static final int REFRESH_RATE = 10; //or 20, or 5, or 30, whatever works best int counter = 0; PointF viewCenter; PointF cardOriginalLocation; boolean checkIfPanning; RelativeLayout layout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); layout = (RelativeLayout)findViewById(R.id.layout); mycardview = (CustomCardView)findViewById(R.id.mycardview); mylistview = (ListView) findViewById(R.id.myListView); List<String> your_array_list = new ArrayList<String>(); your_array_list.add("foo"); your_array_list.add("bar"); your_array_list.add("foo"); your_array_list.add("bar"); your_array_list.add("foo"); your_array_list.add("bar"); your_array_list.add("foo"); your_array_list.add("bar"); your_array_list.add("foo"); your_array_list.add("bar"); your_array_list.add("foo"); your_array_list.add("bar"); your_array_list.add("foo"); your_array_list.add("bar"); ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,your_array_list ); mylistview.setAdapter(arrayAdapter); mycardview.setCardElevation(20); mycardview.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mycardview.getViewTreeObserver().removeOnGlobalLayoutListener(this); cardOriginalLocation = new PointF(mycardview.getX(), mycardview.getY()); viewCenter = new PointF(layout.getWidth() / 2, layout.getHeight() / 2); } }); View.OnTouchListener onTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if(mylistview!=null){ //Route all touch event to listview without logic System.out.println("AAAAAA Touched list"); mylistview.onTouchEvent(event); } if (event.getAction()==MotionEvent.ACTION_DOWN){ System.out.println("ACTION_DOWN"); checkIfPanning=true; lastLocation = new PointF(event.getRawX(),event.getRawY()); return true; } else if (checkIfPanning && event.getAction()==MotionEvent.ACTION_MOVE/* && (getFrontCard().getX() - (lastLocation.x - event.getRawX()))<=10*/){ System.out.println("ACTION_MOVE"); counter += 1; if((REFRESH_RATE % counter) == 0) { float newx = mycardview.getX() - (lastLocation.x - event.getRawX()); float newy = mycardview.getY() - (lastLocation.y - event.getRawY()); mycardview.setX(newx); mycardview.setY(newy); lastLocation.set(event.getRawX(), event.getRawY()); float completedPercent = Math.min(((mycardview.getX() + mycardview.getWidth() / 2) - viewCenter.x) / viewCenter.x, 1); float angle = (completedPercent*15); mycardview.setRotation(angle); } counter=0; return true; } else if (event.getAction()==MotionEvent.ACTION_UP){ System.out.println("ACTION_UP"); checkIfPanning=false; float displaceentFromCenterX = ((mycardview.getX()+mycardview.getWidth()/2) - viewCenter.x); if (Math.abs(displaceentFromCenterX)>225){ float toMove; if (displaceentFromCenterX>0){ toMove = layout.getWidth()+mycardview.getHeight(); } else { toMove = -mycardview.getWidth()-mycardview.getHeight(); } mycardview.animate().rotationBy(30).translationX(toMove).setDuration(100).setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { System.out.println("onAnimationStart"); } @Override public void onAnimationEnd(Animator animation) { animation.removeListener(this); System.out.println("DONNNNNE"); mycardview.setX(cardOriginalLocation.x); mycardview.setY(cardOriginalLocation.y); mycardview.setRotation(0); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); } else { mycardview.animate().rotation(0).translationX(0).translationY(0).setDuration(100).setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); } return true; } else { checkIfPanning=false; if(mylistview!=null){ //Route all touch event to listview without logic System.out.println("BBBBB Touched list"); mylistview.onTouchEvent(event); return true; } } return true; } }; mycardview.setOnTouchListener(onTouchListener); } }

Tanto el oyente cardview onTouch como la vista de lista detectan el toque ahora. Sin embargo, cuando intento mover la vista de carta, la vista de lista sigue intentando desplazarse. Y cuando trato de desplazarme en la lista, la tarjeta mueve intead.

Entiendo por qué esto está sucediendo. Básicamente, el oyente onTouch parece estar en conflicto con el movimiento de vista de tarjeta con desplazamiento de vista de lista, pero no estoy seguro de cómo averiguar en el código si estoy tratando de desplazar o desplazar la vista de tarjeta.

XML para MainActivity:

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.pranapps.testontouch.MainActivity" android:id="@+id/layout" > <com.pranapps.testontouch.CustomCardView xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="200dp" android:id="@+id/mycardview" card_view:cardUseCompatPadding="true"> <ListView android:layout_width="match_parent" android:layout_height="fill_parent" android:id="@+id/myListView" android:dividerHeight="0.2dp" android:overScrollMode="always" android:smoothScrollbar="true" android:groupIndicator="@null" ></ListView> </com.pranapps.testontouch.CustomCardView> </RelativeLayout>

Código de CustomCardView:

package com.pranapps.testontouch; import android.content.Context; import android.support.v7.widget.CardView; import android.util.AttributeSet; import android.view.MotionEvent; /** * Created by pranoychowdhury on 5/9/16. */ public class CustomCardView extends CardView { public CustomCardView(Context context) { super(context); } public CustomCardView(Context context, AttributeSet attrs) { super(context, attrs); } public CustomCardView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { /* * This method determines whether we want to intercept the motion. * If we return true, onTouchEvent will be called. */ System.out.println("Touched custom from card"); return true; } }

Por favor ayuda con sugerencias para probar! ¡Gracias!

Editar: aquí hay un video de cómo funciona en iOS. Puedo desplazarme por la vista de lista. Paneo a la izquierda o derecha hace que la tarjeta se mueva. https://www.dropbox.com/s/5dm52vtjb1xgcl5/iOS.mov?dl=0

La vista de tarjeta no se desplazará, solo se desplazará el contenido dentro de la vista de tarjeta (que es la vista de lista). La tarjeta de visita solo puede moverse siguiendo los dedos. Imagine la yesca donde se pueden cambiar las tarjetas y dentro de la tarjeta, puede desplazarse.


CardView se está enfocando así que intente agregar android:descendantFocusability="blocksDescendants" en su vista de tarjeta como

<com.pranapps.testontouch.CustomCardView xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="200dp" android:id="@+id/mycardview" android:descendantFocusability="blocksDescendants"> //here your listview </com.pranapps.testontouch.CustomCardView>


Pude resolverlo yo mismo luego de 2 semanas de experimentar.

Quité el onInterceptTouchEvent y ontouch de la vista de tarjeta. En su lugar, cree un OnTouchListener personalizado y adjúntelo a la vista de lista. Luego, en el OnTouchListener personalizado, intenta descubrir el ángulo al que va el toque. si el ángulo está dentro de un rango específico, luego hacia arriba o hacia abajo desplazarse. deslizar de izquierda a derecha. El ángulo que todavía estoy tratando de calibrar para descubrir cuál es el mejor para la usabilidad, pero esto parece funcionar decentemente bien.

Obtuve un poco de ayuda de este enlace para calcular ángulos: cómo detectar la dirección del deslizamiento entre izquierda / derecha y arriba / abajo

Solución:

Actividad principal:

package com.pranapps.testontouch; import android.animation.Animator; import android.graphics.PointF; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.CardView; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.Toast; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private ListView mylistview; private CustomCardView mycardview; PointF lastLocation; static final int REFRESH_RATE = 10; //or 20, or 5, or 30, whatever works best int counter = 0; PointF viewCenter; PointF cardOriginalLocation; boolean checkIfPanning; RelativeLayout layout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); layout = (RelativeLayout)findViewById(R.id.layout); mycardview = (CustomCardView)findViewById(R.id.mycardview); mylistview = (ListView) findViewById(R.id.myListView); List<String> your_array_list = new ArrayList<String>(); your_array_list.add("foo"); your_array_list.add("bar"); your_array_list.add("foo"); your_array_list.add("bar"); your_array_list.add("foo"); your_array_list.add("bar"); your_array_list.add("foo"); your_array_list.add("bar"); your_array_list.add("foo"); your_array_list.add("bar"); your_array_list.add("foo"); your_array_list.add("bar"); your_array_list.add("foo"); your_array_list.add("bar"); ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,your_array_list ); mylistview.setAdapter(arrayAdapter); mycardview.setCardElevation(20); mycardview.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mycardview.getViewTreeObserver().removeOnGlobalLayoutListener(this); cardOriginalLocation = new PointF(mycardview.getX(), mycardview.getY()); viewCenter = new PointF(layout.getWidth() / 2, layout.getHeight() / 2); } }); mylistview.setOnTouchListener(new OnSwipeTouchListener(){ @Override public void onSwipeStart(MotionEvent motionEvent) { super.onSwipeStart(motionEvent); System.out.println("Swipe Start"); checkIfPanning=true; //if (lastLocation==null) { lastLocation = new PointF(motionEvent.getRawX(),motionEvent.getRawY()); //} } @Override public void onSwipeEnd(MotionEvent motionEvent) { super.onSwipeEnd(motionEvent); System.out.println("Swipe End"); checkIfPanning=false; float displaceentFromCenterX = ((mycardview.getX()+mycardview.getWidth()/2) - viewCenter.x); if (Math.abs(displaceentFromCenterX)>225){ float toMove; if (displaceentFromCenterX>0){ toMove = layout.getWidth()+mycardview.getHeight(); } else { toMove = -mycardview.getWidth()-mycardview.getHeight(); } mycardview.animate().rotationBy(30).translationX(toMove).setDuration(100).setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { System.out.println("onAnimationStart"); } @Override public void onAnimationEnd(Animator animation) { animation.removeListener(this); System.out.println("DONNNNNE"); mycardview.setX(cardOriginalLocation.x); mycardview.setY(cardOriginalLocation.y); mycardview.setRotation(0); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); } else { mycardview.animate().rotation(0).translationX(0).translationY(0).setDuration(100).setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); } lastLocation = null; } @Override public void onSwipe(MotionEvent motionEvent) { super.onSwipe(motionEvent); System.out.println("Swiping"); counter += 1; if((REFRESH_RATE % counter) == 0) { float newx = mycardview.getX() - (lastLocation.x - motionEvent.getRawX()); float newy = mycardview.getY() - (lastLocation.y - motionEvent.getRawY()); mycardview.setX(newx); mycardview.setY(newy); lastLocation.set(motionEvent.getRawX(), motionEvent.getRawY()); float completedPercent = Math.min(((mycardview.getX() + mycardview.getWidth() / 2) - viewCenter.x) / viewCenter.x, 1); float angle = (completedPercent*15); mycardview.setRotation(angle); } counter=0; } @Override public void onScroll(MotionEvent motionEvent) { super.onSwipe(motionEvent); mylistview.onTouchEvent(motionEvent); } }); View.OnTouchListener onTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { System.out.println("ONTOUCHHHH"); if (event.getAction()==MotionEvent.ACTION_DOWN){ System.out.println("ACTION_DOWN"); checkIfPanning=true; lastLocation = new PointF(event.getRawX(),event.getRawY()); return true; } else if (checkIfPanning && event.getAction()==MotionEvent.ACTION_MOVE){ System.out.println("ACTION_MOVE"); counter += 1; if((REFRESH_RATE % counter) == 0) { float newx = mycardview.getX() - (lastLocation.x - event.getRawX()); float newy = mycardview.getY() - (lastLocation.y - event.getRawY()); mycardview.setX(newx); mycardview.setY(newy); lastLocation.set(event.getRawX(), event.getRawY()); float completedPercent = Math.min(((mycardview.getX() + mycardview.getWidth() / 2) - viewCenter.x) / viewCenter.x, 1); float angle = (completedPercent*15); mycardview.setRotation(angle); } counter=0; return true; } else if (event.getAction()==MotionEvent.ACTION_UP){ System.out.println("ACTION_UP"); checkIfPanning=false; float displaceentFromCenterX = ((mycardview.getX()+mycardview.getWidth()/2) - viewCenter.x); if (Math.abs(displaceentFromCenterX)>225){ float toMove; if (displaceentFromCenterX>0){ toMove = layout.getWidth()+mycardview.getHeight(); } else { toMove = -mycardview.getWidth()-mycardview.getHeight(); } mycardview.animate().rotationBy(30).translationX(toMove).setDuration(100).setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { System.out.println("onAnimationStart"); } @Override public void onAnimationEnd(Animator animation) { animation.removeListener(this); System.out.println("DONNNNNE"); mycardview.setX(cardOriginalLocation.x); mycardview.setY(cardOriginalLocation.y); mycardview.setRotation(0); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); } else { mycardview.animate().rotation(0).translationX(0).translationY(0).setDuration(100).setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); } return true; } return true; } }; mycardview.setOnTouchListener(onTouchListener); } }

OnSwipeTouchListener:

package com.pranapps.testontouch; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.View.OnTouchListener; public class OnSwipeTouchListener implements OnTouchListener{ boolean checkIfStartedTouch; boolean checkIfSwiping; boolean checkIfScrolling; private final GestureDetector gestureDetector = new GestureDetector(new GestureListener()); private MotionEvent currentEvent; @Override public boolean onTouch(View v, MotionEvent event) { currentEvent = event; if (event.getAction()==MotionEvent.ACTION_DOWN){ checkIfStartedTouch = true; } else if (event.getAction()==MotionEvent.ACTION_UP){ if (checkIfSwiping){ onSwipeEnd(event); } checkIfStartedTouch = false; checkIfScrolling = false; checkIfSwiping = false; } else { if (checkIfSwiping){ //System.out.println("swipe2"); onSwipe(event); return false; } if (checkIfScrolling){ //System.out.println("scroll2"); onScroll(event); return false; } } return gestureDetector.onTouchEvent(event); } public void onSwipe(MotionEvent motionEvent) {} public void onScroll(MotionEvent motionEvent) {} public void onSwipeEnd(MotionEvent motionEvent) {} public void onSwipeStart(MotionEvent motionEvent) {} public enum Direction { up, down, left, right; /** * Returns a direction given an angle. * Directions are defined as follows: * <p/> * 0 is on right side middle (east direction) * angle is anticlockwise, 90 is at north pole * * @param angle an angle from 0 to 360 - e * @return the direction of an angle */ public static Direction get(double angle) { System.out.println("Angle: "+angle); if (inRange(angle, 50, 130)) { System.out.println("UPPPPPPPPPP"); return Direction.up; } else if (inRange(angle, 0, 50) || inRange(angle, 310, 360)) { System.out.println("RIGHT"); return Direction.right; } else if (inRange(angle, 240, 310)) { System.out.println("DOWN"); return Direction.down; } else { System.out.println("LEFTT"); return Direction.left; } }//i think I have finally figured out an android coding UI problem after 3 weeks. xml files might work for web development but designing decent UIs on a dedicated device using xml files is a nightmare. The fragmentation of device different resolutions makes things even harder. /** * @param angle an angle * @param init the initial bound * @param end the final bound * @return returns true if the given angle is in the interval [init, end). */ private static boolean inRange(double angle, float init, float end) { return (angle >= init) && (angle < end); } } private final class GestureListener extends SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { return true; } /*@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { System.out.println("flingggg"); float x1 = e1.getX(); float y1 = e1.getY(); float x2 = e2.getX(); float y2 = e2.getY(); Direction direction = getDirection(x1, y1, x2, y2); return onSwipe(direction); }*/ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // Grab two events located on the plane at e1=(x1, y1) and e2=(x2, y2) // Let e1 be the initial event // e2 can be located at 4 different positions, consider the following diagram // (Assume that lines are separated by 90 degrees.) // // // / A / // / / // D e1 B // / / // / C / // // So if (x2,y2) falls in region: // A => it''s an UP swipe // B => it''s a RIGHT swipe // C => it''s a DOWN swipe // D => it''s a LEFT swipe // System.out.println("onscrollllll"); float x1 = e1.getX(); float y1 = e1.getY(); float x2 = e2.getX(); float y2 = e2.getY(); Direction direction = getDirection(x1, y1, x2, y2); return onSwipe(direction); } public boolean onSwipe(Direction direction) { if (direction == Direction.left || direction == Direction.right) { System.out.println("swipe"); if (!checkIfSwiping) { checkIfSwiping = true; onSwipeStart(currentEvent); } return false; } else { System.out.println("scroll"); checkIfScrolling=true; return true; } } /** * Given two points in the plane p1=(x1, x2) and p2=(y1, y1), this method * returns the direction that an arrow pointing from p1 to p2 would have. * * @param x1 the x position of the first point * @param y1 the y position of the first point * @param x2 the x position of the second point * @param y2 the y position of the second point * @return the direction */ public Direction getDirection(float x1, float y1, float x2, float y2) { double angle = getAngle(x1, y1, x2, y2); return Direction.get(angle); } /** * Finds the angle between two points in the plane (x1,y1) and (x2, y2) * The angle is measured with 0/360 being the X-axis to the right, angles * increase counter clockwise. * * @param x1 the x position of the first point * @param y1 the y position of the first point * @param x2 the x position of the second point * @param y2 the y position of the second point * @return the angle between two points */ public double getAngle(float x1, float y1, float x2, float y2) { double rad = Math.atan2(y1 - y2, x2 - x1) + Math.PI; return (rad * 180 / Math.PI + 180) % 360; } } }

CustomCardView:

package com.pranapps.testontouch; import android.content.Context; import android.support.v7.widget.CardView; import android.util.AttributeSet; import android.view.MotionEvent; /** * Created by pranoychowdhury on 5/9/16. */ public class CustomCardView extends CardView { public CustomCardView(Context context) { super(context); } public CustomCardView(Context context, AttributeSet attrs) { super(context, attrs); } public CustomCardView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { //System.out.println("Touched custom from card"); return false; } }

activity_main.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.pranapps.testontouch.MainActivity" android:id="@+id/layout" > <com.pranapps.testontouch.CustomCardView xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="200dp" android:id="@+id/mycardview" card_view:cardUseCompatPadding="true"> <ListView android:layout_width="match_parent" android:layout_height="fill_parent" android:id="@+id/myListView" android:dividerHeight="0.2dp" android:overScrollMode="always" android:smoothScrollbar="true" android:groupIndicator="@null" ></ListView> </com.pranapps.testontouch.CustomCardView> </RelativeLayout>