转载请注明出处:
http://sunxiaodou.com/2017/09/14/记当TextView.setSingleLine(true)时,滑动TextView的区域,导致的ViewPager不能滑动的问题/
本文来自【孫小逗的博客】
概述
问题背景:在ViewPager中包含两个Fragment,其中一个Fragment中有多个TextView,TextView需要开启跑马灯,在XML文件中设置了:android:singleLine="true"
导致滑动TextView的区域时,ViewPager滑动失效。
探索
探索一
首先想到的是滑动冲突,去查看了TextView.setSingleLine(true)
时执行了什么动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 @android .view.RemotableViewMethodpublic void setSingleLine (boolean singleLine) { setInputTypeSingleLine(singleLine); applySingleLine(singleLine, true , true ); } private void setInputTypeSingleLine (boolean singleLine) { if (mEditor != null && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { if (singleLine) { mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; } else { mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; } } } private void applySingleLine (boolean singleLine, boolean applyTransformation, boolean changeMaxLines) { mSingleLine = singleLine; if (singleLine) { setLines(1 ); setHorizontallyScrolling(true ); if (applyTransformation) { setTransformationMethod(SingleLineTransformationMethod.getInstance()); } } else { if (changeMaxLines) { setMaxLines(Integer.MAX_VALUE); } setHorizontallyScrolling(false ); if (applyTransformation) { setTransformationMethod(null ); } } } public void setHorizontallyScrolling (boolean whether) { if (mHorizontallyScrolling != whether) { mHorizontallyScrolling = whether; if (mLayout != null ) { nullLayouts(); requestLayout(); invalidate(); } } }
可以在applySingleLine
方法中看到setLines(1);setHorizontallyScrolling(true);
,setHorizontallyScrolling
方法会设置TextView是否可以横向滚动,关键是改变了mHorizontallyScrolling
成员变量的值。
然后就写了个MyViewPager:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class MyViewPager extends ViewPager { private int mDownX; private int mSlop; public MyViewPager (Context context) { this (context, null ); } public MyViewPager (Context context, AttributeSet attrs) { super (context, attrs); mSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override public boolean onInterceptTouchEvent (MotionEvent ev) { int actionMasked = ev.getActionMasked(); switch (actionMasked) { case MotionEvent.ACTION_DOWN: mDownX = (int ) ev.getX(); break ; case MotionEvent.ACTION_MOVE: int dx = (int ) (ev.getX() - mDownX); if (Math.abs(dx) >= mSlop) { return true ; } break ; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mDownX = 0 ; break ; } return super .onInterceptTouchEvent(ev); } }
覆写了事件拦截方法onInterceptTouchEvent(MotionEvent ev)
方法,在MotionEvent.ACTION_MOVE
里根据横向滑动是否需要拦截事件。
然而现实是残酷的、无情的,失败。
探索二
众所周知,TextView要实现跑马灯需要获取到焦点,然后猜测滑动TextView区域的时ViewPager不滑动的原因是因为ViewPager没有获取到焦点所致。
所以在探索一的基础上加了一句代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class MyViewPager extends ViewPager { private int mDownX; private int mSlop; public MyViewPager (Context context) { this (context, null ); } public MyViewPager (Context context, AttributeSet attrs) { super (context, attrs); mSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override public boolean onInterceptTouchEvent (MotionEvent ev) { int actionMasked = ev.getActionMasked(); switch (actionMasked) { case MotionEvent.ACTION_DOWN: mDownX = (int ) ev.getX(); break ; case MotionEvent.ACTION_MOVE: int dx = (int ) (ev.getX() - mDownX); if (Math.abs(dx) >= mSlop) { requestFocusFromTouch(); return true ; } break ; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mDownX = 0 ; break ; } return super .onInterceptTouchEvent(ev); } }
注意第27行的代码requestFocusFromTouch();
,拦截事件的同时使ViewPager获取到焦点。
奇迹发生,成功。
这个问题虽然解决了,却也引入了另一个问题:另一个Fragment中有一个横向滑动的RecyclerView,由于ViewPager的横向滑动事件拦截,导致RecyclerView的横向滑动失效。
只好重写一个MyRecyclerView:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 public class MyRecyclerView extends RecyclerView { private int mDownX; private int mDownY; private int mSlop; public MyRecyclerView (Context context) { this (context, null ); } public MyRecyclerView (Context context, @Nullable AttributeSet attrs) { this (context, attrs, 0 ); } public MyRecyclerView (Context context, @Nullable AttributeSet attrs, int defStyle) { super (context, attrs, defStyle); mSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override public boolean dispatchTouchEvent (MotionEvent ev) { int actionMasked = ev.getActionMasked(); switch (actionMasked) { case MotionEvent.ACTION_DOWN: mDownX = (int ) ev.getX(); mDownY = (int ) ev.getY(); getParent().requestDisallowInterceptTouchEvent(true ); break ; case MotionEvent.ACTION_MOVE: int dx = (int ) (ev.getX() - mDownX); int dy = (int ) (ev.getY() - mDownY); if (Math.abs(dy) >= mSlop && Math.abs(dy) > Math.abs(dx)) { getParent().requestDisallowInterceptTouchEvent(false ); break ; } Adapter adapter = getAdapter(); if (adapter == null ) { break ; } LayoutManager manager = getLayoutManager(); if (manager == null ) { break ; } int itemCount = adapter.getItemCount(); if (itemCount == 0 ) { break ; } if (manager instanceof LinearLayoutManager) { if (dx > 0 && dx <= mSlop) { int firstVisiblePosition = ((LinearLayoutManager) manager).findFirstCompletelyVisibleItemPosition(); if (firstVisiblePosition == 0 ) { View firstView = manager.findViewByPosition(firstVisiblePosition); if (firstView.getLeft() == 0 ) { getParent().requestDisallowInterceptTouchEvent(false ); } } } else if (dx < 0 && dx >= -mSlop) { int lastVisiblePosition = ((LinearLayoutManager) manager).findLastCompletelyVisibleItemPosition(); if (lastVisiblePosition == itemCount - 1 ) { View lastView = manager.findViewByPosition(lastVisiblePosition); if (lastView.getRight() == this .getWidth()) { getParent().requestDisallowInterceptTouchEvent(false ); } } } } break ; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mDownX = 0 ; mDownY = 0 ; getParent().requestDisallowInterceptTouchEvent(false ); break ; } return super .dispatchTouchEvent(ev); } }
这个横向滑动的RecyclerView是作为一个Item在一个竖向滑动的RecyclerView中,所以代码中加了对竖向滑动的处理。
有关滑动冲突的解决办法可以参考另一篇文章:View滑动冲突的两种解决方式