前言
文章是針對仿miui listView 彈性動畫類庫的分析归榕,類庫elasticity法挨,elasticity 能讓任何滾動View實現(xiàn)彈性動畫码泞,并不僅限于列表兄旬,非常強(qiáng)大;主要通過修改整塊View(如RecyclerView)的Scale來實現(xiàn)余寥,以及松開指頭的回彈動畫领铐;個人認(rèn)為動畫關(guān)鍵點對于滑動過程中沒有滑動到最大距離往回滑動時的處理;以及松開手指回彈的實現(xiàn)
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
來獲得歷史的大小值,它可以返回當(dāng)前事件可用的運動位置的數(shù)目初肉,僅可以用在 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;
}
首先經(jīng)過IdleState 的handleMoveTouchEvent
@Override
public boolean handleMoveTouchEvent(MotionEvent event) {
final View view = mViewAdapter.getView();
//判斷滑動方向是否正確
if (!mMoveAttr.init(view, event)) {
return false;
}
// 是否是開始位置并且向下滑 或者 是否是結(jié)束位置 向上滑動
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);
}
經(jīng)過IdleState 的issueStateTransition 切換mCurrentState成OverScrollingState,這個類是真正做滑動的處理類泽腮,然后調(diào)用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.
//翻譯下,當(dāng)手指切換的時候不支持over-scroll,使用默認(rèn)的反彈動畫結(jié)束
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;
}
上面這段代碼的意思先經(jīng)過IdleState對MotionEvent的處理玖姑,然后交給OverScrollScrollState處理端朵,并且后面的觸摸事件都交給了OverScrollScrollState
-在滑動的過程中依次判斷手指是否有切換或者在滑動過程飛指,如果有使用默認(rèn)的彈性動畫將View的Scale還原
-滑動過程中如果當(dāng)前方向跟開始方向相反承疲,并且view滑動到初使?fàn)顟B(tài)邻耕,還在反方向滑動滑動則將MotionEvent 交給IdleState處理
-否則進(jìn)入下一步,禁用父類的滑動燕鸽,進(jìn)入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