自定義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>
- 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(); } }); } }
- 最重要的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軸還是會繼續前進的, 所以在視覺上會產生落地後繼續滾動的效果。