ViewPager inside ListView row prevents onItemClick to be fired

匿名 (未验证) 提交于 2019-12-03 02:16:02

问题:

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


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!