使用animator實現粒子動畫效果
阿新 • • 發佈:2019-02-15
1、前言
本文圍繞著實現粒子放大效果,著重講解android中涉及到動畫縮放以及動畫集的使用,並且會將講解一些插值器相關的知識。閱讀本文需要讀者有一定的自定義View的基礎知識,本文將不再講解自定義View的相關知識,讀者需要可以自行去學習,也可以閱讀筆者的文章,自定義View的基本知識。2、著色器
為了讓效果的色彩比較的絢麗,需要讓粒子(這裡其實就是用小圓點代替)有一個色彩的過渡,所以需要用到著色器。比較常用到的主要有線性漸變的著色器,它可以讓粒子整體上看上去具有一個按照線性排列的色彩過渡。瞭解線性漸變的著色器之前,先了解shader。在paint裡面有一個setshader方法,用於設定著色器,著色器的作用就是用於給顏色實現一個跨度的平衡感,如果paint裡面包含了shader物件,那麼用此paint繪畫的線,圓圈,影象之類的圖案,都會從shader裡面獲取顏色,因此,它是實現絢麗色彩的基礎。2.1LinearGradient
public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
TileMode tile)
其中x0表示起始點x座標,x1表示終止點x座標,color0是起始顏色,color1是終止顏色,tile是平鋪模式,它主要有三個值分別是TileMode.CLAMP,TileMode.REPEAT,TileMode.MIRROR。分表表示覆制模式,重複模式,映象模式。假如有如下程式碼:
LinearGradient linearGradient=new LinearGradient(0,0,canvas.getWidth(),canvas.getHeight(), Color.RED,Color.BLUE, Shader.TileMode.REPEAT); Paint paint=new Paint(); paint.setShader(linearGradient); canvas.drawCircle(canvas.getWidth()/2,canvas.getHeight()/2,canvas.getWidth()/2,paint);
那麼它的效果如下:
可以看到線性漸變畫筆可以讓顏色有一個過渡,這種感覺是很美妙的。如果我們再加一些動畫,一些透明度變化,就可以做出很絢麗的色彩效果了。
3.animator
animator是android3.0之後提供的特性,在這之前使用的是animation,區別在於,前者是可以真正改變屬性值的,後者只是改變了檢視的位置,但是檢視的屬性並沒有得到任何改變,型別障眼法。animator是一個抽象類,是valueAnimator,objectAnimator等的父類,它主要鑑定了一些屬性動畫的基本操作,比如啟動,暫停設定動畫監聽器等等。 啟動動畫:public void start()
public void end()
上述方法分別用於啟動動畫和終止動畫,start方法由哪個執行緒啟動則執行在哪個執行緒,end方法則用於終止動畫的執行,它會讓動畫停止並且迅速到結尾的值(屬性動畫一般會有插值器,預設的是先加速後減速的插值器,用於從一個初始值到結尾制的過渡)。
暫停,繼續動畫:
public void pause()
public void resume()
pause用於暫停動畫,如果動畫尚未start,或者已經end,則此呼叫會被忽略。注意此方法必須和start同一執行緒被呼叫。如果要繼續動畫,則呼叫resume方法。 設定動畫時長:
public abstract Animator setDuration(long duration)
用於給動畫從開始到結束設定一個時間長度,單位是毫秒。 設定時間插值器:
public abstract void setInterpolator(TimeInterpolator value);
此方法可以設定時間插值器,它的作用是,可以讓動畫不處於線性變化的效果,預設的話是先加速後減速的插值器。即動畫效果從起始值到終止值得過渡,會經歷一個加速減速的過程。 設定監聽器:
public void addListener(AnimatorListener listener)
如果希望監聽動畫是否完成結束或者重複的動作,就可以設定一個動畫監聽器。animatorListener的原型如下:
3.1 animatorListener
public static interface AnimatorListener {
void onAnimationStart(Animator animation);
void onAnimationEnd(Animator animation);
void onAnimationCancel(Animator animation);
void onAnimationRepeat(Animator animation);
}
當呼叫start時候,onAnimationStart就會被回撥,當end被呼叫的時候,onAnimationEnd被回撥。當呼叫cancel停止動畫的時候,onAnimatonCancel被回撥。 以上是animator的基本要點。有了這些基本知識,現在就可以學習ValueAnimator了。
4.valueAnimator
valueAnimator是一個值動畫,怎麼理解呢?它的作用在於,能夠把你設定的初始值,和終止值作為起始點,然後通過插值器,在一段時間內,按照插值器的的計算來設定當前動畫的值,因為這些值一般都會影響動畫的效果,所以叫做值動畫。對於插值器,可能大家比較模糊,插值器主要是用來加速度來改變值的,比如:AccelerateDecelerateInterpolator
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
這是一個先加速後減速的插值器,它的計算過程是通過getInterpolation來計算返回值的。再細心一點,大家可以研究一下cos函式的數值變化,就知道為什麼是先加速後減速了。
4.1常用的初始化valueAnimator的方法
既然valueAnimator是值動畫,那麼自然包含可以設定值的方法,valueAnimator對一些常用的數值型別提供了支援。比如:public static ValueAnimator ofInt(int... values)
public static ValueAnimator ofArgb(int... values)
public static ValueAnimator ofFloat(float... values)
它們都可用於設定多個值,注意,千萬不要只設置一個值,最少都要兩個值,一個代表起始值,一個代表終止值。這三個方法分表表示int資料型別的變化值動畫,float,argb顏色的值動畫。到這裡大家可能迷惑,這些值是除了作為起始點和終止點之外,有什麼用處。在valueAnimator中,有一個方法是用於獲取屬性值的,如下:
public Object getAnimatedValue()
這個方法用於獲取動畫當前處於的值,這個值必定是介於起始值和終止值之間的。比如我們用ofInt獲得了一個valueAnimator,然後監聽動畫,在每次動畫更新的時候,就可以呼叫getAnimatedValue方法獲取當前的int值,這個值可以用來重新設定畫筆的粗細,或者圓的半徑,矩形的寬和高......
除了這些資料型別之外,也允許自定義資料型別,但是因為是自定義的型別,valueAnimator並不知道你所需要的變化規則是什麼,所以你需要給他們提供變化規則。使用自定義資料型別獲取valueAnimator的方法如下:
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
在講述這個方法之前,先了解一下TypeEvaluator,從字面值上看,它是用來評估值的。它確實也是這樣的。
4.2TypeEvaluator
TypeEvaluator是一個介面,用於開發者自定義值變化的規則,可通過ValueAnimator.setEvaluator方法給值動畫設定自定義的值變化規則。在這個介面中,最重要也是為一個的一個方法是:
public T evaluate(float fraction, T startValue, T endValue);
其中fraction是一個插值器提供的值,就是我們前面講的插值器,通常我們實現這個方法,用result=x0+t*(x1-x0)這個規則就好,x0表示startValue,x1表示endValue,t表示fraction。這樣就可以通過getAnimatedValue方法獲取到計算結果。
4.3 oFObject方法
現在回到我們剛剛那個方法來。說到,這個方法可以獲取一個自定義值變化規則的valueAnimator動畫。那麼我們需要怎麼使用它呢。給出一個例子:
首先需要一個TypeEvaluator提供變化規則。假設現在有一個Circle實體,裡面含有x,y,radius分別表示中心點和半徑。我們定義一個circle規則的TypeEvaluator
public class CircleEvaluate implements TypeEvaluator<Circle> {
/**
* 估算值的表示式 result = x0 + t * (x1 - x0)
* x0是startValue,x1是endValue,t是fraction
*
* @param fraction
* @param startValue
* @param endValue
* @return
*/
@Override
public Circle evaluate(float fraction, Circle startValue, Circle endValue) {
float circleX = startValue.getCenterX() + fraction * (endValue.getCenterX() - startValue.getCenterX());
float circleY = startValue.getCenterY() + fraction * (endValue.getCenterY() - startValue.getCenterY());
float radius = startValue.getRadius() + fraction * (endValue.getRadius() - startValue.getRadius());
return new Circle(circleX, circleY, radius);
}
接著,就可以使用它定義我們的valueAnimator了。如下:
ValueAnimator animator = ValueAnimator.ofObject(evaluate, circleGroup[i][j],minCircleGroup[i][j]);
後面兩個其實是circle物件。對於valueAnimator,因為是animator的子類,所以包含有基本的start,end等方法,它也可以設定插值器setInterpolator,設定Evaluator.......
5、監聽valueAnimator的變更
在valueAnimator裡面,我們必須得知道當前的值隨著插值器和typeEvaluator的估值,處於什麼養的一個狀態,所以我們必須監聽值得變化,因為值得變化一定伴隨著動畫的變化,不然怎麼叫值動畫呢?所以我們需要監聽動畫的變化來獲取估值,然後重新整理View重新根據新的值來繪製介面。
public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}
只要我們監聽了這個物件,那麼就可以從animation.getAnimatedValue獲取當前的估值。 大部分時候,僅僅對一個物件進行動畫,並不能滿足我們的需求,我們很經常需要對很多物件進行動畫的變化,這就涉及到集體的變換了。在android裡面,提供了AnimatorSet,來解決這個問題。
6、animatorSet
animatorSet是一個動畫集合,裡面的動畫都儲存在一個列表裡面,它們可以一起播放,也可以按次序播放,或者指定一定的時間間隔播放。總的來說,對它的操作就相當於對集合中所有的動畫的操作。比如呼叫animatorSet的start方法,等同於呼叫動畫集合所有的動畫的start方法。其中呼叫的次序是可以設定的。知道了這些,下面我們詳細瞭解一下這個類帶給了我們什麼。
6.1設定動畫集合的播放規則
這個播放規則不是動畫的播放規則(TypeEvaluator和interpolarter決定),而是動畫間的播放規則。主要有public void playTogether(Animator... items)
public void playTogether(Collection<Animator> items)
public void playSequentially(Animator... items)
public void playSequentially(List<Animator> items)
總共提供了兩種規則,一種是所有的動畫一起播放,另外一種是等前面一個播放完畢再接著播放下一個。
前面提到AnimatorSet只是一個動畫的集合,因此,Animator有的它都有,這裡就不再說了。
7、使用Animator實現粒子縮放效果
接下來,通過例項講解animator的使用。例子實現了一個同時對圓點集合進行縮放的效果,在縮放過程中,通過改變圓點的中心座標和半徑,來改變圓點的位置和大小,從而達到粒子縮放效果。
首先定義一個attrs.xml檔案。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ParticleView">
<attr name="circleStartColor" format="color"></attr>
<attr name="circleEndColor" format="color"></attr>
<attr name="row" format="integer"></attr>
<attr name="col" format="integer"></attr>
</declare-styleable>
</resources>
然後定義小圓點的實體:
package cn.com.chinaweal.mypartical;
/**
* Created by Myy on 2016/8/31.
*/
public class Circle {
private float centerX;
private float centerY;
private float radius;
public Circle(float centerX, float centerY, float radius) {
this.centerX = centerX;
this.centerY = centerY;
this.radius = radius;
}
public float getCenterX() {
return centerX;
}
public void setCenterX(float centerX) {
this.centerX = centerX;
}
public float getCenterY() {
return centerY;
}
public void setCenterY(float centerY) {
this.centerY = centerY;
}
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
}
}
設定圓點的變化規律:
package cn.com.chinaweal.mypartical;
import android.animation.TypeEvaluator;
/**
* 圓點的屬性變化評估值,用於動畫過程獲取值
* Created by Myy on 2016/8/31.
*/
public class CircleEvaluate implements TypeEvaluator<Circle> {
/**
* 估算值的表示式 result = x0 + t * (x1 - x0)
* x0是startValue,x1是endValue,t是fraction
*
* @param fraction
* @param startValue
* @param endValue
* @return
*/
@Override
public Circle evaluate(float fraction, Circle startValue, Circle endValue) {
float circleX = startValue.getCenterX() + fraction * (endValue.getCenterX() - startValue.getCenterX());
float circleY = startValue.getCenterY() + fraction * (endValue.getCenterY() - startValue.getCenterY());
float radius = startValue.getRadius() + fraction * (endValue.getRadius() - startValue.getRadius());
return new Circle(circleX, circleY, radius);
}
}
接著自定義View實現可縮放的粒子效果:
package cn.com.chinaweal.mypartical;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.WindowManager;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Myy on 2016/8/31.
*/
public class ParticleView extends View {
private Paint circlePaint;//小圓點畫筆
private Context context;
private int circleStartColor, circleEndColor;//小圓點起始顏色,終止顏色
private int row = 10, col = 10;
private Circle circleGroup[][],minCircleGroup[][];//圓點陣列,最小圓點陣列,這個可以用來在圓點動畫過程中指定最終值
private float startWidth, startHeight, circleWidth, circleHeight,minCircleBound;
private float circlePadding;//圓點的間距
private float density;//畫素密度
private float radius;//圓點半徑
private float firstCircleX, firstCircleY;//第一個小圓點的中心座標
private final static int START=0,ANIMATION_CIRCLE=1;//初始窗臺,圓點動畫狀態
private int status=START;
public ParticleView(Context context) {
super(context);
init(context);
}
public ParticleView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.ParticleView);
circleStartColor = typeArray.getColor(R.styleable.ParticleView_circleStartColor, Color.CYAN);
circleEndColor = typeArray.getColor(R.styleable.ParticleView_circleEndColor, Color.RED);
row = typeArray.getInt(R.styleable.ParticleView_row, 10);
col = typeArray.getInt(R.styleable.ParticleView_col, 10);
init(context);
}
private void init(Context context) {
this.context=context;
circlePaint = new Paint();
circlePaint.setColor(circleStartColor);
circleGroup = new Circle[row][col];
minCircleGroup=new Circle[row][col];
getDensity();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(status==START) {
initCanvas(canvas);
}
if(status==ANIMATION_CIRCLE)
{
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
canvas.drawCircle(circleGroup[i][j].getCenterX(), circleGroup[i][j].getCenterY(), circleGroup[i][j].getRadius(), circlePaint);
}
}
}
}
/**
* 初始化佈局
* @param canvas
*/
private void initCanvas(Canvas canvas) {
startWidth = canvas.getWidth() - getPaddingLeft() - getPaddingRight();
startHeight = canvas.getHeight() - getPaddingTop() - getPaddingBottom();
circlePadding = 5 * density;
circleWidth = (startWidth - circlePadding * (col - 1)) / col;
circleHeight = (startHeight - circlePadding * (row - 1)) / row;
minCircleBound=Math.min(circleWidth,circleHeight);//以最小的值為直徑,這樣才可以完全顯示所有的圓點
radius = minCircleBound / 2;//獲取半徑
firstCircleX = minCircleBound / 2;
firstCircleY = circleHeight / 2;
//設定漸變畫筆效果
LinearGradient linearGradient = new LinearGradient(0, 0, startWidth, startHeight, circleStartColor, circleEndColor, Shader.TileMode.MIRROR);
circlePaint.setShader(linearGradient);
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
circleGroup[i][j] = new Circle(firstCircleX + (minCircleBound + circlePadding) * i, firstCircleY + (minCircleBound + circlePadding) * j, radius);
minCircleGroup[i][j] = new Circle(firstCircleX + (minCircleBound + circlePadding) * i+circlePadding*3, firstCircleY + (minCircleBound + circlePadding) * j+circlePadding*3, circlePadding);
canvas.drawCircle(circleGroup[i][j].getCenterX(), circleGroup[i][j].getCenterY(), radius, circlePaint);
}
}
}
/**
* 啟動動畫
*/
public void startAnimation() {
//false表示每個動畫都是用自己的插值器否則就是用共有的插值器
status=ANIMATION_CIRCLE;
final AnimatorSet animatorSet = new AnimatorSet();
List<Animator> animatorList = new ArrayList<>();
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
//valueAnimator可以設定動畫屬性的值在某個區間內以某種方式進行加速或者減速
final int tempRow=i;
final int tempCol=j;
final CircleEvaluate evaluate=new CircleEvaluate();
ValueAnimator animator = ValueAnimator.ofObject(evaluate, circleGroup[i][j],minCircleGroup[i][j]);
animator.setDuration(3000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Circle circle=(Circle)animation.getAnimatedValue();
circleGroup[tempRow][tempCol]=circle;
invalidate();
}
});
animatorList.add(animator);
}
}
animatorSet.playTogether(animatorList);
animatorSet.start();
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
/**
* 獲取螢幕密度
*/
private void getDensity() {
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
density = displayMetrics.density;
}
}
讀者在看這個例子的時候,關注點不要在哪些半徑直徑的東西,而是關注valueAnimator和circle之間的變化,以及這些變化如何引起view渲染的。
佈局檔案:
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:weightSum="2"
android:orientation="vertical"
tools:context="cn.com.chinaweal.mypartical.MainActivity">
<cn.com.chinaweal.mypartical.ParticleView
android:id="@+id/particleView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
app:circleStartColor="#efefaa"
app:circleEndColor="#aa11aa"
android:background="#efefef"
app:col="8"
app:row="8" />
<cn.com.chinaweal.mypartical.ParticleView
android:id="@+id/particleView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
app:circleStartColor="#eaaefa"
app:circleEndColor="#1133aa"
app:col="10"
app:row="10" />
</LinearLayout>
acitivtiy:
package cn.com.chinaweal.mypartical;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
private ParticleView particleView;
private ParticleView particleView1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.shader_layout);
particleView = (ParticleView) findViewById(R.id.particleView);
particleView1=(ParticleView)findViewById(R.id.particleView1);
}
@Override
protected void onStart() {
super.onStart();
particleView.postDelayed(new Runnable() {
@Override
public void run() {
particleView.startAnimation();
particleView1.startAnimation();
}
}, 1000);
}
}
效果圖:
開始:
進過一段時間:
如果讀者瞭解了,可以繼續研究文字的拖拽,矩形的變化,圖片的旋轉.......各種效果,綜合起來就可以達到很炫的動畫,可以用作起始頁或者引導頁作為歡迎動畫......
---------文章寫自:HyHarden---------