前言
文章是针对仿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