I have a ViewPager inside every row of a ListView. It works fine, it changes the views inside it when the user use the swipe gesture, but it prevents the ListView\'s onItemC
You need to do one of the following:
Set a setOnClickListener
on the Viewpager
's child or find
android:descendantFocusability="beforeDescendants"
I hope this may help you.
An improvement over Adrian's answer:
public class CustomListView extends ListView {
private GestureDetector gestureDetector;
public CustomListView(Context context) {
super(context);
gestureDetector = new GestureDetector(context, new CustomListViewOnGestureListener());
}
public CustomListView(Context context, AttributeSet attrs) {
super(context, attrs);
gestureDetector = new GestureDetector(context, new CustomListViewOnGestureListener());
}
public CustomListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
gestureDetector = new GestureDetector(context, new CustomListViewOnGestureListener());
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CustomListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
gestureDetector = new GestureDetector(context, new CustomListViewOnGestureListener());
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
gestureDetector.onTouchEvent(ev);
return super.dispatchTouchEvent(ev);
}
private class CustomListViewOnGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent ev) {
int firstVisiblePosition = getFirstVisiblePosition();
int itemPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
int childIndex = itemPosition - firstVisiblePosition;
View view = getChildAt(childIndex);
performItemClick(view, itemPosition, getItemIdAtPosition(itemPosition));
return true;
}
}
}
I would suggest to set OnClickListener
on each ViewPager
instance itself and avoid usage of onItemClickListener
of the ListView. You can then completely remove onInterceptTouchEvent()
too. That would be the simplest and stable solution. Less code - less bugs ;)
The problem is with the list adapters viewholder. I had a similar issue when i was trying to implement same feature (viewpager inside a listview row). I resolved it by doing this---- Set the onclicklistener on viewholder object instead of setting on listview directly. To do this you have to implement onitemclicklistener on your adapter class.
I think better override listview onintercepter than viewgroup.
TouchEvent Flow simply :
Acitivity touch event - > viewgroup.dispatchtouchevent -> viewgroup.intercepter..-> view.dispatchtouch... -> .....
in this case list.dispatch call. toss event to ViewPager.dispatch
. but before ViewPager.dispatchtouchevent
, call ListView.intercepterTouchEvent
.
if dispatchTouchEvent
return false
call parent View
's TouchEvent
but return true
call flow descent.
if intercepterTouchEvent
return true
don't calling child dispatchTouchEvent
but return false
calling child dispatchTouchEvent
.
so listview.intercepterTouchEvent
return true
, calling onItemClick
.
so if listView.intercepterTouchEvent
return true
, not swiped viewPager
items.
you can know user's action swipe or click 2 way.
TouchEvent
and guesturedetector
..
in listview's IntercepterTouchEvent(Event ev);
VelocityTracker mVelocityTracker;
PointF mLastPoint;
public mListView(Context context) {
super(context);
init();
}
public mListView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public mListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
mLastPoint = new PointF();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(mVelocityTracker == null)
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(ev);
switch (ev.getAction()){
case MotionEvent.ACTION_MOVE:
mVelocityTracker.computeCurrentVelocity(100);
int x = (int)Math.abs(mVelocityTracker.getXVelocity());
int move_x = (int)Math.abs(ev.getX() - mLastPoint.x);
Log.d("ListView","speed : " + x +" move_x : " + move_x);
//here x is drag speed. (pixel/s)
// change value left right both value you want speed and move amount
if(move_x < 100 || x <100) {
mLastPoint.set(ev.getX(), ev.getY());
return true;
}
break;
case MotionEvent.ACTION_DOWN:
mLastPoint.set(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_UP:
mVelocityTracker.recycle();mVelocityTracker = null;
break;
}
return super.onInterceptTouchEvent(ev);
}
you can swipe speed about 100 or move amount 100 pixel. if not perform click event.
i hope this text can help you......
and add edit some code blow.
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(mVelocityTracker == null)
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(ev);
switch (ev.getAction()){
case MotionEvent.ACTION_MOVE:
mVelocityTracker.computeCurrentVelocity(10);
int x = (int)Math.abs(mVelocityTracker.getXVelocity());
int move_x = (int)Math.abs(ev.getX() - mLastPoint.x);
int move_y = (int)Math.abs(ev.getY() - mLastPoint.y);
Log.d("ListView","speed : " + x +" move_x : " + move_x + " move_y : "+ move_y);
if(move_x < move_y || x < 10) {
mLastPoint.set(ev.getX(), ev.getY());
return true;
}else if(move_x > move_y){
return false;
}
break;
case MotionEvent.ACTION_DOWN:
mLastPoint.set(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("ListView", "dispatch");
switch (ev.getAction()){
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
if(mVelocityTracker != null){mVelocityTracker.recycle();mVelocityTracker = null;}
break;
}
return super.dispatchTouchEvent(ev);;
}
As your viewpaper is your listview's child,its consuming the touch events. You can prevent this by making your viewpagers child unclickable,i.e.
TextView textView =(TextView)inflater.inflate(R.layout.page_viewpager,container, false);
textView.setText("Page " + position);
textView.setClickable(false);