天天看點

Android動畫:模拟開關按鈕點選打開動畫(屬性動畫之平移動畫)

  在Android裡面,一些炫酷的動畫确實是很吸引人的地方,讓然看了就賞心悅目,一個好看的動畫可能會提高使用者對軟體的使用率。另外說到動畫,在Android裡面支援3種動畫: 逐幀動畫(Frame Animation)、補間動畫(Tween Animation)和屬性動畫(Property Animation),至于這幾種動畫的差別這裡不再介紹,希望開發者都能在使用的過程中體會兩者的不同。

  本文使用屬性動畫完成,說到屬性動畫,肯定要提到 JakeWharton大神寫的NineOldAndroids動畫庫,如果你的app需要在android3.0以下使用屬性動畫,那麼這個庫就很有作用了,如果隻需要在高版本使用,那麼直接使用系統提供的動畫API即可。

首先看一下本文要實作的動畫效果:手指向上移動到開關按鈕處, 然後一個點選動作,開關從關到開動畫執行,同時手指向下移動回到原來的位置

點選圖檔調轉到對應Github連結檢視動畫

Android動畫:模拟開關按鈕點選打開動畫(屬性動畫之平移動畫)

動畫的使用場景

  引導使用者去打開某個功能的開關按鈕或者去打開系統的某項設定的時候,增加動畫可以提高使用者的點選率,表達的意思也更明确

實作之前先做好如下準備工作

  1. 下載下傳nineoldandroids-2.4.0.jar的庫,放到android studio 工程目錄的libs檔案夾中

  2. 在build.gradle檔案中引入

dependencies {
    compile files('libs/nineoldandroids-2.4.0.jar')
}      

  3. 準備好相關的圖檔資源

Android動畫:模拟開關按鈕點選打開動畫(屬性動畫之平移動畫)

接下來封裝一個自定義控件來實作整個動畫

第一步:先定義一個布局檔案finger_switch_on_guide_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/switch_anim_root"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <ImageView
            android:layout_width="42dp"
            android:layout_height="25dp"
            android:background="@drawable/switch_container" />

        <ImageView
            android:id="@+id/switch_anim_circle_point"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:layout_marginLeft="2.5dp"
            android:layout_marginTop="2.5dp"
            android:background="@drawable/switch_off_circle_point" />
    </FrameLayout>

    <ImageView
        android:id="@+id/finger_switch"
        android:layout_width="34dp"
        android:layout_height="41dp"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="25dp"
        android:background="@drawable/finger_normal" />
</merge>      

布局檔案預纜長這樣:

Android動畫:模拟開關按鈕點選打開動畫(屬性動畫之平移動畫)

第二步:定義自定義控件(SwitchOnAnimView)實作整個動畫

package com.androidanimation.animationview;

import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.ImageView;

import com.androidanimation.R;
import com.androidanimation.animations.BaseAnimatorListener;
import com.androidanimation.utils.ViewUtil;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.view.ViewHelper;

/**
 * Created by popfisher on 2016/9/3.
 */

public class SwitchOnAnimView extends FrameLayout {

    private Handler mHandler = new Handler();
    /** 開關中間的圓圈View */
    private ImageView mCirclePtImgv;
    /** 手指View */
    private ImageView mFingerImgv;
    /** 手指移動的距離 */
    private float mFingerMoveDistance;
    /** 開關中間的圓圈View需要移動的距離 */
    private float mCirclePtMoveDistance;
    private static final int FINGER_ANIM_DURATION = 300;
    private static final int CIRCLE_PT_ANIM_DURATION = 500;

    private boolean isStopAnim = false;

    public SwitchOnAnimView(Context context) {
        this(context, null);
    }

    public SwitchOnAnimView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 加載布局
        LayoutInflater.from(context).inflate(R.layout.finger_switch_on_guide_layout, this, true);
        initView();
    }

    private void initView() {
        mCirclePtImgv = (ImageView) findViewById(R.id.switch_anim_circle_point);
        mFingerImgv = (ImageView) findViewById(R.id.finger_switch);

        // 下面兩個距離要根據UI布局來确定
        mFingerMoveDistance = ViewUtil.dp2px(getContext(), 20f);
        mCirclePtMoveDistance = ViewUtil.dp2px(getContext(), 17.5f);
    }

    /**
     * 啟動動畫
     */
    public void startAnim() {
        isStopAnim = false;
        // 啟動動畫之前先恢複初始狀态
        ViewHelper.setTranslationX(mCirclePtImgv, 0);
        mCirclePtImgv.setBackgroundResource(R.drawable.switch_off_circle_point);
        mFingerImgv.setBackgroundResource(R.drawable.finger_normal);
        startFingerUpAnim();
    }

    /**
     * 停止動畫
     */
    public void stopAnim() {
        isStopAnim = true;
    }

    /**
     * 中間的圈點View平移動畫
     */
    private void startCirclePointAnim() {
        if (mCirclePtImgv == null) {
            return;
        }
        ObjectAnimator circlePtAnim = ObjectAnimator.ofFloat(mCirclePtImgv, "translationX", 0, mCirclePtMoveDistance);
        circlePtAnim.setDuration(CIRCLE_PT_ANIM_DURATION);
        circlePtAnim.start();
    }

    /**
     * 手指向上移動動畫
     */
    private void startFingerUpAnim() {
        ObjectAnimator fingerUpAnim = ObjectAnimator.ofFloat(mFingerImgv, "translationY", 0, -mFingerMoveDistance);
        fingerUpAnim.setDuration(FINGER_ANIM_DURATION);
        fingerUpAnim.addListener(new BaseAnimatorListener() {
            @Override
            public void onAnimationEnd(Animator animator) {
                if (mFingerImgv == null || mHandler == null) {
                    return;
                }
                // 手指向上動畫執行完成就設定手指View背景為點選狀态的背景
                mFingerImgv.setBackgroundResource(R.drawable.finger_click);
                // 點選之後為了提現停頓一下的感覺,延遲200毫秒執行其他動畫
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (mCirclePtImgv == null || mHandler == null) {
                            return;
                        }
                        // 将中間圓圈View背景設定為開關打開狀态然後開始向右平移
                        mCirclePtImgv.setBackgroundResource(R.drawable.switch_on_circle_point);
                        startCirclePointAnim();
                        // 延遲100毫秒啟動手指向下平移動畫
                        mHandler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                // 手指向下移動開始時設定手指背景為正常的狀态
                                if (mFingerImgv != null) {
                                    mFingerImgv.setBackgroundResource(R.drawable.finger_normal);
                                }
                                startFingerDownAnim();
                            }
                        }, 100);
                    }
                }, 200);
            }
        });
        fingerUpAnim.start();
    }

    /**
     * 手指向下移動動畫
     */
    private void startFingerDownAnim() {
        if (mFingerImgv == null) {
            return;
        }
        ObjectAnimator fingerDownAnim = ObjectAnimator.ofFloat(mFingerImgv, "translationY", -mFingerMoveDistance, 0);
        fingerDownAnim.setDuration(FINGER_ANIM_DURATION);
        fingerDownAnim.addListener(new BaseAnimatorListener() {
            @Override
            public void onAnimationEnd(Animator animator) {
                // 手指向下移動動畫完成,整個動畫流程結束,重新開始下一次流程,循環執行動畫,間隔1秒
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (isStopAnim) {
                            return;
                        }
                        startAnim();
                    }
                }, 1000);
            }
        });
        fingerDownAnim.start();
    }
}      

最後一步:就是找個載體把SwitchOnAnimView加進去,調用其startAnim方法執行動畫,這裡在一個Activity中把播放此動畫

定義activity布局檔案activity_finger_switchon_anim.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_animation_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
    <com.androidanimation.animationview.SwitchOnAnimView
        android:id="@+id/switch_on_anim_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>      

定義并實作Activity:FingerSwitchOnAnimActivity

package com.androidanimation;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;

import com.androidanimation.animationview.SwitchOnAnimView;

public class FingerSwitchOnAnimActivity extends Activity {

    private Handler mHandler = new Handler();
    private SwitchOnAnimView mSwitchOnAnimView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_finger_switchon_anim);
        mSwitchOnAnimView = (SwitchOnAnimView) findViewById(R.id.switch_on_anim_view);
          
     mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mSwitchOnAnimView.startAnim();
            }
        }, 500);      
} }      

動畫實作總結:

  掌握Android的動畫并不難,難的時候怎麼實作一些複雜的動畫,這裡總結一下實作複雜動畫的幾個步驟。

  1. 動畫分解:任何複雜的動畫都可以分解為很多個原子動畫的組合

  2. 動畫銜接時機分析:複雜動畫分解為很多個原子動畫之後,要重新銜接起來

            這裡其實就是各個原子動畫的執行時機,誰先誰後還是同時執行

  3. 實作原子動畫:将拆解的原子動畫依次實作

  4. 動畫組裝:上面都準備好之後,将原子動畫按照一定的規律組裝串聯起來,整個複雜的動畫就開始工作了

  原子動畫:本文指不能再繼續拆分的動畫

  拿本文中的動畫來說,動畫可以分為四個:

  a. 手指向上平移動畫

  b. 手指點選操作(這裡不是動畫,也可以當做一個簡單的動畫吧)

  c. 開關按鈕原點向右平移動畫

  d. 手指向下平移動畫。

  本文動畫執行時機為:

  a 先執行,a 執行完成之後立即執行 b,b 執行完成之後等待200ms執行 c(展現點選效果)

  c 執行開始100ms後開始執行 d

  

  動畫的分解和動畫銜接時機分析是不太容易的事,因為憑借肉眼有時候沒法觀察出來,是以播放動畫的時候要放慢來看,如果還是不能看出來,最好還是要找公司的UI同僚協助分析。因為我們能簡單的區分平移動畫,縮放動畫這種簡單,但是我們不能區分那種正弦算法動畫或者是另外一些其他算法控制的動畫。本文中的動畫相對還是比較簡單,實作起來也比較容易,但是思想确實一樣的。

源碼下載下傳位址:https://github.com/PopFisher/AndroidAnimationDemos

繼續閱讀