目錄
- 1. View 影片
- 1.1 Android 影片的分類有哪些?
- 1.2 Android 影片的特點是什么?
- 1.3 Tween Animation 補間影片中的軸點是什么作用?
- 1.4 自定義 View 影片的步驟是什么?
- 2. View 影片的特殊使用場景
- 2.1 如何控制 ViewGroup 中子元素的出場效果?
- 2.2 如何自定義 Activity 的切換效果?
- 2.3 如何自定義 Fragment 的切換效果?
- 3. 屬性影片
- 3.1 插值器和估值器的作用分別是什么?
- 3.2 如何使用屬性影片對任意屬性做影片?
- 4. 使用影片的注意事項
- 參考
1. View 影片
1.1 Android 影片的分類有哪些?
總共有兩類影片:View Animation(視圖影片) 和 Property Animation(屬性影片),其中,View Animation 又包括 Tween Animation(補間影片)和 Frame Animation(逐幀影片);Property Animation 又包括 ValueAnimator 和 ObjectAnimator,
1.2 Android 影片的特點是什么?
| 影片 | 特點 |
|---|---|
| Tween Animation(補間影片) | 通過對控制元件不斷地執行影像變換(平移、縮放、旋轉、透明度)從而產生影片,是一種漸進式影片,支持自定義, |
| Frame Animation(逐幀影片) | 通過順序地播放一系列影像從而產生影片效果 |
| ValueAnimator | 不會對控制元件執行任何操作,只會在監聽回呼中回傳值得漸變程序 |
| ObjectAnimator | 通過動態地改變控制元件得屬性從而達到影片效果 |
1.3 Tween Animation 補間影片中的軸點是什么作用?
在縮放影片和旋轉影片中有軸點的概念,默認情況下軸點是 View 的中心點,
對于縮放影片來來說,如果軸點是 View 的中心點,在水平方向上會導致 View 向左右兩個方向同時縮放;如果把軸點設為 View 的右邊界,那么 View 只會向左邊進行縮放,
對于旋轉影片來說,View 是圍繞著軸點進行旋轉的,如果軸點是 View 的中心點,那么 View 的旋轉看起來就是穩定地旋轉,否則會形成一種偏心旋轉效果,
1.4 自定義 View 影片的步驟是什么?
- 集成
Animation抽象類; - 重寫它的
initialize和applyTransformation方法; - 在
initialize方法中做一些初始化作業; - 在
applyTransformation方法中進行相應的矩陣變換,
這里展示一個來自 ApiDemos 里的翻轉影片效果:
public class FlipAnimation extends Animation {
private float mStartDegree;
private float mEndDegree;
private float mCenterX;
private float mCenterY;
private Camera mCamera;
private boolean mFlag;
private float mZDistance = 400f;
public FlipAnimation(float startDegree, float endDegree, float centerX, float centerY, boolean flag) {
mStartDegree = startDegree;
mEndDegree = endDegree;
mCenterX = centerX;
mCenterY = centerY;
mFlag = flag;
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
final float startDegree = mStartDegree;
final float endDegree = mEndDegree;
final float zDistance = mZDistance;
final float centerX = mCenterX;
final float centerY = mCenterY;
float currDegree = startDegree + interpolatedTime * (endDegree - startDegree);
Camera camera = mCamera;
Matrix matrix = t.getMatrix();
camera.save();
if (mFlag) {
camera.translate(0, 0, interpolatedTime * zDistance);
} else {
camera.translate(0,0, (1 - interpolatedTime) * zDistance);
}
camera.rotateX(currDegree);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
頁面布局如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/rl"
xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/cab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/junk_cab"
android:layout_centerHorizontal="true" />
<ImageView
android:id="@+id/junk_ok"
android:visibility="invisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:src="@drawable/ok"
android:layout_below="@id/cab"
android:layout_centerHorizontal="true" />
<ImageView
android:id="@+id/bin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:src="@drawable/junk_bin"
android:layout_below="@id/cab"
android:layout_centerHorizontal="true" />
</RelativeLayout>
頁面代碼如下:
public class CustomFlipAnimationActivity extends Activity {
private RelativeLayout mRl;
private ImageView mIvCab;
private ImageView mIvJunkOk;
private ImageView mIvBin;
public static void start(Context context) {
Intent starter = new Intent(context, CustomFlipAnimationActivity.class);
context.startActivity(starter);
}
private boolean mInvisToVis = true;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_flip_animation);
initViews();
mRl.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Rect rect = new Rect();
mRl.getHitRect(rect);
final float centerX = rect.centerX();
final float centerY = rect.centerY();
FlipAnimation flipAnimation = new FlipAnimation(0.0F, 90.0F, centerX, centerY, true);
flipAnimation.setDuration(600L);
flipAnimation.setInterpolator(new AccelerateInterpolator());
flipAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public final void onAnimationStart(Animation paramAnonymousAnimation) {
}
@Override
public final void onAnimationEnd(Animation paramAnonymousAnimation) {
if (mInvisToVis) {
mIvJunkOk.setVisibility(View.VISIBLE);
mIvCab.setVisibility(View.INVISIBLE);
mIvBin.setVisibility(View.INVISIBLE);
} else {
mIvJunkOk.setVisibility(View.INVISIBLE);
mIvCab.setVisibility(View.VISIBLE);
mIvBin.setVisibility(View.VISIBLE);
}
mInvisToVis = !mInvisToVis;
FlipAnimation localb = new FlipAnimation(270.0F, 360.0F, centerX, centerY, false);
localb.setDuration(600L);
localb.setFillAfter(true);
localb.setInterpolator(new DecelerateInterpolator());
localb.setAnimationListener(null);
mRl.startAnimation(localb);
}
@Override
public final void onAnimationRepeat(Animation paramAnonymousAnimation) {
}
});
mRl.startAnimation(flipAnimation);
}
});
}
private void initViews() {
mRl = (RelativeLayout) findViewById(R.id.rl);
mIvCab = (ImageView) findViewById(R.id.cab);
mIvJunkOk = (ImageView) findViewById(R.id.junk_ok);
mIvBin = (ImageView) findViewById(R.id.bin);
}
}
翻轉效果如下:

2. View 影片的特殊使用場景
2.1 如何控制 ViewGroup 中子元素的出場效果?
使用 <layoutAnimation>,一般用于 ListView,
2.2 如何自定義 Activity 的切換效果?
使用 overridePendingTransition(int enterAnim, int exitAnim) 方法,這個方法必須在 startActivity(Intent intent) 或者 finish() 之后被呼叫才有效,
當啟動一個 Activity 時,overridePendingTransition(int enterAnim, int exitAnim) 方法中的 enterAnim 表示給新打開的 Actiivty 添加入場影片效果,exitAnim 表示給當前的 Activity 添加出場影片效果,
當關閉一個 Activity 時,overridePendingTransition(int enterAnim, int exitAnim) 方法中的 enterAnim 表示恢復到前臺的 Actiivty 添加入場影片效果,exitAnim 表示給關閉的 Activity 添加出場影片效果,
也就是說,overridePendingTransition(int enterAnim, int exitAnim) 為即將到來的 Activity 添加入場影片效果,為即將退出的 Activity 添加出場影片效果,
需要注意的是,enterAnim 和 exitAnim 都是定義在 anim 目錄下的影片資源檔案的 id,如果不希望有任何頁面切換效果,可以把這個引數都傳入 0 即可,
2.3 如何自定義 Fragment 的切換效果?
使用 setCustomAnimations(@AnimRes int enter, @AnimRes int exit) 這個方法,這里同樣是定義在 anim 目錄下的影片資源檔案的 id,
3. 屬性影片
3.1 插值器和估值器的作用分別是什么?
插值器(或者說時間插值器)需要實作 Interpolator 介面或者 TimeInterpolator 介面:
/**
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
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);
}
public interface Interpolator extends TimeInterpolator {
// A new interface, TimeInterpolator, was introduced for the new android.animation
// package. This older Interpolator interface extends TimeInterpolator so that users of
// the new Animator-based animations can use either the old Interpolator implementations or
// new classes that implement TimeInterpolator directly.
}
需要說明的是:
getInterpolation 方法的 input 引數與我們設定的任何值都沒有關系,只與時間有關,隨著時間的推移,影片的進度也自然地增加,這個值也會從 0 增加到 1.0,所以,這個值的取值范圍是 [0,1.0],其中 0 表示影片剛開始,1.0 表示影片結束了,0.5 表示影片進行到一半了,
getInterpolation 方法的回傳值表示當前實際想要顯示的進度,這個值可以超過 1.0,也可以小于 0,超過 1.0 表示超過了目標值,小于 0 表示小于開始位置,
通過 setInterpolator(TimeInterpolator value) 方法設定插值器,這是策略模式的應用了,
估值器要實作 TypeEvaluator 介面:
/**
* Interface for use with the {@link ValueAnimator#setEvaluator(TypeEvaluator)} function. Evaluators
* allow developers to create animations on arbitrary property types, by allowing them to supply
* custom evaluators for types that are not automatically understood and used by the animation
* system.
*
* @see ValueAnimator#setEvaluator(TypeEvaluator)
*/
public 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);
}
根據當前實際顯示的進度,影片開始屬性值,影片結束屬性值,來計算出當前要顯示的屬性值,其中,fraction 的值就是插值器的 getInterpolation 方法的回傳值,
3.2 如何使用屬性影片對任意屬性做影片?
-
嘗試給控制元件提供該屬性的 get 和 set 方法,這樣屬性影片會根據外界傳遞的該屬性的初始值和最終值,以影片的效果多次去呼叫 set 方法,每次傳遞給 set 方法的值都不一樣,這樣隨著時間的推移,所傳遞的值越來越接近最終值,
需要說明的是,set 方法是必須的,get 方法只有在影片沒有傳遞初始值的時候才需要,因為此時系統需要通過 get 方法去獲取初始值,如果沒有 get 方法則會取影片引數型別的默認值作為初始值,當無法獲取影片引數型別的默認值時,則會直接崩潰,
-
如果不能給直接給控制元件添加該屬性的 get 和 set 方法,則可以使用一個類來包裝原始的控制元件物件,間接地為其提供 get 和 set 方法;
-
采用
ValueAnimator,監聽影片程序,自己實作屬性的改變,
下面使用具體的例子來說明:
使用影片在 5s 內把 Button 的寬度增加到 500 px,
我們先采用繼承 Button 來自定義一個 MyButton 來實作:
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setBreadth(int width) {
getLayoutParams().width = width;
requestLayout();
}
}
使用屬性影片來實作影片效果,代碼如下:
ObjectAnimator.ofInt(mBtn5, "breadth", 500).setDuration(2000).start();
效果如下:

可以看到,影片開始的寬度是從 0 開始的,這是因為我們并沒有提供 breadth 屬性的 get 方法,而 breadth 引數的默認值是 0,所以影片開始的寬度是 0,那該怎么辦呢?添加對應的 get 方法即可,
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setBreadth(int width) {
getLayoutParams().width = width;
requestLayout();
}
public int getBreadth() {
return getWidth();
}
}
代碼不用改動,再次查看效果:

我們還可以使用一個類包裝 Button 物件來實作:
private static class ViewWrapper {
private View mTarget;
private ViewWrapper(View target) {
mTarget = target;
}
public int getBreadth() {
return mTarget.getLayoutParams().width;
}
public void setBreadth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
點擊按鈕時的代碼如下:
ViewWrapper viewWrapper = new ViewWrapper(mBtn3);
ObjectAnimator.ofInt(viewWrapper, "breadth", mBtn3.getWidth(), 500).setDuration(2000).start();
同樣可以實作效果,
現在需求變化了:使用影片在 5s 內把 Button 的寬度和高度都增加到 500 px,
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setSize(PointF point) {
getLayoutParams().width = (int) point.x;
getLayoutParams().height = (int) point.y;
requestLayout();
}
}
點擊按鈕的代碼如下:
ObjectAnimator.ofObject(mBtn6, "size", new PointFEvaluator(), new PointF(500F,500F)).setDuration(2000).start();
運行程式,崩潰了,日志如下:
W/PropertyValuesHolder( 6601): Method getSize() with type null not found on target class class com.wzc.chapter_7.MyButton
D/AndroidRuntime( 6601): Shutting down VM
E/AndroidRuntime( 6601): FATAL EXCEPTION: main
E/AndroidRuntime( 6601): Process: com.wzc.chapter_7, PID: 6601
E/AndroidRuntime( 6601): java.lang.NullPointerException: Attempt to read from field 'float android.graphics.PointF.x' on a null object reference
E/AndroidRuntime( 6601): at android.animation.PointFEvaluator.evaluate(PointFEvaluator.java:73)
E/AndroidRuntime( 6601): at android.animation.PointFEvaluator.evaluate(PointFEvaluator.java:23)
E/AndroidRuntime( 6601): at android.animation.KeyframeSet.getValue(KeyframeSet.java:202)
E/AndroidRuntime( 6601): at android.animation.PropertyValuesHolder.calculateValue(PropertyValuesHolder.java:1017)
E/AndroidRuntime( 6601): at android.animation.ValueAnimator.animateValue(ValueAnimator.java:1561)
E/AndroidRuntime( 6601): at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:987)
E/AndroidRuntime( 6601): at android.animation.ValueAnimator.setCurrentFraction(ValueAnimator.java:692)
E/AndroidRuntime( 6601): at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:655)
E/AndroidRuntime( 6601): at android.animation.ValueAnimator.start(ValueAnimator.java:1087)
E/AndroidRuntime( 6601): at android.animation.ValueAnimator.start(ValueAnimator.java:1106)
E/AndroidRuntime( 6601): at android.animation.ObjectAnimator.start(ObjectAnimator.java:852)
E/AndroidRuntime( 6601): at com.wzc.chapter_7.PropertyAnimationActivity$6.onClick(PropertyAnimationActivity.java:104)
E/AndroidRuntime( 6601): at android.view.View.performClick(View.java:7509)
E/AndroidRuntime( 6601): at android.view.View.performClickInternal(View.java:7486)
E/AndroidRuntime( 6601): at android.view.View.access$3600(View.java:841)
E/AndroidRuntime( 6601): at android.view.View$PerformClick.run(View.java:28720)
E/AndroidRuntime( 6601): at android.os.Handler.handleCallback(Handler.java:938)
E/AndroidRuntime( 6601): at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime( 6601): at android.os.Looper.loop(Looper.java:236)
E/AndroidRuntime( 6601): at android.app.ActivityThread.main(ActivityThread.java:8059)
E/AndroidRuntime( 6601): at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime( 6601): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
E/AndroidRuntime( 6601): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)
從日志可以看到,是因為缺少 getSize 方法,
添加 getSize 方法:
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setSize(PointF point) {
getLayoutParams().width = (int) point.x;
getLayoutParams().height = (int) point.y;
requestLayout();
}
public PointF getSize() {
return new PointF(getWidth(), getHeight());
}
}
運行程式,效果如下:

4. 使用影片的注意事項
- 幀影片 OOM 問題:對于幀影片來說,當圖片數量較多且圖片尺寸較大時就很容易出現 OOM 問題,這時我們要減少圖片數量,減小圖片尺寸,或者采用其他影片來實作,
- 無限回圈屬性影片記憶體泄漏問題:無限回圈的屬性影片,在
Activity退出時要及時取消影片,否則影片會無限回圈,從而導致View控制元件無法釋放,進一步導致整個Activity無法釋放,最終引起記憶體泄漏,作為對比,View 影片不存在這種問題, - View 影片完成后 View 無法隱藏問題:View 影片是對 View 的影像做影片,并不是真正地改變 View 的狀態,所以會出現影片完成后設定
setVisibility(View.GONE)失效的問題,這個時候只要呼叫View.clearAnimation()清除 View 影片即可解決此問題, - 使用 px 影片在不同設備上影片效果不同的問題:盡量使用 dp,而不要使用 px,
- View 影片平移后,控制元件點擊事件仍在原位置的問題:可以改用屬性影片來實作,
- 影片不流暢問題:建議開啟硬體加速,這樣可以提供影片的流暢性,
- onAnimationEnd沒有回呼的問題:
onAnimationEnd可能因各種例外沒被回呼,建議加上超時保護或者通過postDelay替代onAnimationEnd,參考:onAnimationEnd is not getting called, onAnimationStart works fine,
參考
- Android setVisibility(View.GONE)無效的問題及原因分析
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/352255.html
標籤:其他
上一篇:小程式仿instagram互動(附帶長串列性能優化處理)
下一篇:反編譯獲取apk安裝包源代碼步驟
