前言
文章是針對仿miui listView 彈性動畫類庫的分析,類庫elasticity,elasticity 能讓任何滾動View實現彈性動畫,并不僅限于列表,非常強大;主要通過修改整塊View(如RecyclerView)的Scale來實現,以及松開指頭的回彈動畫;個人認為動畫關鍵點對于滑動過程中沒有滑動到最大距離往回滑動時的處理;以及松開手指回彈的實現
ElasticityBounceEffectBase
- IdleState 初始滑動事件處理者,用來判斷是否滿足滑動條件,滿足則將事件傳遞給OverscrollingState處理
- OverScrollingState 實際拉伸view處理者,手指松開時將事件傳遞給BounceBackState
- BounceBackState 彈性動畫處理者,動畫處理完畢,將事件返回給IdleState
整個事件處理類似與android的事件傳遞
新知識
在計算與上次滑動的距離時 可以用 getHistorySize() event.getHistorical..
官方的解釋是:returns the number of historical points in this event.these are movements that have occurred between this event and the previous event.this only applies to action_move events-- all other actions will have a size of 0
來獲得歷史的大小值,它可以返回當前事件可用的運動位置的數目,僅可以用在 Action_Move 中
觸摸RecyclerView
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
return mCurrentState.handleMoveTouchEvent(event);
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
return mCurrentState.handleUpOrCancelTouchEvent(event);
}
return false;
}
首先經過IdleState 的handleMoveTouchEvent
@Override
public boolean handleMoveTouchEvent(MotionEvent event) {
final View view = mViewAdapter.getView();
//判斷滑動方向是否正確
if (!mMoveAttr.init(view, event)) {
return false;
}
// 是否是開始位置并且向下滑 或者 是否是結束位置 向上滑動
if ((mViewAdapter.isInAbsoluteStart() && mMoveAttr.mDir) ||
(mViewAdapter.isInAbsoluteEnd() && !mMoveAttr.mDir)) {
// Save initial over-scroll attributes for future reference.
mOverScrollStartAttr.mPointerId = event.getPointerId(0);
mOverScrollStartAttr.mAbsOffset = mMoveAttr.mAbsOffset;
mOverScrollStartAttr.mDir = mMoveAttr.mDir;
issueStateTransition(mOverScrollingState);
return mOverScrollingState.handleMoveTouchEvent(event);
}
return false;
}
這段代碼的大致意思:
-先判斷滑動的方向(垂直或者水平)是否是是否正確
-如果滑動的條件滿足,記錄手指ID,mAbsOffset,和方向
protected void issueStateTransition(IDecoratorState state) {
IDecoratorState oldState = mCurrentState;
mCurrentState = state;
mCurrentState.handleEntryTransition(oldState);
}
經過IdleState 的issueStateTransition 切換mCurrentState成OverScrollingState,這個類是真正做滑動的處理類,然后調用OverScrollingState的handleMoveTouchEvent
public boolean handleMoveTouchEvent(MotionEvent event) {
// Switching 'pointers' (e.g. fingers) on-the-fly isn't supported -- abort over-scroll
// smoothly using the default bounce-back animation in this case.
//翻譯下,當手指切換的時候不支持over-scroll,使用默認的反彈動畫結束
if (mOverScrollStartAttr.mPointerId != event.getPointerId(0)) {
issueStateTransition(mBounceBackState);
return true;
}
final View view = mViewAdapter.getView();
if (!mMoveAttr.init(view, event)) {
// Keep intercepting the touch event as long as we're still over-scrolling...
return true;
}
float deltaOffset = mMoveAttr.mDeltaOffset / (mMoveAttr.mDir == mOverScrollStartAttr.mDir ? mTouchDragRatioFwd : mTouchDragRatioBck);
float newOffset = mMoveAttr.mAbsOffset + deltaOffset;
// If moved in counter direction onto a potential under-scroll state -- don't. Instead, abort
// over-scrolling abruptly, thus returning control to which-ever touch handlers there
// are waiting (e.g. regular scroller handlers).
if ((mOverScrollStartAttr.mDir && !mMoveAttr.mDir && (newOffset <= mOverScrollStartAttr.mAbsOffset)) ||
(!mOverScrollStartAttr.mDir && mMoveAttr.mDir && (newOffset >= mOverScrollStartAttr.mAbsOffset))) {
translateViewAndEvent(view, mOverScrollStartAttr.mDir, mOverScrollStartAttr.mAbsOffset, event);
mUpdateListener.onOverScrollUpdate(ElasticityBounceEffectBase.this, mCurrDragState, 0);
issueStateTransition(mIdleState);
return true;
}
if (view.getParent() != null) {
view.getParent().requestDisallowInterceptTouchEvent(true);
}
long dt = event.getEventTime() - event.getHistoricalEventTime(0);
if (dt > 0) { // Sometimes (though rarely) dt==0 cause originally timing is in nanos, but is presented in millis.
mVelocity = deltaOffset / dt;
}
translateView(view, mOverScrollStartAttr.mDir, newOffset);
mUpdateListener.onOverScrollUpdate(ElasticityBounceEffectBase.this, mCurrDragState, newOffset);
return true;
}
上面這段代碼的意思先經過IdleState對MotionEvent的處理,然后交給OverScrollScrollState處理,并且后面的觸摸事件都交給了OverScrollScrollState
-在滑動的過程中依次判斷手指是否有切換或者在滑動過程飛指,如果有使用默認的彈性動畫將View的Scale還原
-滑動過程中如果當前方向跟開始方向相反,并且view滑動到初使狀態,還在反方向滑動滑動則將MotionEvent 交給IdleState處理
-否則進入下一步,禁用父類的滑動,進入translateView
@Override
protected void translateView(View view, boolean dir, float offset) {
Log.d("wxy-motion", String.format("translateView setTag %s", offset));
setViewOffset(view, offset);
view.setPivotX(0.f);
if (dir) {
Log.d("wxy-motion", String.format("translateView setPivotY %s", 0));
view.setPivotY(0.f);
} else {
view.setPivotY(view.getMeasuredHeight());
Log.d("wxy-motion", String.format("translateView setPivotY %s", view.getMeasuredHeight()));
}
view.setScaleY(Math.min(getMaxScaleFactor(), (1.f + Math.abs(offset) / view.getWidth())));
view.postInvalidate();
// view.setTranslationY(offset);
}
上面是垂直方向的拉伸,更改View的 ScaleY