Android NestedScrollingスライド衝突問題解決(1)-関連インタフェース
55525 ワード
Android NestedScrollingスライド衝突問題の解決
親Viewおよび子Viewがスライド可能で、スライド方向が一致している場合(CoordinatorLayoutにRecyclerViewまたはWebviewが組み込まれている場合など)、スライド競合の解決はAndroidが提供するNestedScrollingインタフェースに依存する必要があります.
NestedScrollingインタフェースは、
説明の便宜上、以下、
NestedScrollingChild
含まれるインタフェース:
および
したがって、1回のスライド操作に対して、
一般的な処理ロジックは以下の疑似コードでまとめることができる.
NestedScrollingParent
インタフェースを含める:
インタフェース呼び出し順序 NCは、MotionEventの処理時に、スライド要求を開始することを決定し、startNestedScroll を呼び出す. startNestedScrollを呼び出すと、親viewを上へ階層的に遍歴し、onStartNestedScrollインタフェースを呼び出す.trueを返すと、このviewは今回のnested scrollに連動したNPであり、遍歴を中断する.falseを返すと、ルートviewまで上層に遍歴し続けます.ルートビューを巡回しても連動NPが見つからない場合は、後続のスライドは連動できません.見つかったら、手順3に進みます. NCはNPのonNestedScrollAcceptedインタフェースを呼び出す. NPのonNestedScrollAcceptedインタフェースが呼び出され、スライドの初期動作が行われます. NCは、ユーザインタラクションがスライド距離を生成することを検出し、NPのonNestedPreScrollインターフェースを呼び出す. NPのonNestedPreScrollインタフェースが呼び出され、今回のスライドを前処理し、部分的なスライド距離を消費する(または消費しない). NCは残りのスライド距離を処理する. NCが残りのスライド距離を処理しない場合、dispatchNestedScrollが呼び出される. NPのonNestedScrollが呼び出され、残りのスライド距離の処理を継続するか否かが自己決定される. インタラクションのスライドが終了し、NCはstopNestedScrollを呼び出す. NPのonStopNestedScrollが呼び出されます.
親Viewおよび子Viewがスライド可能で、スライド方向が一致している場合(CoordinatorLayoutにRecyclerViewまたはWebviewが組み込まれている場合など)、スライド競合の解決はAndroidが提供するNestedScrollingインタフェースに依存する必要があります.
NestedScrollingインタフェースは、
NestedScrollingParent
とNestedScrollingChild
の2つの部分に分かれています.説明の便宜上、以下、
NestedScrollingParent
と略すとNP
であり、NestedScrollingChild
はNC
である.NestedScrollingChild
含まれるインタフェース:
public interface NestedScrollingChild {
/**
* View nested scroll
* @param enabled
*/
void setNestedScrollingEnabled(boolean enabled);
/**
* View nested scroll
* @return
*/
boolean isNestedScrollingEnabled();
/**
* axes nested scroll
* @param axes
* @return NestedScrollingParent
*/
boolean startNestedScroll(@ViewCompat.ScrollAxis int axes);
/**
* nested scroll
*/
void stopNestedScroll();
/**
* NestedScrollingParent
* @return
*/
boolean hasNestedScrollingParent();
/**
* nested scroll , , ( NestedScrollingParent )
* @param dx x
* @param dy y
* @param consumed ( ,consumed[0] x ,consumed[1] y )
* @param offsetInWindow , null。 , view (offsetInWindow[0] offsetInWindow[1] x y )
* @return
*/
boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
@Nullable int[] offsetInWindow);
/**
* View ( , NestedScrollingParent )
* @param dxConsumed x
* @param dyConsumed y
* @param dxUnconsumed x
* @param dyUnconsumed y
* @param offsetInWindow , null。 , view (offsetInWindow[0] offsetInWindow[1] x y )
* @return
*/
boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);
/**
* NestedScrollingChild fling ( NestedScrollingParent fling)
* @param velocityX x
* @param velocityY y
* @return fling
*/
boolean dispatchNestedPreFling(float velocityX, float velocityY);
/**
* fling
* @param velocityX x
* @param velocityY y
* @param consumed NestedScrollingChild fling
* @return NestedScrollingParent fling
*/
boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
}
NC
はNP
の子孫(必ずしも直接子Viewとは限らない)であり、連合スライドのリクエスト側でもあり、スライドによって生成された一連のMotionEvent
はこのViewで追跡処理され、一般にこのViewはonTouchEvent
でMotionEvent
の追跡分析に基づいてスライド要求を開始する.例えば、以下のRecyclerView
のうちonTouchEvent
の簡略化されたバージョン.@Override
public boolean onTouchEvent(MotionEvent e) {
final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
final boolean canScrollVertically = mLayout.canScrollVertically();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
boolean eventAddedToVelocityTracker = false;
final MotionEvent vtev = MotionEvent.obtain(e);
final int action = e.getActionMasked();
final int actionIndex = e.getActionIndex();
if (action == MotionEvent.ACTION_DOWN) {
mNestedOffsets[0] = mNestedOffsets[1] = 0;
}
vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);
switch (action) {
case MotionEvent.ACTION_DOWN: {
mScrollPointerId = e.getPointerId(0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
//
startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
} break;
case MotionEvent.ACTION_MOVE: {
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
// NP ( )
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
// NP
dx -= mScrollConsumed[0]; // NP X
dy -= mScrollConsumed[1]; // NP Y
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
//
if (mScrollState != SCROLL_STATE_DRAGGING) {
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
if (dx > 0) {
dx -= mTouchSlop;
} else {
dx += mTouchSlop;
}
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
//
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
} break;
case MotionEvent.ACTION_POINTER_UP: {
onPointerUp(e);
} break;
case MotionEvent.ACTION_UP: {
mVelocityTracker.addMovement(vtev);
eventAddedToVelocityTracker = true;
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
final float xvel = canScrollHorizontally
? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
final float yvel = canScrollVertically
? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
// fling ( , )
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetTouch();
} break;
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
} break;
}
if (!eventAddedToVelocityTracker) {
mVelocityTracker.addMovement(vtev);
}
vtev.recycle();
return true;
}
および
scrollByInternal
簡略化版:boolean scrollByInternal(int x, int y, MotionEvent ev) {
int unconsumedX = 0, unconsumedY = 0;
int consumedX = 0, consumedY = 0;
if (mAdapter != null) {
if (x != 0) {
// X
consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
// X
unconsumedX = x - consumedX;
}
if (y != 0) {
// Y
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
// Y
unconsumedY = y - consumedY;
}
}
// NP
if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
TYPE_TOUCH)) {
// Update the last touch co-ords, taking any scroll offset into account
mLastTouchX -= mScrollOffset[0];
mLastTouchY -= mScrollOffset[1];
if (ev != null) {
ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
}
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
//
return consumedX != 0 || consumedY != 0;
}
したがって、1回のスライド操作に対して、
NC
のインタフェース呼び出し順序は、startNestedScroll
-> dispatchNestedPreScroll
-> dispatchNestedScroll
-> stopNestedScroll
一般的な処理ロジックは以下の疑似コードでまとめることができる.
private int mLastX;
private int mLastY;
private int[] mConsumed = new int[2];
private int[] mOffsetInWindow = new int[2];
@Override
void onTouchEvent(MotionEvent event) {
int eventX = (int) event.getRawX();
int eventY = (int) event.getRawY();
int action = event.getAction();
int deltaX = eventX - mLastX;
int deltaY = eventY - mLastY;
switch (action) {
case MotionEvent.ACTION_DOWN:
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally()) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically()) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis);
break;
case MotionEvent.ACTION_MOVE:
if (dispatchNestedPreScroll(deltaX, deltaY, mConsumed, mOffsetInWindow)) {
deltaX -= mConsumed[0];
deltaY -= mConsumed[1];
}
int internalScrolledX = internalScrollByX(deltaX);
int internalScrolledY = internalScrollByY(deltaY);
deltaX -= internalScrolledX;
deltaY -= internalScrolledY;
if (deltaX != 0 || deltaY != 0) {
dispatchNestedScroll(mConsumed[0] + internalScrolledX, mConsumed[1] + internalScrolledY, deltaX, deltaY, mOffsetInWindow);
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
stopNestedScroll();
break;
}
mLastX = eventX;
mLastY = eventY;
}
/**
* X
* @param deltaX
* @return
*/
abstract int internalScrollByX(int deltaX);
/**
* Y
* @param deltaY
* @return
*/
abstract int internalScrollByY(int deltaY);
/**
*
* @return
*/
abstract boolean canScrollHorizontally();
/**
*
* @return
*/
abstract boolean canScrollVertically();
NestedScrollingParent
インタフェースを含める:
public interface NestedScrollingParent {
/**
* NP (NestedScrollingChild.startNestedScroll)
* @param child NP view view
* @param target NP view
* @param axes
* @return
*/
boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ViewCompat.ScrollAxis int axes);
/**
* (onStartNestedScroll true ), NestedScrollingParent
* @param child NP view view
* @param target NP view
* @param axes
*/
void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ViewCompat.ScrollAxis int axes);
/**
* nested scroll (NestedScrollingChild stopNestedScroll)
* @param target NP view
*/
void onStopNestedScroll(@NonNull View target);
/**
* NestedScrollingChild ,
* @param target NP view
* @param dx x
* @param dy y
* @param consumed ,
*/
void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);
/**
* NestedScrollingChild
* @param target NP view
* @param dxConsumed x
* @param dyConsumed y
* @param dxUnconsumed x
* @param dyUnconsumed y
*/
void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed);
/**
* NestedScrollingChild fling
* @param target NP view
* @param velocityX x fling
* @param velocityY y fling
* @return fling
*/
boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY);
/**
* fling
* @param target NP view
* @param velocityX x fling
* @param velocityY y fling
* @param consumed NestedScrollingChild fling
* @return fling
*/
boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed);
/**
*
* @return
*/
@ViewCompat.ScrollAxis
int getNestedScrollAxes();
}
インタフェース呼び出し順序