Android動畫之屬性動畫(下)
前言
前面我們已經完整的講述了屬性動畫的實現,我們已經學會了怎麼實現動畫,如果沒有屬性我們也學會了怎麼新增屬性,還學習了用ValueAnimator來實現動畫。
Evaluator
這裡我們來學習剩下的屬性,首先我們來看看Evaluator,Evaluator是什麼?他有什麼用?
Evaluator翻譯為求值器,或者表示式,反正只可意會不可言傳,就是給一個表示式,計算對應的值,他主要有以下屬性:
1, 返回動畫當前時間點的屬性值
2,全部繼承自TypeEvaluator
3,系統實現了IntEvaluator,FloatEvaluator,ArgbEvaluator
4,Api21實現了PointFEvaluator
我們解釋一下上面的屬性,首先表示式可以計算當前時間點的值,並且所有的Evaluator都繼承自TypeEvaluator,從這一點就可以知道,我們可以自己來實現Evaluator,目前已經有幾個預設的實現,我們前面所實現的動畫都是ofFloat,ofInt沒有實現過其他型別的動畫,這是因為float,int的表示式系統已經預設實現,如果有一個新的表示式不屬於上面的任何屬性,我們怎麼辦?
我們可以自定義Evaluator,我們接下來實現一個自定義的Evaluator。我們來實現一個拋物線的效果,效果如下圖:
一個view從0這個位置移動到600這個位置,一個拋物線的效果,表示式如下:
我們根據上述的表示式來實現動畫,動畫下過如下:
private void setCustomEvaluator() {
ValueAnimator animator = new ValueAnimator();
float offsetX = love.getX();
float offsetY = love.getY();
animator.setDuration(2000);
animator.setObjectValues(new PointF(offsetX, offsetY), new PointF(TRANS_X + offsetX, offsetY));
animator.setEvaluator(new TypeEvaluator<PointF>() {
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
PointF pointF = new PointF();
float d = fraction * TRANS_X;
pointF.x = startValue.x + d;
pointF.y = startValue.y + (1.0f / 150f) * d * d - 4 * d;
return pointF;
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointF = (PointF) animation.getAnimatedValue();
love.setX(pointF.x);
love.setY(pointF.y);
}
});
animator.start();
}
這裡我們需要注意的幾點是第一個設定好Evaluator,第二設定ObjectValues,之後在onAnimationUpdate回撥中獲取得到的值,Evaluator中就是我們根據我們的函式計算得到的值。這樣我們的自定義動畫就已經實現完成了。
Keyframe
這裡的第二個需要講解的知識點是Keyframe,Keyframe的意思如下:
1, KeyFrame是一個時間/值對
2, KeyFrame之間可以定義不同的Interpolator
這裡我們用Keyframe來實現動畫:
private void keyFrame() {
Keyframe keyframe1 = Keyframe.ofFloat(0f, 0f);
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 360f);
Keyframe keyframe3 = Keyframe.ofFloat(1.0f, 0f);
PropertyValuesHolder pvh = PropertyValuesHolder.ofKeyframe("rotation", keyframe1, keyframe2, keyframe3);
ObjectAnimator rotate = ObjectAnimator.ofPropertyValuesHolder(love, pvh);
rotate.setDuration(2000);
rotate.start();
}
我們先定義幾個Keyframe,最後將Keyframe使用到PropertyValuesHolder中,最後又將PropertyValuesHolder使用到ObjectAnimator中,上訴定義了三個Keyframe,比如我們實現一個旋轉的動畫,第一個關鍵幀為Keyframe.ofFloat(0f, 0f)表示第0時角度為0,剩下的一半的時間角度360,最終位置的角度為0度。
其實這裡的Keyframe我們前面已經有過使用,比如:
ObjectAnimator scaleX = ObjectAnimator.ofFloat(love, "scaleX", 1.0f, 2.0f);
這裡的1.0f與2.0f就表示開始的關鍵幀與結束的關鍵幀,只是這裡只有兩幀。
XML實現
前面我們已經完全用程式碼實現了動畫,但是動畫有兩種實現方式,還有一種實現方式就是用xml來實現。接下來我們就用xml來實現一個動畫:
1:首先定義一個動畫資源,放置到res/animator資料夾下,定義一個trans_scale.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially">
<objectAnimator
android:duration="2000"
android:propertyName="translationY"
android:valueTo="-200f"
android:valueType="floatType"></objectAnimator>
<set android:ordering="together">
<objectAnimator
android:duration="2000"
android:propertyName="scaleX"
android:valueTo="2.0f"
android:valueType="floatType"></objectAnimator>
<objectAnimator
android:duration="2000"
android:propertyName="scaleY"
android:valueTo="2.0f"
android:valueType="floatType"></objectAnimator>
</set>
</set>
這裡如果是set我們需要設定ordering屬性,主要有together並且與sequentially序列,每一屬性需要設定valueType。
2:程式碼中進行載入:
private void compositeAnimXml(){
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.trans_scale);
animator.setTarget(love);
animator.start();
}
這樣我們就用xml實現了動畫。
Layout Animations
Layout Animations為佈局動畫,控制view加入與刪除自身的動畫與周圍view的動畫。主要有以下五種效果:
1, LayoutTransition.APPEARING,對出現的view設定動畫
2, LayoutTransition.CHANGE_APPEARING,其他view設定動畫
3, LayoutTransition.DISAPPEARING,對消失的view設定動畫
4, LayoutTransition. CHANGE_DISAPPEARING ,其他view設定動畫
5, LayoutTransition. CHANGE ,其他view設定動畫
上面的五種效果,後面都已經分別做了解釋,這裡我們用一個demo來演示一下:
定義佈局檔案如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/root"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.netease.study.ui.animation.LayoutAnimationActivity">
<Button
android:id="@+id/add_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="addView"/>
</LinearLayout>
程式碼實現如下:
public class LayoutAnimationActivity extends AppCompatActivity implements View.OnClickListener {
public static void start(Context context) {
Intent intent = new Intent();
intent.setClass(context, LayoutAnimationActivity.class);
context.startActivity(intent);
}
LinearLayout root;
int i = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout_animation);
findViews();
setViewListener();
}
private void findViews() {
root = (LinearLayout) findViewById(R.id.root);
root.setLayoutTransition(getLayoutTransition());
}
private LayoutTransition getLayoutTransition() {
LayoutTransition transition = new LayoutTransition();
transition.setAnimator(LayoutTransition.APPEARING, transition.getAnimator(LayoutTransition.APPEARING));
transition.setAnimator(LayoutTransition.CHANGE_APPEARING, transition.getAnimator(LayoutTransition
.CHANGE_APPEARING));
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, transition.getAnimator(LayoutTransition
.CHANGE_DISAPPEARING));
transition.setAnimator(LayoutTransition.DISAPPEARING, transition.getAnimator(LayoutTransition.DISAPPEARING));
transition.setAnimator(LayoutTransition.CHANGING, transition.getAnimator(LayoutTransition.CHANGING));
return transition;
}
private void setViewListener() {
findViewById(R.id.add_view).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.add_view:
addViews();
break;
}
}
private void addViews() {
final Button button = new Button(this);
button.setText("button=" + i++);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
root.removeView(button);
}
});
root.addView(button);
}
}
private void compositeAnimXml(){
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.love_f);
animator.setTarget(love);
animator.start();
}
我們對線性佈局新增button,對佈局設定LayoutTransition,這樣就實現了佈局動畫,最常用的效果就是比如listview中載入item時設定動畫。
轉場動畫
在文章的最後我們來講述一下轉場動畫,什麼是轉場動畫,通俗的來說,就是頁面切換的效果。
為什麼要設定?
由於不同的手機開啟關閉的效果不一致,各個手機都有不同的效果,效果不是很好,我們可以統一來控制頁面開啟關閉的效果,保證所有手機效果的統一性,提升使用者體驗。
怎麼設定?
首先定義進入退出的動畫,第一個種方式就是採用style來實現,主要是來複寫android:windowAnimationStyle節點,系統預設有如下可以設定的屬性:
<style name="Animation.Activity">
<item name="activityOpenEnterAnimation">@anim/activity_open_enter</item>
<item name="activityOpenExitAnimation">@anim/activity_open_exit</item>
<item name="activityCloseEnterAnimation">@anim/activity_close_enter</item>
<item name="activityCloseExitAnimation">@anim/activity_close_exit</item>
<item name="taskOpenEnterAnimation">@anim/task_open_enter</item>
<item name="taskOpenExitAnimation">@anim/task_open_exit</item>
<item name="launchTaskBehindTargetAnimation">@anim/launch_task_behind_target</item>
<item name="launchTaskBehindSourceAnimation">@anim/launch_task_behind_source</item>
<item name="taskCloseEnterAnimation">@anim/task_close_enter</item>
<item name="taskCloseExitAnimation">@anim/task_close_exit</item>
<item name="taskToFrontEnterAnimation">@anim/task_open_enter</item>
<item name="taskToFrontExitAnimation">@anim/task_open_exit</item>
<item name="taskToBackEnterAnimation">@anim/task_close_enter</item>
<item name="taskToBackExitAnimation">@anim/task_close_exit</item>
<item name="wallpaperOpenEnterAnimation">@anim/wallpaper_open_enter</item>
<item name="wallpaperOpenExitAnimation">@anim/wallpaper_open_exit</item>
<item name="wallpaperCloseEnterAnimation">@anim/wallpaper_close_enter</item>
<item name="wallpaperCloseExitAnimation">@anim/wallpaper_close_exit</item>
<item name="wallpaperIntraOpenEnterAnimation">@anim/wallpaper_intra_open_enter</item>
<item name="wallpaperIntraOpenExitAnimation">@anim/wallpaper_intra_open_exit</item>
<item name="wallpaperIntraCloseEnterAnimation">@anim/wallpaper_intra_close_enter</item>
<item name="wallpaperIntraCloseExitAnimation">@anim/wallpaper_intra_close_exit</item>
<item name="fragmentOpenEnterAnimation">@animator/fragment_open_enter</item>
<item name="fragmentOpenExitAnimation">@animator/fragment_open_exit</item>
<item name="fragmentCloseEnterAnimation">@animator/fragment_close_enter</item>
<item name="fragmentCloseExitAnimation">@animator/fragment_close_exit</item>
<item name="fragmentFadeEnterAnimation">@animator/fragment_fade_enter</item>
<item name="fragmentFadeExitAnimation">@animator/fragment_fade_exit</item>
</style>
這裡定義了頁面進入的動畫,頁面退出的動畫等等,由於不同的手機預設實現的效果不同,因此我們可以複寫上述這些屬性,達到統一的效果。
我們首先定義如下三個動畫:
水平進入的動畫base_slide_right_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:interpolator="@android:anim/decelerate_interpolator" android:duration="300"
android:fromXDelta="100.0%" android:toXDelta="0.0%" />
</set>
水平退出的動畫base_slide_right_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="0.0%"
android:interpolator="@android:anim/accelerate_interpolator"
android:toXDelta="100.0%"/>
</set>
原地不動的動畫base_stay_orig.xml
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="0.0%"
android:interpolator="@android:anim/decelerate_interpolator"
android:toXDelta="0.0%"/>
</set>
之後我們複寫android:windowAnimationStyle屬性,我們定義一個新的style,設定到android:windowAnimationStyle中:
<style name="WindowAnimation" parent="@android:style/Animation">
<item name="android:activityOpenEnterAnimation">@anim/base_slide_right_in</item>
<item name="android:activityOpenExitAnimation">@anim/base_stay_orig</item>
<item name="android:activityCloseEnterAnimation">@anim/base_stay_orig</item>
<item name="android:activityCloseExitAnimation">@anim/base_slide_right_out</item>
<item name="android:taskOpenEnterAnimation">@anim/base_slide_right_in</item>
<item name="android:taskOpenExitAnimation">@anim/base_stay_orig</item>
<item name="android:taskCloseEnterAnimation">@anim/base_stay_orig</item>
<item name="android:taskCloseExitAnimation">@anim/base_slide_right_out</item>
<item name="android:taskToFrontEnterAnimation">@anim/base_slide_right_in</item>
<item name="android:taskToFrontExitAnimation">@anim/base_stay_orig</item>
<item name="android:taskToBackEnterAnimation">@anim/base_stay_orig</item>
<item name="android:taskToBackExitAnimation">@anim/base_slide_right_out</item>
</style>
這樣頁面轉場的動畫就已經實現完成了,不過遺憾的是,有的手機不起作用。。。。我們就需要採用另外一種方式來實現了,我們呼叫overridePendingTransition:
overridePendingTransition函式如下:
public void overridePendingTransition(int enterAnim, int exitAnim) {
try {
ActivityManagerNative.getDefault().overridePendingTransition(
mToken, getPackageName(), enterAnim, exitAnim);
} catch (RemoteException e) {
}
}
定義一個進入的動畫,和一個退出的動畫
這裡需要注意的是overridePendingTransition有非常嚴格的呼叫條件,它必須在startActivity或者finish之後呼叫
呼叫方式如下:
@Override
public void finish() {
super.finish();
customExit();
}
private void customExit() {
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
總結
到此屬性動畫就已經實現完成了,我們學習了動畫怎麼實現,怎麼自定義Evaluator,怎麼實現佈局動畫。