外層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里也有寫,做個對比