elasticity弹性动画类库浅析

前言

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 什么是View View 是 Android 中所有控件的基类。 View的位置参数 View 的位置由它的四个顶...
    acc8226阅读 1,149评论 0 7
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,413评论 25 707
  • 年底业绩冲刺,坐着飞机到处跑,忙得不亦乐乎,结果大姨妈推迟了十天也没注意到。参加完小组聚餐,回家测了一下,没想到双...
    落de雪阅读 591评论 0 50
  • 走在初秋的校园里,凉风习习。 抬头仰望,张开怀抱,天空海一样蓝, 广阔而又深远。 初秋的校园, 各种各样的树木, ...
    水月风荷阅读 932评论 7 7
  • (2017-11-01-周三 23:34:52) You never get bored. 你永远不会感到无聊。
    菜五阅读 165评论 0 0