自定義View | ofObject詳解與實戰(ValueAnimator進階)

  • 2019 年 10 月 8 日
  • 筆記
  • ofInt()ofFloat()函數用來定義動畫, 但ofInt()函數只能傳入Integer類型的值, ofFloat()函數則只能傳入Float類型的值。
  • 如果需要操作其他類型的變量, 需要使用ValueAnimator另一個函數ofObject(), 可以傳入任何類型的變量。
  • Ctrl + 左鍵看一下源碼對ofObject()的定義
    public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {          ValueAnimator anim = new ValueAnimator();          anim.setObjectValues(values);          anim.setEvaluator(evaluator);          return anim;      }
  • 第一個參數是自定義Evaluator; 第二個參數是可變長參數,屬於Object類型;
  • 第一個參數Evaluator的作用: 根據當前動畫的數值進度計算出當前進度所對應的值。 既然Object對象是我們自定義的, 那必然從進度到值的轉換過程也必須由我們來做, 否則系統不可能知道我們要將數值進度轉換成什麼具體值
  • 使用ofObject()函數實現將TextView的內容從字母A變化到Z
    private void startAnimationTestCharEvaluator() {          ValueAnimator animator = ValueAnimator.ofObject(new CharEvaluator(), 'A', 'Z');            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {              @Override              public void onAnimationUpdate(ValueAnimator animation) {                  char text = (Character) animation.getAnimatedValue();                  tv_text.setText(String.valueOf(text));              }          });          animator.setDuration(10000);          animator.setInterpolator(new AccelerateInterpolator());          animator.start();      }
這裡注意兩個地方
  • 第一,構造ValueAnimator時,傳入了一個自定義CharEvaluator; 在初始化動畫時,傳入的是Character對象 字母A字母Z 在這裡要實現的效果是: 對Character對象應用動畫, 動畫自動將字母A變化到字母Z具體實現則封裝CharEvaluator中, 這裡只需知道, 構造ValueAnimator時傳入的是兩個Character對象, 一個是起始值,另一個是終止值,即可。
  • 第二,監聽; !!!構造類型返回值類型相一致!!! 先通過animation.getAnimatedValue()函數得到當前動畫的字符, 然後把字符設置給TextView構造ValueAnimator時, 傳入的值類型Character對象 所以 在動畫過程中通過Evaluator 返回的值類型 必然跟構造時的類型一致的 也是Character對象。

ASCII碼錶中數字與字符的轉換方法

  • ASCII碼錶中,每個字符都有數字與它一一對應, 字母A~Z之間的所有字母對應的數字區間為65~90
  • 在程序中,可以數字強制轉換成對應的字符
  • 數字轉字符 char temp =(char)65;//得到的 temp的值 就是 大寫字母A
  • 字符轉數字 char temp = 'A'; int num = (int) temp; //這裡得到的num值就是對應的ASCII碼值65
實現CharEvaluator
public class CharEvaluator implements TypeEvaluator<Character> {        @Override      public Character evaluate(float fraction, Character startValue, Character endValue) {          int startInt = (int)startValue;          int endInt = (int)endValue;          int curInt = (int) (startInt + fraction * (endInt - startInt));            char result = (char) curInt;            return result;      }    }
  • 先把Character對象轉換成Int去運算, 再把運算完的Int結果轉換成Character返回

案例:拋物動畫

插值器只能改變動畫進展的快慢, 而Evaluator則可以改變返回的值。 Evaluator與ofObject結合 使得ValueAnimator更加強大 使參數可以在Evaluator中處理, 並返回給一個自定義的對象

  • 下面實現一個拋物動畫, 當單擊按鈕時,圓球開始向下做拋物運動, 在滾動一段距離後結束;
實現(FallingBallActivity)

1.利用shape標籤實現一個圓形Drawable(drawable/circle.xml)

<?xml version="1.0" encoding="utf-8"?>  <shape xmlns:android="http://schemas.android.com/apk/res/android"      android:shape='oval'>        <solid android:color="#0066FF"/>    </shape>

2.FallingBallActivity布局文件

<?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:app="http://schemas.android.com/apk/res-auto"      xmlns:tools="http://schemas.android.com/tools"      android:orientation="horizontal"      android:layout_width="match_parent"      android:layout_height="match_parent"      tools:context=".CustomViews.FallingBallActivity">        <ImageView          android:id="@+id/ball_img"          android:layout_width="50dp"          android:layout_height="50dp"          android:src="@drawable/circle"/>        <Button          android:id="@+id/start_fallingBallAnim"          android:text="StartAnim"          android:layout_width="wrap_content"          android:layout_height="wrap_content" />    </LinearLayout>
  1. Activity代碼 核心在於button的點擊事件, 注意代碼中注釋:

這裡需要定義球的位置, 需要實時計算出當前球所在的 X,Y 坐標, 所以ValueAnimator要返回含有X,Y坐標的對象 才能將球移動到指定位置; 這裡使用Point對象來返回球在每一時刻的位置

public class FallingBallActivity extends AppCompatActivity {        private Button startAnim;      private ImageView ballImg;        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_falling_ball);            initViews();      }        private void initViews() {          startAnim = findViewById(R.id.start_fallingBallAnim);          ballImg = findViewById(R.id.ball_img);            startAnim.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                    //設置自定義Evaluator, 以及初始值終止值(皆為Point類型)                  ValueAnimator animator = ValueAnimator.ofObject(new FallingBallEvaluator(),                          new Point(0, 0), new Point(500, 500));                    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {                      @Override                      public void onAnimationUpdate(ValueAnimator animation) {                          Point mCurPoint = (Point) animation.getAnimatedValue();                            //以函數x,y值為左上坐標,加以自身寬高為右下坐標                          ballImg.layout(mCurPoint.x, mCurPoint.y, mCurPoint.x + ballImg.getWidth(),                                  mCurPoint.y + ballImg.getHeight());                      }                  });                  animator.setDuration(2000);                  animator.start();              }          });      }  }
  1. 最重要的FallingBallEvaluator, 由它返回每一時刻球的實際位置。
public class FallingBallEvaluator implements TypeEvaluator<Point> {        private Point point = new Point();        @Override      public Point evaluate(float fraction, Point startValue, Point endValue) {          point.x = (int) (startValue.x + fraction * (endValue.x - startValue.x));            //y為二倍速          if (fraction * 2 <= 1) {                //y二倍速,若未完成,繼續二倍刷新              point.y = (int) (startValue.y + fraction * 2 * (endValue.y - startValue.y));          } else {              //如果完成,則不變              point.y = endValue.y;          }            return point;      }  }

計算point,把x、y值分開來算

  • x值的運算結果賦給結果point對象的x, y值的運算結果賦給結果point對象的y, 即可
  • 在拋物運動中, 物體在X軸方向的速度是不變的,所以在X軸方向它的實時位置是: point.x=(int)(startValue.x+fraction*(endValue.x-startValue.x)); 而在Y軸方向則是s=V0t+gt*t。 其中,V0表示初始速度; g是重力加速度,取值是9.8;t表示當前的時間。
  • 而在這裡我們是沒有時間概念的,只有fraction表示的進度, 所以要想完美匹配這個自由落體公式, 需要複雜的計算,這不是本例的重點。
  • 這裡取一個折中公式:將實時進度乘以2作為當前進度。 很顯然,Y軸的進度會首先完成(變成1), 這時X軸還是會繼續前進的, 所以在視覺上會產生落地後繼續滾動的效果。