Android中動畫大概可以分爲補間動畫,幀動畫,屬性動畫三種
(一)補間動畫Animation分以下4種:
1.TranslateAnimation 平移動畫 x,y方向上的平移 對應xml標籤
2.ScaleAnimation 縮放動畫 x,y方向上的縮放 對應xml標籤
3.RotateAnimation 旋轉動畫 對應xml標籤
4.AlphaAnimation 透明度動畫 對應xml標籤
創建的xml動畫文件放在res/anim文件夾下
(二)幀動畫是通過順序播放一組圖片實現的
系統使用AnimationDrawable類來定義幀動畫也可以通過xml來實現,對應使用標籤中包含標籤來實現
(三)屬性動畫是api11新加入的,api11以下的需要使用NineOldAndroid.jar來做兼容,屬性動畫的實現原理是通過修改對象的屬性值來實現的,可作用於任何對象。補間動畫中的四種動畫效果,也可以通過屬性動畫來實現。
下面主要介紹一下屬性動畫的使用,屬性動畫中主要用到以下幾個類:
1)ObjectAnimator 該類繼承於ValueAnimator 對應xml標籤
2)ValueAnimator 值動畫 對應xml標籤
3)AnimatorSet 屬性動畫集合 對應xml標籤
4)PropertyValueHolder
5)TypeEvaluator 估值器 控制屬性值的變化
6)Interpolator 插值器 控制屬性值變化的速度,加速減速勻速等變化
當然屬性動畫也可以通過xml來定義,xml文件要放在res/animator的目錄下,相關定義標籤。通常情況下,我們都是通過代碼方式來實現屬性動畫的。
一.ValueAnimator
通過以下方法創建
ValueAnimator.ofFloat(“對象屬性”,“屬性值1…屬性值n”);
ValueAnimator.ofArgb(“對象屬性”,“屬性值1…屬性值n”);
ValueAnimator.ofInt(“對象屬性”,“屬性值1…屬性值n”);
…
常用方法有以下幾種
setDuration(500);//設置動畫時長 時間單位爲ms
setRepeatCount(ValueAnimator.INFINITE);//設置循環次數 INFINITE表示循環執行動畫
setRepeatMode(ValueAnimator.RESTART);//設置重複模式 RESTART重新開始/REVERSE反轉
setStartDelay(200);//延遲執行動畫 時間單位爲ms
setEvaluator(new FloatEvaluator());//設置動畫的估值器
setInterpolator(new LinearInterpolator());//設置動畫的插值器
addUpdateListener(AnimatorUpdateListener listener);//添加監聽值改變的監聽器
addListener(AnimatorListener listener)//添加動畫監聽 當前動畫執行狀態 onAnimationStart / onAnimationEnd / onAnimationCancel
getAnimatedFraction()//在監聽中可以通過該方法來獲取當前動畫執行進度
getAnimatedValue()//在監聽中可以通過該方法來獲取當前動畫執行的值
cancel()//取消當前動畫
isRunning()
isStarted()
start()
二.ObjectAnimator
ObjectAnimator繼承自ValueAnimator
ObjectAnimator對象可以使用以下方法來創建
ObjectAnimator.ofFloat(“作用對象類”,“對象屬性”,“屬性值1…屬性值n”);
ObjectAnimator.ofArgb(“作用對象類”,“對象屬性”,“屬性值1…屬性值n”);
ObjectAnimator.ofInt(“作用對象類”,“對象屬性”,“屬性值1…屬性值n”);
…
先看一下View中自帶的實現屬性動畫的方法
//將ImageView水平向左平移500
iv.setTranslationX(500);
//將ImageView垂直向下平移500
iv.setTranslationY(500);
//將ImageView水平方向上縮放爲原來的1.5倍
iv.setScaleX(1.5f);
//將ImageView垂直方向上縮放爲原來的1.5倍
iv.setScaleY(1.5f);
//將ImageView水平方向上旋轉30度
iv.setRotationX(30);
//將ImageView垂直方向上旋轉30度
iv.setRotationY(30);
//將ImageView的透明度設置爲100
iv.setAlpha(100);
//改變ImageView的背景色
iv.setBackgroundColor(Color.RED);
以上代碼都是View的一些屬性方法
這裏我們也可以通過ObjectAnimator來對ImageView的屬性進行改變,可實現相關動畫效果
//實現水平方向上平移 向左500 設置一個值的時候,默認初始值爲0
ObjectAnimator translateXAnim=ObjectAnimator.ofFloat(iv,//作用的對象
"translationX", //改變對象相關的屬性
500f);//這裏是一個泛型數組,可以有多個值 表示改變對象屬性的值
translateXAnim.setDuration(500);//設置動畫時長
translateXAnim.start();
//實現垂直方向上的平移 向下500
ObjectAnimator translateYAnim=ObjectAnimator.ofFloat(iv,//作用的對象
"translationY", //改變對象相關的屬性
500f);//這裏是一個泛型數組,可以有多個值 表示改變對象屬性的值
translateYAnim.setDuration(500);
translateYAnim.start();
//實現水平方向上的縮放 爲原來的1.5倍
ObjectAnimator scaleXAnim=ObjectAnimator.ofFloat(iv,//作用的對象
"scaleX", //改變對象相關的屬性
1.5f);//這裏是一個泛型數組,可以有多個值 表示改變對象屬性的值
scaleXAnim.setDuration(500);
scaleXAnim.start();
//實現垂直方向上的縮放 爲原來的1.5倍
ObjectAnimator scaleYAnim=ObjectAnimator.ofFloat(iv,//作用的對象
"scaleY", //改變對象相關的屬性
1.5f);//這裏是一個泛型數組,可以有多個值 表示改變對象屬性的值
scaleYAnim.setDuration(500);
scaleYAnim.start();
//實現水平方向上的旋轉
ObjectAnimator rotateXAnim=ObjectAnimator.ofFloat(iv,//作用的對象
"rotationX", //改變對象相關的屬性
30f);//這裏是一個泛型數組,可以有多個值 表示改變對象屬性的值
rotateXAnim.setDuration(500);
rotateXAnim.start();
//實現垂直方向上的旋轉
ObjectAnimator rotateYAnim=ObjectAnimator.ofFloat(iv,//作用的對象
"rotationY", //改變對象相關的屬性
30f);//這裏是一個泛型數組,可以有多個值 表示改變對象屬性的值
rotateYAnim.setDuration(500);
rotateYAnim.start();
爲什麼通過以上方法就可以實現對ImageView的動畫效果呢,主要是通過改變View的屬性值來實現,這個就涉及到屬性動畫的實現原理了,後面我們會通過對屬性動畫的源碼分析來它的具體實現
多個動畫同時執行可通過PropertyValuesHolder來實現,也可以通過下面的AnimatorSet來實現
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("alpha", 1f,0.5f);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleX", 1f,0.5f);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("scaleY", 1f,0.5f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(iv, holder1,holder2,holder3);
animator.setDuration(200);
animator.start();
三.AnimatorSet
AnimatorSet表示一個動畫集合控制多個動畫一起播放,主要通過以下方法來實現:
play(Animator anim)
playSequentially(Animator… items) //表示按照順序播放動畫
playSequentially(List items)
playTogether(Animator… items) //一起播放所有動畫
playTogether(Collection items)
play()方法可以和AnimatorSet.Builder中的方法一起使用來控制動畫播放順序
with(Animator anim)
before(Animator anim)
after(Animator anim)
after(long delay)
下面我們來實現多個動畫同時執行
ObjectAnimator animator1 = ObjectAnimator.ofFloat(iv, "translationX", 0f,100f);
animator1.setRepeatCount(3);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(iv, "alpha", 0f,1f);
animator2.setStartDelay(startDelay)//設置延遲執行
ObjectAnimator animator3 = ObjectAnimator.ofFloat(iv, "scaleX", 0f,2f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(500);
animatorSet.play(animator3).with(animator2).after(animator1);//animator1在前面
animatorSet.play(animator3).with(animator2).before(animator1);//animator1在後面
animatorSet.playTogether(animator1,animator2,animator3);
animatorSet.playSequentially(animator1,animator2,animator3);
animatorSet.start();
四.TypeEvaluator
Android系統給我們提供了一些常用的估值器,TypeEvaluator有以下子類可供使用
IntEvaluator
FloatEvaluator
ArgbEvaluator
IntArrayEvaluator
FloatArrayEvaluator
RectEvaluator
PointFEvaluator
我們也可以根據自己的需求來自定義估值器,TypeEvaluator的源碼如下,TypeEvaluator只是一個接口,定義了一個計算值的方法evaluate
ublic interface TypeEvaluator<T> {
/**
* This function returns the result of linearly interpolating the start and end values, with
* <code>fraction</code> representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
* and <code>t</code> is <code>fraction</code>.
*
* @param fraction The fraction from the starting to the ending values 這個是一個進度值百分比
* @param startValue The start value.開始值
* @param endValue The end value.結束值
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
public T evaluate(float fraction, T startValue, T endValue);
}
自定義估值器需要實現TypeEvaluator接口,並實現evaluate方法,主要代碼實現邏輯是在evaluate方法中根據當前進度值對要返回的值進行動態改變,例如系統提供的FloatEvaluator類
public class FloatEvaluator implements TypeEvaluator<Number> {
public Float evaluate(float fraction, Number startValue, Number endValue) {
//對傳入的值根據進度百分比進行計算後返回
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
五.Interpolator
Android系統給我們提供了一些常用的插值器,供我們日常使用
Interpolator繼承自TimeInterpolator,BaseInterpolator和LookupTableInterpolator都繼承自Interpolator
BaseInterpolator主要有以下子類
AccelerateDecelerateInterpolator
AnticipateInterpolator
PathInterpolator
BounceInterpolator
OvershootInterpolator
AnticipateOvershootInterpolator
LinearInterpolator
AccelerateInterpolator
DecelerateInterpolator
CycleInterpolator
LookupTableInterpolator主要有以下子類
FastOutSlowInInterpolator
FastOutLinearInInterpolator
LinearOutSlowInInterpolator
我們也可以自定義插值器對當前動畫的進行速度進行動態改變,這裏需要實現TimeInterpolator 接口來實現自定義類
TimeInterpolator的源碼如下
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}
AccelerateInterpolator的源碼實現,代碼的主要實現邏輯在getInterpolation方法中
BaseInterpolator extends Interpolator
Interpolator extends TimeInterpolator
public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mFactor;
private final double mDoubleFactor;
public AccelerateInterpolator() {
mFactor = 1.0f;
mDoubleFactor = 2.0;
}
/**
* Constructor
*
* @param factor Degree to which the animation should be eased. Seting
* factor to 1.0f produces a y=x^2 parabola. Increasing factor above
* 1.0f exaggerates the ease-in effect (i.e., it starts even
* slower and ends evens faster)
*/
public AccelerateInterpolator(float factor) {
mFactor = factor;
mDoubleFactor = 2 * mFactor;
}
public AccelerateInterpolator(Context context, AttributeSet attrs) {
this(context.getResources(), context.getTheme(), attrs);
}
/** @hide */
public AccelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
TypedArray a;
if (theme != null) {
a = theme.obtainStyledAttributes(attrs, R.styleable.AccelerateInterpolator, 0, 0);
} else {
a = res.obtainAttributes(attrs, R.styleable.AccelerateInterpolator);
}
mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f);
mDoubleFactor = 2 * mFactor;
setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
//主要實現方法
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createAccelerateInterpolator(mFactor);
}
}
六.實現今日頭條點贊效果
實現原理
1.在點擊按鈕的時候,不斷的向佈局中添加不同的ImageView
2.自定義TypeEvaluator重寫evaluate方法,對座標值進行二階貝塞爾曲線計算,並返回計算後的座標值
3.然後使用ValueAnimator添加AnimatorUpdateListener監聽來獲取當前點的座標值
4.在動畫改變的過程中對ImageView的x,y值進行處理,同時改變它的透明度和大小縮放
實現代碼如下:
public class AnimatorActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener {
private static final String TAG = "AnimatorActivity";
//表情資源
private static final int[] faces = {
R.mipmap.face1, R.mipmap.face2, R.mipmap.face3, R.mipmap.face4,
R.mipmap.face5, R.mipmap.face6, R.mipmap.face7, R.mipmap.face8,
R.mipmap.face9, R.mipmap.face10, R.mipmap.face11, R.mipmap.face12,
R.mipmap.face13, R.mipmap.face14, R.mipmap.face15, R.mipmap.face16,
R.mipmap.face17, R.mipmap.face18, R.mipmap.face19, R.mipmap.face20,
};
private Stack<ImageView> cacheViews;//用於緩存ImageView 重複使用
private Random random;
private int screenWidth;
private int screenHeight;
private List<PointF> points;
private PointF pointF;
private PointF leftTopP;
private PointF midTopP;
private PointF rightTopP;
private PointF leftBottomP;
private PointF midBottomP;
private PointF rightBottomP;
private RelativeLayout content;
private CheckBox checkbox;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animator);
checkbox = findViewById(R.id.checkbox);
checkbox.setOnCheckedChangeListener(this);
content = findViewById(R.id.content);
cacheViews = new Stack<>();
screenWidth = getScreenSize()[0];
screenHeight = getScreenSize()[1];
points = new ArrayList<>();
random = new Random();
//初始化點贊起始點得座標值 爲手機屏幕中央
pointF = new PointF(screenWidth / 2, screenHeight / 2);
//初始化點贊表情移動軌跡結束點6個 分別爲左上,中上,右上,左底,中底,右底
leftTopP = new PointF(0, 0);
midTopP = new PointF(screenWidth / 2, 0);
rightTopP = new PointF(screenWidth, 0);
leftBottomP = new PointF(0, screenHeight / 2);
midBottomP = new PointF(screenWidth / 2, screenHeight);
rightBottomP = new PointF(screenWidth, screenHeight);
points.add(leftTopP);
points.add(midTopP);
points.add(rightTopP);
points.add(leftBottomP);
points.add(midBottomP);
points.add(rightBottomP);
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
ImageView imageView = addFaceView();
startAnim(imageView);
}
/**
* 每點擊一次生成一個View 執行平移和縮放和淡出動畫
*/
private ImageView addFaceView() {
//生成一個隨機數來獲取圖片資源
int index = random.nextInt(faces.length - 1);
Log.e(TAG, "addFaceView: index=" + index);
ImageView imageView;
//先從緩存中取控件
if (!cacheViews.empty()) {
imageView = cacheViews.pop();
} else {
imageView = new ImageView(this);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
imageView.setLayoutParams(params);
}
imageView.setImageResource(faces[index]);
//當前結束點只有6 個,而圖片有20個 這裏需要轉換一下index避免數組越界
imageView.setTag(index >= points.size() ? index / (faces.length / points.size() + 1) : index);
content.addView(imageView);
return imageView;
}
private void startAnim(final ImageView view) {
int index = (int) view.getTag();
//這裏動態生成二階貝塞爾曲線路徑控制點的座標
PointF controlP = new PointF();
if (index < index / 2) {
//控制點x座標不變 y座標屏幕上半部分使用結束點y座標加上屏幕四分之一
controlP.x = points.get(index).x;
controlP.y = points.get(index).y + screenHeight / 4;
} else {
//控制點x座標不變 y座標屏幕上半部分使用結束點y座標減去屏幕四分之一
controlP.x = points.get(index).x;
controlP.y = points.get(index).y - screenHeight / 4;
}
//這裏將控制點座標傳入估值器
FaceEvaluator faceEvaluator = new FaceEvaluator(controlP);
//在值動畫中傳入 動畫執行的開始點座標和結束點座標 開始點座標不變都是屏幕中心 結束點則隨機生成
ValueAnimator valueAnimator = ValueAnimator.ofObject(faceEvaluator, pointF, points.get(index));
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointF = (PointF) animation.getAnimatedValue();
//移動路徑動畫
view.setX(pointF.x);
view.setY(pointF.y);
//透明度動畫
view.setAlpha(1-animation.getAnimatedFraction()/2);
//縮放動畫
view.setScaleX(0.9f+(animation.getAnimatedFraction()/2));
view.setScaleY(0.9f+(animation.getAnimatedFraction()/2));
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//動畫執行完成後將view緩存起來,方便下次使用
content.removeView(view);
cacheViews.push(view);
}
});
valueAnimator.start();
}
private static class FaceEvaluator implements TypeEvaluator<PointF> {
//控制點座標
private PointF controlPoint;
public FaceEvaluator(PointF controlPoint) {
this.controlPoint = controlPoint;
}
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
//(1 - t)^2 P0 + 2 t (1 - t) P1 + t^2 P2
//使用二階貝塞爾曲線來改變值得走勢
PointF point = new PointF();
point.x = (float) (Math.sqrt(1 - fraction) * startValue.x + 2 * fraction * (1 - fraction) * controlPoint.x + Math.sqrt(fraction) * endValue.x);
point.y = (float) (Math.sqrt(1 - fraction) * startValue.y + 2 * fraction * (1 - fraction) * controlPoint.y + Math.sqrt(fraction) * endValue.y);
// point.x=startValue.x+(endValue.x-startValue.x)*fraction;
// point.y=startValue.y+(endValue.y-startValue.y)*fraction;
return point;
}
}
/**
* 獲取屏幕得寬高
*
* @return 寬高的數組
*/
private int[] getScreenSize() {
int[] screenSize = new int[2];
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
screenSize[0] = metrics.widthPixels;
screenSize[1] = metrics.heightPixels;
return screenSize;
}
}
XML佈局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AnimatorActivity">
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:button="@drawable/agree_selector"/>
</RelativeLayout>