可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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 onItemClick method to be called. I know that the ViewPager is the culprit because when I hide it, the onItemClick is called, so this is what I am trying:
I have created a ViewGroup to be the row (RowView). This ViewGroup has onInterceptTouchEvent
overriden to avoid the ViewPager to handle further touch events when I detect a click. But the onItemClick callback is still not being called. And the list selector doesn't show either on click. I want this two features to work.
This is how the onInterceptTouchEvent from RowView looks like:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { int actionMasked = ev.getActionMasked(); switch(actionMasked) { case MotionEvent.ACTION_DOWN: Log.d("RowView", "OnInterceptTouchEvent - Down"); tapDetector.onTouchEvent(ev); return false; case MotionEvent.ACTION_CANCEL: Log.d("RowView", "OnInterceptTouchEvent - Cancel"); tapDetector.onTouchEvent(ev); break; case MotionEvent.ACTION_UP: if(tapDetector.onTouchEvent(ev)) { Log.d("RowView", "OnInterceptTouchEvent - UP!"); return true; } break; } return super.onInterceptTouchEvent(ev); }
Any suggestions to solve this?
EDIT: Not working example about how the onItemClick in MainActivity is not called when the ViewPager is active (Lollipop list selector doesn't appear either)
MainActivity
ListView listView = (ListView) findViewById(R.id.main_list); listView.setAdapter(new MainListAdapter(this, 30)); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Log.d(TAG, "onItemClick: " + position); } });
List item XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="80dp" android:descendantFocusability="blocksDescendants" > <TextView android:id="@+id/row_num" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" /> <android.support.v4.view.ViewPager android:id="@+id/row_viewpager" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="visible" /> </RelativeLayout>
List adapter:
public class MainListAdapter extends BaseAdapter { private Context context; private LayoutInflater inflater; private int count; public MainListAdapter(Context context, int count) { this.context = context; this.inflater = LayoutInflater.from(context); this.count = count; } @Override public int getCount() { return count; } @Override public Object getItem(int position) { return position; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if(convertView == null) { holder = new ViewHolder(); convertView = createRow(parent, holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.textView.setText(Integer.toString(position)); int randomPages = (int) (new Random().nextDouble()*5+2); holder.viewPager.setAdapter(new NumAdapter(context, randomPages)); return convertView; } private View createRow(ViewGroup parent, ViewHolder holder) { View view = inflater.inflate(R.layout.row_main_listview, parent, false); holder.textView = (TextView) view.findViewById(R.id.row_num); holder.viewPager = (ViewPager) view.findViewById(R.id.row_viewpager); view.setTag(holder); return view; } private static class ViewHolder { public TextView textView; public ViewPager viewPager; } }
ViewPager's Adapter:
public class NumAdapter extends PagerAdapter { private LayoutInflater inflater; private int count; public NumAdapter(Context context, int count) { this.inflater = LayoutInflater.from(context); this.count = count; } @Override public Object instantiateItem(ViewGroup container, int position) { TextView textView = (TextView) inflater.inflate(R.layout.page_viewpager, container, false); textView.setText("Page " + position); container.addView(textView); return textView; } @Override public int getCount() { return count; } @Override public boolean isViewFromObject(View arg0, Object arg1) { return arg0 == arg1; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View)object); } }
回答1:
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);; }
回答2:
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);
回答3:
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 ;)
回答4:
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.
回答5:
I think this should do the trick.
viewPager.getParent().requestDisallowInterceptTouchEvent(false);
I hope this will help you.
回答6:
The idea is to do the click listener on the view pager and not the listView
When adding the viewPager in the getView method, set its tag to be the position of the row
holder.viewPager.setTag(position);
Then add the click listener to the viewPager (also in getView)
viewPager.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { int position = v.getTag(); Log.d(TAG, "onItemClick: " + position); //Fire a delegate or notification of whatever you want to do on the item click. You now have the position myClickListener.onItemClicked(position); } });
回答7:
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.
回答8:
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; } } }
回答9:
This is what I've done finally to manage this although the list selector doesn't work. This could be improved, but, for now, it is the only workaround that I like how it works.
public class CustomListView extends ListView implements AbsListView.OnScrollListener { /* * Used for detect taps */ private GestureDetector tapDetector; /* * As we need to set our own OnScrollListener, this stores the one * used outside, if any */ private OnScrollListener onScrollListener; private boolean isScrolling = false; public CustomListView(Context context) { super(context); initView(context); } public CustomListView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public CustomListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public CustomListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context); } private void initView(Context context) { tapDetector = new GestureDetector(context, new TapListener()); super.setOnScrollListener(this); } @Override public void setOnScrollListener(OnScrollListener onScrollListener) { this.onScrollListener = onScrollListener; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { boolean isTap = tapDetector.onTouchEvent(ev); if(ev.getActionMasked() == MotionEvent.ACTION_UP) { // Don't perform the click if the ListView is scrolling // so it is able to stop the scroll if(isTap && !isScrolling && hasOnItemClickListener()) { int itemPosition = pointToPosition((int)ev.getX(), (int)ev.getY()); performItemClick(this, itemPosition, getItemIdAtPosition(itemPosition)); } } return super.dispatchTouchEvent(ev); } public boolean hasOnItemClickListener() { return getOnItemClickListener() != null; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { isScrolling = scrollState != OnScrollListener.SCROLL_STATE_IDLE; if(this.onScrollListener != null) { onScrollListener.onScrollStateChanged(view, scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if(this.onScrollListener != null) { onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } }