外层ScrollView,内嵌ListView,都是垂直方向。采用内部拦截法,实现ListView能滚动时则让ListView处理,当ListView滑到顶部或者底部不能滑动时让ScrollView处理
布局
上面有一段文本,中间是ListView,下面还有一段文本
<?xml version="1.0" encoding="utf-8"?>
<org.icegeneral.scroll.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sv"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="AAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA" />
<org.icegeneral.scroll.MyListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="500dp"
android:background="#888888">
</org.icegeneral.scroll.MyListView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB" />
</LinearLayout>
</org.icegeneral.scroll.MyScrollView>
MyScrollView
ACTION_DOWN必须让给ListView,ListView才能收到ACTION_MOVE,ListView才能判断自己是否还能滚动
第二点要注意的是:因为ACTION_DOWN让给ListView,那么ACTION_DOWN就无法进入ScrollView的onTouchEvent, 但是ScrollView的滚动需要在ACTION_DOWN阶段做一些准备,所以主动调用了一次
public class MyScrollView extends ScrollView {
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onTouchEvent(ev);
return false;
}
return true;
}
}
MyListView
public class MyListView extends ListView {
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
private float lastY;
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
getParent().getParent().requestDisallowInterceptTouchEvent(true);
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
if (lastY > ev.getY()) {
// 如果是向上滑动,且不能滑动了,则让ScrollView处理
if (!canScrollList(1)) {
getParent().getParent().requestDisallowInterceptTouchEvent(false);
return false;
}
} else if (ev.getY() > lastY) {
// 如果是向下滑动,且不能滑动了,则让ScrollView处理
if (!canScrollList(-1)) {
getParent().getParent().requestDisallowInterceptTouchEvent(false);
return false;
}
}
}
lastY = ev.getY();
return super.dispatchTouchEvent(ev);
}
}
Activity
protected void onCreate(Bundle savedInstanceState) {
....
scrollView.smoothScrollTo(0, 0);
}
ACTION_DOWN的特殊性
解释下为什么ListView的ACTION_UP无需调用
getParent().getParent().requestDisallowInterceptTouchEvent(false)
来看下ViewGroup源码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState(); //重点是这句
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
...
}
...
}
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; //移除FLAG_DISALLOW_INTERCEPT
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
所以如果是ACTION_DOWN,会调用resetTouchState(),移除FLAG_DISALLOW_INTERCEPT,所以ACTION_DOWN时,parent一定会进入onInterceptTouchEvent
其他
其实现在都用RecyclerView代替ListView,ScrollView嵌套RecyclerView时,对于手势已经支持得很好,不必自己处理冲突。这个在demo里也有写,做个对比
网友评论