ViewPager中ListView的OnItemClick事件无法触发的解决咨询
我之前做项目时也碰到过一模一样的情况,你判断的方向没错——ViewPager的触摸事件拦截机制确实会干扰子Fragment里ListView的点击事件,不过还有一种容易忽略的可能性是ListView的item布局本身有可点击控件抢占了焦点。我给你几个实用的解决方案,你可以根据自己的需求选:
方案一:自定义ViewPager,按需拦截触摸事件
如果你还需要保留ViewPager的滑动切换功能,只是不想让它拦截ListView区域的点击/触摸,那可以自定义一个ViewPager,重写onInterceptTouchEvent方法,让它在触摸点落在ListView范围内时,把事件交给ListView处理:
public class CustomViewPager extends ViewPager { private ListView mTargetListView; public CustomViewPager(Context context) { super(context); } public CustomViewPager(Context context, AttributeSet attrs) { super(context, attrs); } // 传入需要排除拦截的ListView public void setExcludeListView(ListView listView) { this.mTargetListView = listView; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (mTargetListView != null) { // 计算ListView在屏幕上的坐标范围 int[] screenLocation = new int[2]; mTargetListView.getLocationOnScreen(screenLocation); int listLeft = screenLocation[0]; int listTop = screenLocation[1]; int listRight = listLeft + mTargetListView.getWidth(); int listBottom = listTop + mTargetListView.getHeight(); // 如果触摸点在ListView内部,不拦截事件 if (ev.getRawX() >= listLeft && ev.getRawX() <= listRight && ev.getRawY() >= listTop && ev.getRawY() <= listBottom) { return false; } } // 其他情况沿用ViewPager默认的拦截逻辑 return super.onInterceptTouchEvent(ev); } }
接下来把布局文件里的ViewPager替换成这个CustomViewPager,然后在Fragment的onViewCreated方法里,给ViewPager设置要排除的ListView:
// Fragment中初始化ListView后 ListView listView = v.findViewById(R.id.listView); // 获取Activity里的CustomViewPager实例 CustomViewPager viewPager = getActivity().findViewById(R.id.viewpager); viewPager.setExcludeListView(listView);
方案二:完全禁用ViewPager的滑动功能(如果不需要滑动)
如果你的ViewPager只是用来配合TabLayout做标签切换,不需要用户滑动切换页面,那直接禁用ViewPager的触摸事件就最省心了,有两种方式:
方式A:自定义不可滑动的ViewPager(推荐)
这种方式兼容性最好,直接重写触摸相关方法:
public class NonSwipeableViewPager extends ViewPager { public NonSwipeableViewPager(Context context) { super(context); } public NonSwipeableViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onTouchEvent(MotionEvent event) { // 不处理任何触摸事件 return false; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { // 不拦截触摸事件,让子View自行处理 return false; } }
把布局里的ViewPager换成这个类,之后ViewPager就不会再拦截任何触摸事件,ListView的点击自然就能正常触发了。
方式B:用反射禁用滑动(快速但不推荐)
如果不想自定义ViewPager,也可以用反射修改ViewPager内部的滚动逻辑,不过这种方式依赖Android系统的内部实现,可能在不同版本出问题,应急用用还行:
public static void disableViewPagerSwipe(ViewPager viewPager) { try { Class<?> viewPagerClass = ViewPager.class; Field scrollerField = viewPagerClass.getDeclaredField("mScroller"); scrollerField.setAccessible(true); Object scroller = scrollerField.get(viewPager); Class<?> scrollerClazz = Class.forName("android.widget.Scroller"); Constructor<?> constructor = scrollerClazz.getConstructor(Context.class, Interpolator.class); Object customScroller = constructor.newInstance(viewPager.getContext(), new LinearInterpolator()) { @Override public void startScroll(int startX, int startY, int dx, int dy, int duration) { // 强制不执行滚动 super.startScroll(startX, startY, 0, 0, duration); } }; scrollerField.set(viewPager, customScroller); } catch (Exception e) { e.printStackTrace(); } }
在MainActivity里初始化ViewPager后调用这个方法就行:
disableViewPagerSwipe(viewPager);
方案三:检查ListView的Item布局(容易踩坑的点)
有时候根本不是ViewPager的问题,而是ListView的item布局里有可点击控件(比如Button、CheckBox,甚至设置了clickable="true"的TextView),这些控件会抢占item的焦点,导致OnItemClickListener无法触发。解决方法很简单,给item的根布局加上:
android:descendantFocusability="blocksDescendants"
这个属性会让根布局拦截所有子控件的焦点,保证ListView的item点击事件能正常响应。
内容的提问来源于stack exchange,提问作者Ophitect

