- 動畫效果一直是人機交互中非常重要的部分,與死板、突兀的顯示效果不同,動畫效果的加入,讓交互變得更加友好,特別是在提示、引導類的場景中,合理地使用動畫能讓用戶獲得更加愉悅的使用體驗
一、Android View動畫框架
- Animation框架定義了透明度、旋轉、縮放、位移等幾種常見的動畫
- 實現原理:
- 每次繪制View時,ViewGroup中的drawChild函數獲取該view的Animation的Transformation值,然后調用canvas.concat(transformToApply.getMatrix())
- 通過矩陣運算完成幀動畫,如果動畫沒有完成,就繼續調用invalidate() 函數,啟動下次繪制來驅動動畫,從而完成整個動畫的繪制。
二、幀動畫
- 幀動畫就是一張張圖片不同的切換,形成的動畫效果。
- 一般手機的開機動畫,應用的等待動畫等都是幀動畫,因為只需要幾張圖片輪播,極其節省資源,如果真的設計成動畫,那么是很耗費資源的事。
- 在res目錄下新建一個drawable文件夾並定義xml文件,子節點為 animation-list,在這里定義要顯示的圖片和每張圖片的顯示時長。
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false"><!-- false表示循環播放,true表示只播放一次 -->
<item android:drawable="@drawable/g1" android:duration="200" />
<item android:drawable="@drawable/g2" android:duration="200" />
<item android:drawable="@drawable/g3" android:duration="200" />
<item android:drawable="@drawable/g4" android:duration="200" />
<item android:drawable="@drawable/g5" android:duration="200" />
<item android:drawable="@drawable/g6" android:duration="300" />
<item android:drawable="@drawable/g7" android:duration="400" /><!-- 慢動作 -->
<item android:drawable="@drawable/g8" android:duration="500" />
<item android:drawable="@drawable/g9" android:duration="200" />
<item android:drawable="@drawable/g10" android:duration="200" />
<item android:drawable="@drawable/g11" android:duration="200" />
</animation-list>
- 在屏幕上播放幀動畫,需要布局文件有一個ImageView來顯示動畫圖片
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView iv = (ImageView) findViewById(R.id.iv);
//把動畫文件設置為imageView的背景
iv.setBackgroundResource(R.drawable.frameanimation);
AnimationDrawable ad = (AnimationDrawable) iv.getBackground();
//播放動畫
ad.start();
}
}
三、補間動畫(視圖動畫)
- 組件由原始狀態向終極狀態轉變時,為了讓過渡更自然,而自動生成的動畫叫做補間動畫。
- 主要是在Android 3.0之前
- 最大的缺陷就是不具備交互性
- 位移、旋轉、縮放、透明
public class MainActivity extends Activity {
private ImageView iv;
private TranslateAnimation ta;
private RotateAnimation ra;
private ScaleAnimation sa;
private AlphaAnimation aa;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv);
}
//位移動畫
public void translate(View v){
//創建位移動畫的對象,設置動畫的初始位置和結束位置
//10,100表示,從imageview的真實坐標的左上角 x+10,移動到 x+100的位置
//20,200表示,從imageview的真實坐標的左上角 y+20,移動到 y+200的位置
//ta = new TranslateAnimation(10, 100, 20, 200);
//Animation.RELATIVE_TO_SELF相對於自己
//對於x,表示,相對於自己,起點是 imageview的真是坐標的左上角 x+ 0.5*iv的寬度 到 x+ 2*iv的寬度
//y 就是乘以 iv 的高度,也可以相對於父控件,但是用的比較少
ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2);
//設置播放時間
ta.setDuration(2000);
//設置重復次數,播放一次,重復一次
ta.setRepeatCount(1);
//設置重復放的模式
ta.setRepeatMode(Animation.REVERSE);
//動畫播放完畢后,組件停留在動畫結束的位置上
ta.setFillAfter(true);
//播放動畫
iv.startAnimation(ta);
}
//旋轉動畫
public void rotate(View v){
//20表示開始的角度,180表示結束的角度,默認的旋轉圓心在iv左上角
//ra = new RotateAnimation(20, 180);
//指定圓心坐標,相對於自己, iv真實的坐標左上角 x+ 0.5*iv寬度, y+0.5*iv高度
ra = new RotateAnimation(20, 360,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
ra.setDuration(2000);
ra.setRepeatCount(1);
ra.setRepeatMode(Animation.REVERSE);
iv.startAnimation(ra);
}
//縮放動畫
public void scale(View v){
//sa = new ScaleAnimation(fromX, toX, fromY, toY);
//改變縮放的中心點,相對於自己的中心坐標,iv的真是坐標左上角 x+0.5*iv寬度, y+0.5*iv高度
sa = new ScaleAnimation(0.5f, 2, 0.1f, 3,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
sa.setDuration(2000);
sa.setRepeatCount(1);
sa.setRepeatMode(Animation.REVERSE);
iv.startAnimation(sa);
}
//透明動畫
public void alpha(View v){
//0為完全透明,1為完全透明
aa = new AlphaAnimation(0, 1);
aa.setDuration(2000);
aa.setRepeatCount(1);
aa.setRepeatMode(Animation.REVERSE);
iv.startAnimation(aa);
}
//所有動畫一起飛
public void fly(View v){
//創建動畫集合 false表示每個動畫的時間校准有動畫自己決定, true表示有動畫集合決定
AnimationSet set = new AnimationSet(false);
set.addAnimation(aa);
set.addAnimation(ra);
set.addAnimation(sa);
set.addAnimation(ta);
iv.startAnimation(set);
}
}
四、屬性動畫
- 補間動畫,只是一個動畫效果,組件其實還在原來的位置上,xy沒有改變。
- 屬性動畫是組件的位置發生了真實的改變,而且在動畫的過程中組件的位置是實時改變的,可以相應組件事件。
- 使用最多的就是 AnimatorSet和ObjectAnimator配合:
- 使用ObjectAnimator進行更精細化控制,只控制一個對象的一個屬性值
- 使多個ObjectAnimator組合到AnimatorSet形成一個動畫
- ObjectAnimator可以自動驅動:
- 調用setFrameDelay()設置動畫幀之間的間隙時間,調整幀率,減少動畫繪制過程中頻繁繪制,在不影響動畫效果的情況下減少CPU資源消耗
- 屬性動畫基本可以實現左右動畫
- 但是View的該屬性一定要具有 set和get 方法
1.ObjectAnimator
- 內部是通過反射機制實現的,所以該屬性一定要具有 set和get方法
- 一些常用的可以直接使用的屬性:
- translationX 和translationY: 控制View從父容器的左上角的偏移
- rotation、rotationX、rotationY : 控制View圍繞 “支點”進行2D和3D旋轉
- scaleX、scaleY : 控制View圍繞 “支點”進行2D縮放
- pivotX、pivotY : 控制View的“支點”位置,默認為View的中心點
- x、y : 這兩個屬性描述了View對象在父容器中的最終位置,它是最初左上角坐標和translationX 、translationY值的累計和
- alpha : 透明度
public class MainActivity extends Activity {
private ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv);
iv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "點不到我", 0).show();
}
});
}
public void translate(View v){
//target:動畫作用於哪個組件
//protertyName指定要改變組件的哪個屬性,
//屬性動畫是真正的改變組件的屬性的,每個組件對應一個java類,所以,protertyName是指組件Java類中具有get,set方法的成員變量
//iv.setXXX
//values 是可變參數,就是賦予屬性的新的值,可以往回走
//ObjectAnimator oa = ObjectAnimator.ofFloat(target, propertyName, values);
ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "translationX", 10, 70, 20, 100);
oa.setDuration(2000);
oa.setRepeatCount(1);
oa.setRepeatMode(ValueAnimator.REVERSE);
oa.start();
}
public void scale(View v){
ObjectAnimator oa = ObjectAnimator.ofFloat(iv,"scaleX", 1, 1.6f, 1.2f, 2);
oa.setDuration(2000);
oa.start();
}
public void alpha(View v){
ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "alpha", 0, 0.6f, 0.2f, 1);
oa.setDuration(2000);
oa.start();
}
public void rotate(View v){
ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "rotationY", 0, 180, 90, 360);
oa.setDuration(2000);
oa.setRepeatCount(1);
oa.setRepeatMode(ValueAnimator.REVERSE);
oa.start();
}
public void fly(View v){
AnimatorSet set = new AnimatorSet();
ObjectAnimator oa1 = ObjectAnimator.ofFloat(iv, "translationX", 10, 70, 20, 100);
oa1.setDuration(2000);
oa1.setRepeatCount(1);
oa1.setRepeatMode(ValueAnimator.REVERSE);
ObjectAnimator oa2 = ObjectAnimator.ofFloat(iv, "translationY", 10, 70, 20, 100);
oa2.setDuration(2000);
oa2.setRepeatCount(1);
oa2.setRepeatMode(ValueAnimator.REVERSE);
ObjectAnimator oa3 = ObjectAnimator.ofFloat(iv, "scaleX", 1, 1.6f, 1.2f, 2);
oa3.setDuration(2000);
oa3.setRepeatCount(1);
oa3.setRepeatMode(ValueAnimator.REVERSE);
ObjectAnimator oa4 = ObjectAnimator.ofFloat(iv, "rotation", 0, 180, 90, 360);
oa4.setDuration(2000);
oa4.setRepeatCount(1);
oa4.setRepeatMode(ValueAnimator.REVERSE);
//設置挨個飛
//set.playSequentially(oa1, oa2, oa3, oa4);
//設置一起飛
set.playTogether(oa1, oa2, oa3, oa4);
set.start();
}
//使用xml文件配置屬性動畫
public void xml(View v){
//加載屬性動畫文件
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.objanimator);
//設置作用於哪個組件
animator.setTarget(iv);
animator.start();
}
}
- 可以用xml配置屬性動畫只需要在res目錄下創建一個property animator屬性動畫文件
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:propertyName="translationX"
android:duration="200"
android:repeatCount="1"
android:repeatMode="reverse"
android:valueFrom="-100"
android:valueTo="100"
>
</objectAnimator>
</set>
2. 如果屬性沒有set 和 get方法的解決方法
- 方案一: 通過自定義一個屬性類或者包裝類,類間接的給這個屬性增加 get、set方法
/**
* Created at: 2016/8/5 14:21.
* by author: mwp
* 描述:使用屬性動畫時,給沒有set和get方法的屬性包裝工具
*/
public class WrapperView {
private View mTarget;
public WrapperView(View target) {
this.mTarget = target;
}
/**包裝寬度屬性*/
public int getWidth(){
return mTarget.getLayoutParams().width;
}
public void setWidth(int width){
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
//使用方法
public void use(){
WrapperView wrapper = new WrapperView(mButton);
ObjectAnimator.ofInt(wrapper,"width",500).setDuration(5000).start();
}
}
3. ValueAnimator(值動畫)
- ObjectAnimator 也是集成自ValueAnimator
- ValueAnimator本身不提供任何動畫效果,它更像一個數值發生器,用來產生具有一定規律的數字,調用者通過這個過程來執行自己特定的動畫邏輯
ValueAnimator animator = ValueAnimator.ofInt(0,100);
animator.setTarget(view);
animator.setDuration(1000).start();
//最核心的方法
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 進度百分比
float value = animation.getAnimatedFraction();
}
});
4. 動畫監聽
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha",0.5f);
anim.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) {
}
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationEnd(animation);
}
});
- 這里邊有一個重要的思想,如果一個接口需要實現的方法比較多,而通常時候又不需要實現那么多方法,導致代碼亂亂的,這個時候,可以寫一個抽象類來實現這個接口的所有方法,由於接口的實現類也是接口類型,所以使用的時候就可以只復寫這個抽象類中感興趣的方法就好了
5. View的animate方法
- 在3.0之后,Google給View增加了animate方法來直接驅動屬性動畫
view.animate()
.alpha(0)
.scaleX(1)
.x(300)
.y(200)
.setDuration(1000)
.withStartAction(new Runnable() {
@Override
public void run() {
//動畫開始
}
})
.withEndAction(new Runnable() {
@Override
public void run() {
//動畫結束
runOnUiThread(new Runnable() {
@Override
public void run() {
//執行一些主線程方法
}
});
}
})
.start();
6. Android 布局動畫
- 布局動畫是指作用在ViewGroup上,給ViewGroup增加View時添加一個動畫過渡效果
- 最簡單的布局動畫是在ViewGroup的xml中使用如下屬性打開布局動畫
android:animateLayoutChanges="true"
- 但是這個默認的動畫效果無法替換
- 使用LayoutAnimationController類來自定義一個過渡效果
LinearLayout ll = (LinearLayout)findViewById(R.id.ll);
//設置過渡動畫
ScaleAnimation sa = new ScaleAnimation(0,1,0,1);
sa.setDuration(2000);
//設置布局動畫的顯示屬性,第二個參數是每個子View顯示的delay時間
LayoutAnimationController lac = new LayoutAnimationController(sa,0.5f);
lac.setOrder(LayoutAnimationController.ORDER_NORMAL);//順序,隨機,倒序
//為ViewGroup設置布局動畫
ll.setLayoutAnimation(lac);
7. Interpolators(插值器 )
- 插值器是動畫中一個非常重要的概念,通過插值器可以定義動畫變換速率,其作用主要是控制目標變量的變化值進行對應的變化
- AccelerateDecelerateInterpolator開始與結束的地方速率改變比較慢,在中間的時候加速

- AccelerateInterpolator開始的地方速率改變比較慢,然后開始加速

- AnticipateInterpolator開始的時候向后然后向前甩

- AnticipateOvershootInterpolator開始的時候向后然后向前甩一定值后返回最后的值

- BounceInterpolator動畫結束的時候彈起

- CycleInterpolator循環播放特定的次數,速率改變沿着正弦曲線

- DecelerateInterpolator在開始的地方快然后慢

- 創建的時候,可以傳factor值,如DecelerateInterpolator(2f):

- LinearInterpolator以常量速率改變

- OvershootInterpolator向前甩一定值后再回到原來位置

- 創建的時候,可以傳tension值,OvershootInterpolator(0.8f):

五、自定義動畫
- 就是現有的透明度,旋轉,平移,縮放等行為組合起來仍然不能滿足你的話,可以自定義一些更炫的動畫
public class CustomAnim extends Animation {
private int mCenterWidth;
private int mCenterHeight;
private Camera mCamera = new Camera();
private float mRotateY = 0.0f;
@Override
public void initialize(int width,
int height,
int parentWidth,
int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
// 設置默認時長
setDuration(2000);
// 動畫結束后保留狀態
setFillAfter(true);
// 設置默認插值器
setInterpolator(new BounceInterpolator());
mCenterWidth = width / 2;
mCenterHeight = height / 2;
}
// 暴露接口-設置旋轉角度
public void setRotateY(float rotateY) {
mRotateY = rotateY;
}
@Override
protected void applyTransformation(
float interpolatedTime,
Transformation t) {
final Matrix matrix = t.getMatrix();
mCamera.save();
// 使用Camera設置旋轉的角度
mCamera.rotateY(mRotateY * interpolatedTime);
// 將旋轉變換作用到matrix上
mCamera.getMatrix(matrix);
mCamera.restore();
// 通過pre方法設置矩陣作用前的偏移量來改變旋轉中心
matrix.preTranslate(mCenterWidth, mCenterHeight);
matrix.postTranslate(-mCenterWidth, -mCenterHeight);
}
}
六、Android 5.X SVG 矢量動畫機制
- 可伸縮矢量圖形(Scalable Vector Graphics)
- 定義用於網絡的基於矢量的圖形
- 使用XML格式定義圖形
- 圖像在放大或改變尺寸的情況下其圖形質量不會有損失
- 與Bitmap對比,SVG最大的優點就是方法不失真,而且不需要為不同分辨率設計多套圖標
七、點擊view顯示隱藏其他View帶動畫的一個小例子
public class DropTest extends Activity {
private LinearLayout mHiddenView;
private float mDensity;
private int mHiddenViewMeasuredHeight;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.drop);
mHiddenView = (LinearLayout) findViewById(R.id.hidden_view);
// 獲取像素密度
mDensity = getResources().getDisplayMetrics().density;
// 獲取布局的高度
mHiddenViewMeasuredHeight = (int) (mDensity * 40 + 0.5);
}
public void llClick(View view) {
if (mHiddenView.getVisibility() == View.GONE) {
// 打開動畫
animateOpen(mHiddenView);
} else {
// 關閉動畫
animateClose(mHiddenView);
}
}
private void animateOpen(final View view) {
view.setVisibility(View.VISIBLE);
ValueAnimator animator = createDropAnimator(
view,
0,
mHiddenViewMeasuredHeight);
animator.start();
}
private void animateClose(final View view) {
int origHeight = view.getHeight();
ValueAnimator animator = createDropAnimator(view, origHeight, 0);
animator.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
view.setVisibility(View.GONE);
}
});
animator.start();
}
private ValueAnimator createDropAnimator(
final View view, int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams =
view.getLayoutParams();
layoutParams.height = value;
view.setLayoutParams(layoutParams);
}
});
return animator;
}
}