滑動沖突
在開發android中, 滑動沖突是一常見的事件沖突。
列如:在scrollView中嵌套listView 或 recyclerView。
由于這2種視圖都可以滑動,就會導致父視圖攔截了滑動事件,從而導致子視圖獲取不到滑動事件。
如何解決
第一種: 清晰的了解android的事件分發機制,在各個view的攔截事件中做相應的處理。
第二種:android在Lollipop之后為滑動機制提供了NestedScrolling特性,可以使用NestedScrollingChild和NestedScrollingParent 來解決滑動沖突。
今天我們主要來講第二種實現方式。
我們先看下他們的方法對應的關系:
子view | 父view |
---|---|
startNestedScroll | onStartNestedScroll、onNestedScrollAccepted |
dispatchNestedPreScroll | onNestedPreScroll |
dispatchNestedScroll | onNestedScroll |
stopNestedScroll | onStopNestedScroll |
一般是子View發起,父view接受回調。
首先,我們先了解做為子view的RecyclerView是如何實現和調用NestedScrollingChild接口中的方法。
我們來看看reyclerView的onTouch()方法:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
.......
@Override
public boolean onTouchEvent(MotionEvent e) {
.......
if (action == MotionEvent.ACTION_DOWN) {
mNestedOffsets[0] = mNestedOffsets[1] = 0;
}
vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);
switch (action) {
.......
case MotionEvent.ACTION_MOVE: {
.......
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
//在recyclerView進行滑動之前,會先調用dispatchNestedPreScroll來判斷父view是否需要處理
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
// 這里主要對mScrollOffset數組賦值, mScrollConsumed[0]代表父View 在x坐標消耗的滑動
//的數值,mScrollConsumed[1]表示父View在Y軸消耗的滑動數值
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
.......
}
.......
return true;
}
}
.......
我們發現recyclerView在滑動之前會調用dispatchNestedPreScroll方法來判斷是否有實現NestedScrollingParent 的父View處理。有的話就會執行里方法,減去父View已經消耗的滑動值。
OK 現在我們了解這些,可以正式進入我們的主題了。
如何快速的解決在scrollView中嵌套RecyclerVeiw的事件沖突?
我們可以看到,recylcerView已經實現了NestedScrollingChild 接口, 所以我們只需要自定義scrollView。我們只要讓scrollVeiw實現NestedScrollingParent,在recyclerView發起調用時做相應的操作就可以了。
看下我是如何實現的:
package eebochina.com.testtechniques.nestedScroll;
import android.content.Context;
import android.support.v4.view.NestedScrollingParent;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;
/**
* Created by User on 2017/1/17.
*/
public class NestedParent extends ScrollView implements NestedScrollingParent {
//方便測試先固定。
private int maxHeight = 464;
private RecyclerView mRecyclerView;
public NestedParent(Context context) {
super(context);
}
public NestedParent(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NestedParent(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setMaxHeight(int maxHeight) {
this.maxHeight = maxHeight;
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return super.onStartNestedScroll(child, target, nestedScrollAxes);
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
super.onNestedScrollAccepted(child, target, axes);
}
@Override
public void onStopNestedScroll(View target) {
super.onStopNestedScroll(target);
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
//這里可以不處理, 因為srollView內部已經重寫了改方法,我們可以直接調用
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return super.onNestedFling(target, velocityX, velocityY, consumed);;
}
//返回true代表父view消耗滑動速度,子View將不會滑動
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
if (null == mRecyclerView) mRecyclerView = (RecyclerView) target;
if (mRecyclerView.computeVerticalScrollOffset() != 0) {
return false;
}
this.fling((int) velocityY);
return true;
}
//對應子view 的dispatchNestedPreScroll方法, 最后一個數組代表消耗的滾動量,下標0代表x軸,下標1代表y軸
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
//判斷是否滾動到最大值
if ( dy >= 0 && this.getScrollY() < maxHeight) {
if (null == mRecyclerView) mRecyclerView = (RecyclerView) target;
//計算RecyclerView的偏移量, 等于0的時候說明recyclerView沒有滑動,否則應該交給recyclerView自己處理
if (mRecyclerView.computeVerticalScrollOffset() != 0) return;
this.smoothScrollBy(dx, dy);
consumed[1] = dy; //consumed[1]賦值為 dy ,代表父類已經消耗了改滾動。
}
}
}
代碼非常少,主要是實現了NestedScrollingParent方法,并在滑動和滾動的方法進行處理。
主要邏輯也進行了相應的注釋,最大值為了方便測試現也寫了一個固定值,也預留了賦值方法。
我們看下效果圖:
完整代碼地址:
https://github.com/hu5080126/SimpleExample/tree/master/nestedScrollView/src
到這里已經寫完了, 希望可以幫到大家。 ( 如果能去了解事件的分發機制那肯定是最好的)
大家有什么好的建議,歡迎提出。
(只支持5.0+的版本)