Андроид listview внутри cardview onTouch прослушиватель конфликт / чувствительность

Вот тестовый проект, в котором показана ссылка на проблему: https://www.dropbox.com/sh/8s3v9ydcj6jvpl8/AACZ2VRP2N9R1ec7pxrsAn0ga?dl=0

Это продолжение вопроса, который у меня был здесь, на который был дан ответ, но теперь я спрашиваю о чувствительности / конфликте onTouch: Android CardView с ListView внутри – onTouchListener на CardView не работает

У меня есть карта с списком внутри. Мне понадобится прокрутка и щелкните элемент в режиме списка, чтобы работать, и я хочу, чтобы иметь возможность перемещать весь вид карты с помощью ontouchlistener. Я установил onTouchListener в картотеке, но он не работает должным образом, так как свиток списка просматривает противоречие с движением карты.

Мне удалось получить аналогичную работу для работы на iOS, поэтому на андроиде тоже можно справиться.

Код:

Поместите это в build.gradle:

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

Основная деятельность:

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

Как прослушиватель cardview onTouch, так и список видят теперь обнаружение касания. Однако, когда я пытаюсь переместить картографию, список продолжает прокручиваться. И когда я пытаюсь прокручивать список, карта начинает двигаться.

Я понимаю, почему это происходит. По сути, слушатель onTouch, похоже, конфликтует с движением cardview с прокруткой списка, но я не уверен, как определить код в том, пытаюсь ли я прокручивать или перемещать карту.

XML для 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> 

Код 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; } } 

Пожалуйста, помогите с предложениями попробовать! Благодаря!

Изменить: вот видео о том, как это работает на iOS. Я могу прокручивать список. Панорамирование влево или вправо приводит к перемещению карты. https://www.dropbox.com/s/5dm52vtjb1xgcl5/iOS.mov?dl=0

Картинка не будет прокручиваться, будет прокручиваться только содержимое внутри карты (которое является списком). Карту можно перемещать только путем отслеживания пальцев. Представьте себе, где карты могут быть скопированы и внутри карты, вы можете прокручивать.

Я смог решить это сам после двух недель экспериментов.

Я удалил onInterceptTouchEvent и ontouch из карты. Вместо этого создайте пользовательский OnTouchListener и присоедините его к самому списку. Затем в пользовательском OnTouchListener попытайтесь определить угол, по которому происходит касание. Если угол в определенном диапазоне, то вверх или вниз прокрутите. Другой мудрый левый / правый салфетки. Угол я все еще пытаюсь откалибровать, чтобы выяснить, какой из них лучше всего подходит для удобства использования, но это, похоже, работает прилично.

Я получил немного помощи от этой ссылки для расчета углов: Как определить направление движения между левым / правым и вверх / вниз

Решение:

Основная деятельность:

 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> 

CardView получает фокус, поэтому попробуйте добавить android:descendantFocusability="blocksDescendants" в вашем виде карты, например

  <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>