zoukankan      html  css  js  c++  java
  • Rebound动画框架简单介绍

    Rebound动画框架简单介绍

    Android菜鸟一枚,有不对的地方希望大家指出,谢谢。
    最近在接手了一个老项目,发现里面动画框架用的是facebook中的Rebound框架,由于以前没听说过,放假时闲得蛋痛,看看了源码,就顺手写这一篇吧。
    写了一个小Demo,具体效果如下:
    这是图片啊
    代码很简单,这是xml布局:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context=".MainActivity" >
    
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/image"
            android:src="@drawable/a1" />
    
    </RelativeLayout>
    

    这是MainActivity:

    package com.micro.mytest_button;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.Menu;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.View.OnTouchListener;
    import android.widget.ImageView;
    
    import com.facebook.rebound.Spring;
    import com.facebook.rebound.SpringListener;
    import com.facebook.rebound.SpringSystem;
    import com.nineoldandroids.view.ViewHelper;
    
    public class MainActivity extends Activity {
    
        private ImageView image;
        private Spring spring;
    
        private final float mScale = 1.0f;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            image = (ImageView) findViewById(R.id.image);
    
            SpringSystem springSystem = SpringSystem.create();
            spring = springSystem.createSpring();       
            spring.addListener(new SpringListener() {
    
                @Override
                public void onSpringUpdate(Spring spring) {
                    float value = (float) spring.getCurrentValue();
                    float scale = 1f - (value * mScale);
    
                    System.out.println("the value is " + value + "--the scale is --" + scale);
    
                    ViewHelper.setScaleX(image, scale);
                    ViewHelper.setScaleY(image, scale);
                }
    
                @Override
                public void onSpringEndStateChange(Spring spring) {
                }
    
                @Override
                public void onSpringAtRest(Spring spring) {
                }
    
                @Override
                public void onSpringActivate(Spring spring) {
                }
            });
    
            image.setOnTouchListener(new OnTouchListener() {
    
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        spring.setEndValue(1.0f);
                        break;
    
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:
                        spring.setEndValue(0.0f);
                        break;
                    }
    
                    return true;
                }
            });
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }
    
    }
    

    现在主要是来分析下Rebound的源码,看看里面到底怎么走的,用法很简单(这是最简单的用法),就三句话:

    SpringSystem springSystem = SpringSystem.create();
    spring = springSystem.createSpring();
    spring.addListener(new SpringListener() {}
    • 1

    主要是创建了三个对象,SpringSystem/spring/SpringListener,对于动画的效果,就是这三个玩意搞出来的。具体的模式如下:
    SpringSystem:继承自BaseSpringSystem,其中包含了Spring对象引用的容器,控制Spring对象的操作,存在一个SpringLooper(是一个抽象方法,只有start()/stop()方法),这也是facebook自定义的类,与android.os.Looper无关,只是模拟了android.os.Looper的方法,内存start(),end()方法。其构造方法:

     public static SpringSystem create() {
        return new SpringSystem(AndroidSpringLooperFactory.createSpringLooper());
      }
    • 1

    打开SpringSystem(SpringLooper sl)源码:

      public BaseSpringSystem(SpringLooper springLooper) {
        if (springLooper == null) {
          throw new IllegalArgumentException("springLooper is required");
        }
        mSpringLooper = springLooper;
        mSpringLooper.setSpringSystem(this);
      }
    • 1

    现在再看AndroidSpringLooperFactory.createSpringLooper()源码:

     public static SpringLooper createSpringLooper() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
          return ChoreographerAndroidSpringLooper.create();
        } else {
          return LegacyAndroidSpringLooper.create();
        }
      }
    • 1

    可以看到为了兼容JDK,高低版本创建的SpringLooper容器不经相同,这个不是考虑的重点。

    现在来看第二句:

    spring = springSystem.createSpring();
    • 1

    翻开SpringSytem.createSpring()源码:

     public Spring createSpring() {
        Spring spring = new Spring(this);
        registerSpring(spring);
        return spring;
      }
    • 1
    • 2

    可以看到Spring类是一个对立的Java类,持有SpringSystem的引用,来看看Spring(SpringSystem ss)构造函数:

      Spring(BaseSpringSystem springSystem) {
        if (springSystem == null) {
          throw new IllegalArgumentException("Spring cannot be created outside of a BaseSpringSystem");
        }
        mSpringSystem = springSystem;
        mId = "spring:" + ID++;
        setSpringConfig(SpringConfig.defaultConfig);
      }

    再看setSpringConfig(SpringConfig.defaultConfig)源码:

    public Spring setSpringConfig(SpringConfig springConfig) {
        if (springConfig == null) {
          throw new IllegalArgumentException("springConfig is required");
        }
        mSpringConfig = springConfig;
        return this;
      }
    • 1

    也只是初始化一些参数,并没有有我们想要的源码(这里的意思就是我点击图片时,它为什么会有动画的意思),再看第三个方法吧:

    spring.addListener(new SpringListener() {}
    • 1

    添加监听器,用的多的人就会有感觉,这个也不会有关键代码可以使我们图片有缩放效果,那么问题来了,看到facebook写的三句话,我们并没有找到动画缩放的想过函数啊,是不是有配置文件或者静态/动态代码块呢??找了很久,没有啊。那只有接下来再看image的代码了,在看到Image的绑定的onTouch事件上,看到了这句话:

        spring.setEndValue(1.0f);
    • 1

    啥也不说,进去看看再说吧:

      public Spring setEndValue(double endValue) {
        if (mEndValue == endValue && isAtRest()) {
          return this;
        }
        mStartValue = getCurrentValue();
        mEndValue = endValue;
        mSpringSystem.activateSpring(this.getId());
        for (SpringListener listener : mListeners) {
          listener.onSpringEndStateChange(this);
        }
        return this;
      }
    • 1

    其他没啥好看的,看到了这句话:

     mSpringSystem.activateSpring(this.getId());
    • 1

    活了二十几年,立马感觉这句话有问题,进入看看:

      void activateSpring(String springId) {
        Spring spring = mSpringRegistry.get(springId);
        if (spring == null) {
          throw new IllegalArgumentException("springId " + springId + " does not reference a registered spring");
        }
        mActiveSprings.add(spring);
        if (getIsIdle()) {
          mIdle = false;
          mSpringLooper.start();
        }
      }

    解释一下,刚才说的SpringSystem包含了Spring对象的引用,mSpringRegistry.get(springId)是找到SpringSystem中注册的Spring, mActiveSprings.add(spring)是将该spring添加到活动的spring队列中,那现在就看看这句话吧: mSpringLooper.start(),刚才说SpringLooper是一个抽象类,那好随便找个其子类看看里面的方法吧,

    private static class LegacyAndroidSpringLooper extends SpringLooper {}
    • 1

    刚才的兼容性代码中,JELLY_BEAN(也就是4.1之前的代码),那我们就看看mSpringLooper.start()是个什么鬼了吧:

    private static class LegacyAndroidSpringLooper extends SpringLooper {
    
        private final Handler mHandler;
        private final Runnable mLooperRunnable;
        private boolean mStarted;
        private long mLastTime;
    
        /**
         * @return an Android spring looper using a new {@link Handler} instance
         */
        public static SpringLooper create() {
          return new LegacyAndroidSpringLooper(new Handler());
        }
    
        public LegacyAndroidSpringLooper(Handler handler) {
          mHandler = handler;
          mLooperRunnable = new Runnable() {
            @Override
            public void run() {
              if (!mStarted || mSpringSystem == null) {
                return;
              }
              long currentTime = SystemClock.uptimeMillis();
              mSpringSystem.loop(currentTime - mLastTime);
              mHandler.post(mLooperRunnable);
            }
          };
        }
    
        @Override
        public void start() {
          if (mStarted) {
            return;
          }
          mStarted = true;
          mLastTime = SystemClock.uptimeMillis();
          mHandler.removeCallbacks(mLooperRunnable);
          mHandler.post(mLooperRunnable);
        }
    
        @Override
        public void stop() {
          mStarted = false;
          mHandler.removeCallbacks(mLooperRunnable);
        }
      }

    对,没错我们看到了 mHandler.post(mLooperRunnable);金典的方法啊,那我们看看这mLooperRunnable方法里面在干嘛吧,

     long currentTime = SystemClock.uptimeMillis();
     mSpringSystem.loop(currentTime - mLastTime);

    废话不多说,立马去找我们想要看到的SpringSystem.loop方法了:

    public void loop(double ellapsedMillis) {
        for (SpringSystemListener listener : mListeners) {
          listener.onBeforeIntegrate(this);
        }
        advance(ellapsedMillis);
        if (mActiveSprings.isEmpty()) {
          mIdle = true;
        }
        for (SpringSystemListener listener : mListeners) {
          listener.onAfterIntegrate(this);
        }
        if (mIdle) {
          mSpringLooper.stop();
        }
      }
    

    对了,我们好像看到了advance(ellapsedMillis)啊,这个看起来比较吊,不说了进去看看再说啊:

     void advance(double deltaTime) {
        for (Spring spring : mActiveSprings) {
          // advance time in seconds
          if (spring.systemShouldAdvance()) {
            spring.advance(deltaTime / 1000.0);
          } else {
            mActiveSprings.remove(spring);
          }
        }
      }

    尼玛啊,我看到了

     spring.advance(deltaTime / 1000.0);

    哈哈,我感觉快要找到了,不说,赶快进去找:

    void advance(double realDeltaTime) {
    
        boolean isAtRest = isAtRest();
    
        if (isAtRest && mWasAtRest) {
          /* begin debug
          Log.d(TAG, "bailing out because we are at rest:" + getName());
          end debug */
          return;
        }
    
        // clamp the amount of realTime to simulate to avoid stuttering in the UI. We should be able
        // to catch up in a subsequent advance if necessary.
        double adjustedDeltaTime = realDeltaTime;
        if (realDeltaTime > MAX_DELTA_TIME_SEC) {
          adjustedDeltaTime = MAX_DELTA_TIME_SEC;
        }
    
        /* begin debug
        long startTime = System.currentTimeMillis();
        int iterations = 0;
        end debug */
    
        mTimeAccumulator += adjustedDeltaTime;
    
        double tension = mSpringConfig.tension;
        double friction = mSpringConfig.friction;
    
        double position = mCurrentState.position;
        double velocity = mCurrentState.velocity;
        double tempPosition = mTempState.position;
        double tempVelocity = mTempState.velocity;
    
        double aVelocity, aAcceleration;
        double bVelocity, bAcceleration;
        double cVelocity, cAcceleration;
        double dVelocity, dAcceleration;
    
        double dxdt, dvdt;
    
        // iterate over the true time
        while (mTimeAccumulator >= SOLVER_TIMESTEP_SEC) {
          /* begin debug
          iterations++;
          end debug */
          mTimeAccumulator -= SOLVER_TIMESTEP_SEC;
    
          if (mTimeAccumulator < SOLVER_TIMESTEP_SEC) {
            // This will be the last iteration. Remember the previous state in case we need to
            // interpolate
            mPreviousState.position = position;
            mPreviousState.velocity = velocity;
          }
    
          // Perform an RK4 integration to provide better detection of the acceleration curve via
          // sampling of Euler integrations at 4 intervals feeding each derivative into the calculation
          // of the next and taking a weighted sum of the 4 derivatives as the final output.
    
          // This math was inlined since it made for big performance improvements when advancing several
          // springs in one pass of the BaseSpringSystem.
    
          // The initial derivative is based on the current velocity and the calculated acceleration
          aVelocity = velocity;
          aAcceleration = (tension * (mEndValue - tempPosition)) - friction * velocity;
    
          // Calculate the next derivatives starting with the last derivative and integrating over the
          // timestep
          tempPosition = position + aVelocity * SOLVER_TIMESTEP_SEC * 0.5;
          tempVelocity = velocity + aAcceleration * SOLVER_TIMESTEP_SEC * 0.5;
          bVelocity = tempVelocity;
          bAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity;
    
          tempPosition = position + bVelocity * SOLVER_TIMESTEP_SEC * 0.5;
          tempVelocity = velocity + bAcceleration * SOLVER_TIMESTEP_SEC * 0.5;
          cVelocity = tempVelocity;
          cAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity;
    
          tempPosition = position + cVelocity * SOLVER_TIMESTEP_SEC;
          tempVelocity = velocity + cAcceleration * SOLVER_TIMESTEP_SEC;
          dVelocity = tempVelocity;
          dAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity;
    
          // Take the weighted sum of the 4 derivatives as the final output.
          dxdt = 1.0/6.0 * (aVelocity + 2.0 * (bVelocity + cVelocity) + dVelocity);
          dvdt = 1.0/6.0 * (aAcceleration + 2.0 * (bAcceleration + cAcceleration) + dAcceleration);
    
          position += dxdt * SOLVER_TIMESTEP_SEC;
          velocity += dvdt * SOLVER_TIMESTEP_SEC;
        }
    
        mTempState.position = tempPosition;
        mTempState.velocity = tempVelocity;
    
        mCurrentState.position = position;
        mCurrentState.velocity = velocity;
    
        if (mTimeAccumulator > 0) {
          interpolate(mTimeAccumulator / SOLVER_TIMESTEP_SEC);
        }
    
        // End the spring immediately if it is overshooting and overshoot clamping is enabled.
        // Also make sure that if the spring was considered within a resting threshold that it's now
        // snapped to its end value.
        if (isAtRest() || (mOvershootClampingEnabled && isOvershooting())) {
          // Don't call setCurrentValue because that forces a call to onSpringUpdate
          mStartValue = mEndValue;
          mCurrentState.position = mEndValue;
          setVelocity(0);
          isAtRest = true;
        }
    
        /* begin debug
        long endTime = System.currentTimeMillis();
        long elapsedMillis = endTime - startTime;
        Log.d(TAG,
            "iterations:" + iterations +
                " iterationTime:" + elapsedMillis +
                " position:" + mCurrentState.position +
                " velocity:" + mCurrentState.velocity +
                " realDeltaTime:" + realDeltaTime +
                " adjustedDeltaTime:" + adjustedDeltaTime +
                " isAtRest:" + isAtRest +
                " wasAtRest:" + mWasAtRest);
        end debug */
    
        // NB: do these checks outside the loop so all listeners are properly notified of the state
        //     transition
        boolean notifyActivate = false;
        if (mWasAtRest) {
          mWasAtRest = false;
          notifyActivate = true;
        }
        boolean notifyAtRest = false;
        if (isAtRest) {
          mWasAtRest = true;
          notifyAtRest = true;
        }
        for (SpringListener listener : mListeners) {
          // starting to move
          if (notifyActivate) {
            listener.onSpringActivate(this);
          }
    
          // updated
          listener.onSpringUpdate(this);
    
          // coming to rest
          if (notifyAtRest) {
            listener.onSpringAtRest(this);
          }
        }
      }
    • 1

    代码精简一下,就变成这样了:

    void advance(double realDeltaTime) {
    
        boolean isAtRest = isAtRest();
    
        if (isAtRest && mWasAtRest) {
          return;
        }
    
        double adjustedDeltaTime = realDeltaTime;
        if (realDeltaTime > MAX_DELTA_TIME_SEC) {
          adjustedDeltaTime = MAX_DELTA_TIME_SEC;
        }
    
        mTimeAccumulator += adjustedDeltaTime;
    
        while (mTimeAccumulator >= SOLVER_TIMESTEP_SEC) {
            /**
                inner change 
            **/
        }
    
        mTempState.position = tempPosition;
        mTempState.velocity = tempVelocity;
    
        mCurrentState.position = position;
        mCurrentState.velocity = velocity;
    
        for (SpringListener listener : mListeners) {
          // starting to move
          if (notifyActivate) {
            listener.onSpringActivate(this);
          }
    
          // updated
          listener.onSpringUpdate(this);
    
          // coming to rest
          if (notifyAtRest) {
            listener.onSpringAtRest(this);
          }
        }
      }
    • 1

    这下看得十分清楚了吧,在while循环里面变换之后,我们得到了 :

      mCurrentState.position = position;
     mCurrentState.velocity = velocity;

    然后在接口回调中我们使用到了这些参数,

    spring.addListener(new SpringListener() {
    
                @Override
                public void onSpringUpdate(Spring spring) {
                    float value = (float) spring.getCurrentValue();
                    float scale = 1f - (value * mScale);
    
                    ViewHelper.setScaleX(image, scale);
                    ViewHelper.setScaleY(image, scale);
                }
    
                @Override
                public void onSpringEndStateChange(Spring spring) {}
                @Override
                public void onSpringAtRest(Spring spring) {}
                @Override
                public void onSpringActivate(Spring spring) {}
            });
    

    就这样我么的变换终于就成功了,我们也就走通了相关的流程了,是不是很好玩呢???主要是facebook这个Rebound框架很小,类很少,我们可以在很短的时间对它通读,是不是感觉到分析源码很好玩呢?我也是一名android菜鸟,很多时候不敢去分析源码,很为很多的东西都看不懂看不透,那就慢慢来吧,先从简单的开始吧,呵呵。

    最后,代码地址

  • 相关阅读:
    大数运算
    混合背包问题
    多重背包问题(二进制优化)
    完全背包
    01背包问题
    树状数组
    构建之法阅读笔记04
    第一次冲刺个人总结07
    构建之法阅读笔记03
    第一次冲刺个人总结06
  • 原文地址:https://www.cnblogs.com/ldq2016/p/7079770.html
Copyright © 2011-2022 走看看