通過本篇文章秽之,你將會了解
- 安卓屬性動畫的基本架構(gòu)
- 插值器和估值器在動畫中的作用
- 手擼屬性動畫
設(shè)想一下,如果你是google的工程師,讓你去設(shè)計一個屬性動畫玄货,你該如何設(shè)計?在設(shè)計屬性動畫時我們應(yīng)該要考慮哪些問題悼泌?
- 生成動畫的api調(diào)用約簡單越好
- 一個View可以有多個動畫松捉,但同時只能有一個在運行
- 動畫的執(zhí)行不能依賴自身的for循環(huán)
- 如何讓動畫動起來
我們先來看下屬性動畫的種類
- 平移動畫
- 透明度動畫
- 縮放動畫
- 旋轉(zhuǎn)動畫
- 幀動畫
屬性動畫的使用
ObjectAnimator animator = ObjectAnimator.ofFloat(view,"scale",1f,2f,3f);
animator.setInterpolator(new LinearInterpolator());
animator.setDuration(500);
animator.start()
動畫的本質(zhì)
???? 動畫實際上是改變View在某一時間點上的樣式屬性,比如在0.1s的時候View的x坐標為50px馆里,在0.2s的時候View的x坐標變?yōu)?50px隘世,在0.3s的時候View的x坐標變?yōu)?50px,肉眼看就會感覺View在向右移動鸠踪。
????實際上是通過一個線程每隔一段時間通過調(diào)用view.setX(index++)來改變屬性值產(chǎn)生動畫效果丙者。
????動畫實際上是一個復(fù)雜的流程,需要考慮的因素比較多营密,在開發(fā)者層面不建議直接調(diào)用view.setX()來實現(xiàn)動畫械媒。
動畫架構(gòu)分析
??????根據(jù)上面的架構(gòu)圖,我們將動畫任務(wù)拆成若干個關(guān)鍵幀,每個關(guān)鍵幀在不同的時間點執(zhí)行自己的動畫纷捞,最終將整個動畫完成痢虹,但每兩個關(guān)鍵幀之間是有時間間隔的,我們要實現(xiàn)一個補幀的操作來過渡兩個關(guān)鍵幀動畫主儡,使動畫看起來銜接平滑自然奖唯。
??????這里可能大家會有一個疑問:為什么要將動畫分解成不同的關(guān)鍵幀?原因是動畫完成是需要時間開銷的缀辩。如果不給出關(guān)鍵幀動畫臭埋,動畫的過程將無法控制,而且在不同的時間點臀玄,控件的狀態(tài)也不一樣瓢阴。
代碼設(shè)計架構(gòu)圖
擼代碼
1、首先我們來模擬VSync信號健无,每隔16ms發(fā)送一個信號去遍歷animationFrameCallbackList執(zhí)行動畫Callback荣恐,定義一個VSyncManager類來模擬
public class VSyncManager {
private List<AnimationFrameCallback> list = new ArrayList<>();
public static VSyncManager getInstance() {
return Holder.instance;
}
private VSyncManager() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(16);
} catch (Exception e) {
e.printStackTrace();
}
for (AnimationFrameCallback animationFrameCallback : list) {
animationFrameCallback.doAnimationFrame(System.currentTimeMillis());
}
}
}
}).start();
}
interface AnimationFrameCallback {
boolean doAnimationFrame(long currentTime);
}
public void add(AnimationFrameCallback animationFrameCallback) {
list.add(animationFrameCallback);
}
static class Holder {
static final VSyncManager instance = new VSyncManager();
}
}
定義一個時間插值器TimeInterpolator
public interface TimeInterpolator {
float getInterpolator(float input);
}
創(chuàng)建一個線性插值器LinearInterpolator實現(xiàn)TimeInterpolator,插值器的輸出我們定義為輸入的一般累贤,你可以設(shè)置你想要的任何值
public class LinearInterpolator implements TimeInterpolator {
@Override
public float getInterpolator(float input) {
return 0.5f*input;
}
}
接著定義我們的關(guān)鍵幀實體類MyFloatKeyFrame叠穆,主要用來存儲三個屬性:當前動畫執(zhí)行的進度百分比,當前幀對應(yīng)的View的屬性值臼膏,當前幀對應(yīng)的屬性值的類型
public class MyFloatKeyFrame {
//當前的百分比
float fraction;
//當前幀對應(yīng)的屬性值
float mValue;
//當前幀對應(yīng)得值得類型
Class mValueType;
public MyFloatKeyFrame(float fraction, float mValue) {
this.fraction = fraction;
this.mValue = mValue;
mValueType = float.class;
}
public float getValue() {
return mValue;
}
public void setValue(float mValue) {
this.mValue = mValue;
}
public float getFraction() {
return fraction;
}
public void setFraction(float fraction) {
this.fraction = fraction;
}
}
再接著定義關(guān)鍵幀集合硼被,用來初始化關(guān)鍵幀信息并且返回對應(yīng)的View的屬性值
public class MyKeyframeSet {
//類型估值器
TypeEvaluator mEvaluator;
List<MyFloatKeyFrame> mKeyFrames;
public MyKeyframeSet(MyFloatKeyFrame... keyFrame) {
this.mEvaluator = new FloatEvaluator();
mKeyFrames = Arrays.asList(keyFrame);
}
//關(guān)鍵幀初始化
public static MyKeyframeSet ofFloat(float[] values) {
if (values.length <= 0) {
return null;
}
int numKeyframes = values.length;
//循環(huán)賦值
MyFloatKeyFrame keyFrame[] = new MyFloatKeyFrame[numKeyframes];
keyFrame[0] = new MyFloatKeyFrame(0, values[0]);
for (int i = 1; i < numKeyframes; i++) {
keyFrame[i] = new MyFloatKeyFrame((float) i / (numKeyframes - 1), values[i]);
}
return new MyKeyframeSet(keyFrame);
}
//獲取當前百分比對應(yīng)得具體屬性值
public Object getValue(float fraction) {
MyFloatKeyFrame prevKeyFrame = mKeyFrames.get(0);
for (int i = 0; i < mKeyFrames.size(); i++) {
MyFloatKeyFrame nextKeyFrame = mKeyFrames.get(i);
if (fraction < nextKeyFrame.getFraction()) {
//當前百分比在此之間
//計算間隔百分比
float intervalFraction = (fraction - prevKeyFrame.getFraction())
/ (nextKeyFrame.getFraction() - prevKeyFrame.getFraction());
//通過估值器返回對應(yīng)得值
return mEvaluator == null ?
prevKeyFrame.getValue() + intervalFraction * (nextKeyFrame.getValue() - prevKeyFrame.getValue()) :
((Number) mEvaluator.evaluate(intervalFraction, prevKeyFrame.getValue(), nextKeyFrame.getValue())).floatValue();
}
prevKeyFrame = nextKeyFrame;
}
//對應(yīng)得幀不夠
return mKeyFrames.get(mKeyFrames.size() - 1).getValue();
}
}
根據(jù)當前動畫執(zhí)行進度百分比fraction獲取對應(yīng)得具體屬性值的相關(guān)計算邏輯可以參考下圖
接下來我們來定義動畫任務(wù)屬性值管理類MyFloatPropertyValuesHolder,主要作用是通過反射獲取控件對應(yīng)的方法渗磅,然后通過調(diào)用該方法(如setScale)給控件設(shè)置相應(yīng)的屬性值
public class MyFloatPropertyValuesHolder {
//屬性名
String mPropertyName;
//屬性類型 float
Class mValueType;
//反射
Method mSetter = null;
//關(guān)鍵幀管理類
MyKeyframeSet mKeyframeSet;
public MyFloatPropertyValuesHolder(String propertyName, float... values) {
this.mPropertyName = propertyName;
mValueType = float.class;
//交給關(guān)鍵幀管理初始化
mKeyframeSet = MyKeyframeSet.ofFloat(values);
}
//通過反射獲取控件對應(yīng)的方法
public void setupSetter() {
char firstLetter = Character.toUpperCase(mPropertyName.charAt(0));
String theRest = mPropertyName.substring(1);
//setScaleX
String methodName = "set" + firstLetter + theRest;
try {
mSetter = View.class.getMethod(methodName, float.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
//給控件設(shè)置相應(yīng)的屬性值
public void setAnimatedValue(View view, float fraction) {
Object value = mKeyframeSet.getValue(fraction);
try {
mSetter.invoke(view, value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
最后定義我們對開發(fā)者暴露的MyObjectAnimator類嚷硫,功能類似Android源碼的的ObjectAnimator類,給開發(fā)人員調(diào)用設(shè)置屬性動畫的api
public class MyObjectAnimator implements VSYNCManager.AnimationFrameCallback {
//動畫時長
private long mDuration = 0;
//需要執(zhí)行動畫的對象
private WeakReference<View> mTarget;
//屬性值管理類
private MyFloatPropertyValuesHolder mFloatPropertyValuesHolder;
private int index = 0;
private TimeInterpolator interpolator;
public long getDuration() {
return mDuration;
}
public void setDuration(long mDuration) {
this.mDuration = mDuration;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public TimeInterpolator getInterpolator() {
return interpolator;
}
public void setInterpolator(TimeInterpolator interpolator) {
this.interpolator = interpolator;
}
public MyObjectAnimator(View target, String propertyName, float... values) {
mTarget = new WeakReference<>(target);
mFloatPropertyValuesHolder = new MyFloatPropertyValuesHolder(propertyName, values);
}
public static MyObjectAnimator ofFloat(View target, String propertyName, float... values) {
MyObjectAnimator anim = new MyObjectAnimator(target, propertyName, values);
return anim;
}
//每隔16ms執(zhí)行一次
@Override
public boolean doAnimationFrame(long currentTime) {
//后續(xù)的效果渲染
//動畫的總幀數(shù)
float total = mDuration / 16;
//拿到執(zhí)行百分比 (index)/total
float fraction = (index++) / total;
//通過插值器去改變對應(yīng)的執(zhí)行百分比
if (interpolator != null) {
fraction = interpolator.getInterpolator(fraction);
}
//循環(huán) repeat
if (index >= total) {
index = 0;
}
//交給mFloatPropertyValuesHolder始鱼,改變對應(yīng)的屬性值
mFloatPropertyValuesHolder.setAnimatedValue(mTarget.get(), fraction);
return false;
}
//開啟動畫
public void start() {
//交給mFloatPropertyValuesHolder改變對應(yīng)的屬性值
mFloatPropertyValuesHolder.setupSetter();
VSYNCManager.getInstance().add(this);
}
}
最后我們來使用下MyObjectAnimator來看看動畫效果
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.bottom);
MyObjectAnimator animator = MyObjectAnimator.ofFloat(button, "ScaleX", 1f, 2f, 3f, 1f);
animator.setInterpolator(new LineInterpolator());
animator.setDuration(3000);
animator.start();
}
布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/bottom"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#008500"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
效果如下圖仔掸,對button進行橫向縮放,和使用原生的ObjectAnimator實現(xiàn)的效果基本一致