0%

记当TextView.setSingleLine(true)时,滑动TextView的区域,导致的ViewPager不能滑动的问题

转载请注明出处:

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.RemotableViewMethod
public void setSingleLine(boolean singleLine) {
// Could be used, but may break backward compatibility.
// if (mSingleLine == singleLine) return;
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滑动冲突的两种解决方式

欢迎关注我的其它发布渠道