Android動(dòng)畫 實(shí)現(xiàn)開關(guān)按鈕動(dòng)畫(屬性動(dòng)畫之平移動(dòng)畫)實(shí)例代碼
Android動(dòng)畫 實(shí)現(xiàn)開關(guān)按鈕動(dòng)畫(屬性動(dòng)畫之平移動(dòng)畫),最近做項(xiàng)目,根據(jù)項(xiàng)目需求,有一個(gè)這樣的功能,實(shí)現(xiàn)類似開關(guān)的動(dòng)畫效果,經(jīng)過自己琢磨及上網(wǎng)查找資料,終于解決了,這里就記錄下:
在Android里面,一些炫酷的動(dòng)畫確實(shí)是很吸引人的地方,讓然看了就賞心悅目,一個(gè)好看的動(dòng)畫可能會(huì)提高用戶對(duì)軟件的使用率。另外說到動(dòng)畫,在Android里面支持3種動(dòng)畫: 逐幀動(dòng)畫(Frame Animation)、補(bǔ)間動(dòng)畫(Tween Animation)和屬性動(dòng)畫(Property Animation),至于這幾種動(dòng)畫的區(qū)別這里不再介紹,希望開發(fā)者都能在使用的過程中體會(huì)兩者的不同。
本文使用屬性動(dòng)畫完成,說到屬性動(dòng)畫,肯定要提到 JakeWharton大神寫的NineOldAndroids動(dòng)畫庫,如果你的app需要在android3.0以下使用屬性動(dòng)畫,那么這個(gè)庫就很有作用了,如果只需要在高版本使用,那么直接使用系統(tǒng)提供的動(dòng)畫API即可。
首先看一下本文要實(shí)現(xiàn)的動(dòng)畫效果:手指向上移動(dòng)到開關(guān)按鈕處, 然后一個(gè)點(diǎn)擊動(dòng)作,開關(guān)從關(guān)到開動(dòng)畫執(zhí)行,同時(shí)手指向下移動(dòng)回到原來的位置
點(diǎn)擊圖片調(diào)轉(zhuǎn)到對(duì)應(yīng)鏈接查看動(dòng)畫
動(dòng)畫的使用場景
引導(dǎo)用戶去打開某個(gè)功能的開關(guān)按鈕或者去打開系統(tǒng)的某項(xiàng)設(shè)置的時(shí)候,增加動(dòng)畫可以提高用戶的點(diǎn)擊率,表達(dá)的意思也更明確
實(shí)現(xiàn)之前先做好如下準(zhǔn)備工作
1. 下載nineoldandroids-2.4.0.jar的庫,放到android studio 工程目錄的libs文件夾中
2. 在build.gradle文件中引入
dependencies { compile files('libs/nineoldandroids-2.4.0.jar')}
3. 準(zhǔn)備好相關(guān)的圖片資源

接下來封裝一個(gè)自定義控件來實(shí)現(xiàn)整個(gè)動(dòng)畫
第一步:先定義一個(gè)布局文件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>
布局文件預(yù)纜長這樣:

第二步:定義自定義控件(SwitchOnAnimView)實(shí)現(xiàn)整個(gè)動(dòng)畫
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();
/** 開關(guān)中間的圓圈View */
private ImageView mCirclePtImgv;
/** 手指View */
private ImageView mFingerImgv;
/** 手指移動(dòng)的距離 */
private float mFingerMoveDistance;
/** 開關(guān)中間的圓圈View需要移動(dòng)的距離 */
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);
// 下面兩個(gè)距離要根據(jù)UI布局來確定
mFingerMoveDistance = ViewUtil.dp2px(getContext(), 20f);
mCirclePtMoveDistance = ViewUtil.dp2px(getContext(), 17.5f);
}
/**
* 啟動(dòng)動(dòng)畫
*/
public void startAnim() {
isStopAnim = false;
// 啟動(dòng)動(dòng)畫之前先恢復(fù)初始狀態(tài)
ViewHelper.setTranslationX(mCirclePtImgv, 0);
mCirclePtImgv.setBackgroundResource(R.drawable.switch_off_circle_point);
mFingerImgv.setBackgroundResource(R.drawable.finger_normal);
startFingerUpAnim();
}
/**
* 停止動(dòng)畫
*/
public void stopAnim() {
isStopAnim = true;
}
/**
* 中間的圈點(diǎn)View平移動(dòng)畫
*/
private void startCirclePointAnim() {
if (mCirclePtImgv == null) {
return;
}
ObjectAnimator circlePtAnim = ObjectAnimator.ofFloat(mCirclePtImgv, "translationX", 0, mCirclePtMoveDistance);
circlePtAnim.setDuration(CIRCLE_PT_ANIM_DURATION);
circlePtAnim.start();
}
/**
* 手指向上移動(dòng)動(dòng)畫
*/
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;
}
// 手指向上動(dòng)畫執(zhí)行完成就設(shè)置手指View背景為點(diǎn)擊狀態(tài)的背景
mFingerImgv.setBackgroundResource(R.drawable.finger_click);
// 點(diǎn)擊之后為了提現(xiàn)停頓一下的感覺,延遲200毫秒執(zhí)行其他動(dòng)畫
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (mCirclePtImgv == null || mHandler == null) {
return;
}
// 將中間圓圈View背景設(shè)置為開關(guān)打開狀態(tài)然后開始向右平移
mCirclePtImgv.setBackgroundResource(R.drawable.switch_on_circle_point);
startCirclePointAnim();
// 延遲100毫秒啟動(dòng)手指向下平移動(dòng)畫
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
// 手指向下移動(dòng)開始時(shí)設(shè)置手指背景為正常的狀態(tài)
if (mFingerImgv != null) {
mFingerImgv.setBackgroundResource(R.drawable.finger_normal);
}
startFingerDownAnim();
}
}, 100);
}
}, 200);
}
});
fingerUpAnim.start();
}
/**
* 手指向下移動(dòng)動(dòng)畫
*/
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) {
// 手指向下移動(dòng)動(dòng)畫完成,整個(gè)動(dòng)畫流程結(jié)束,重新開始下一次流程,循環(huán)執(zhí)行動(dòng)畫,間隔1秒
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (isStopAnim) {
return;
}
startAnim();
}
}, 1000);
}
});
fingerDownAnim.start();
}
}
最后一步:就是找個(gè)載體把SwitchOnAnimView加進(jìn)去,調(diào)用其startAnim方法執(zhí)行動(dòng)畫,這里在一個(gè)Activity中把播放此動(dòng)畫
定義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>
定義并實(shí)現(xiàn)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);
} }
動(dòng)畫實(shí)現(xiàn)總結(jié):
掌握Android的動(dòng)畫并不難,難的時(shí)候怎么實(shí)現(xiàn)一些復(fù)雜的動(dòng)畫,這里總結(jié)一下實(shí)現(xiàn)復(fù)雜動(dòng)畫的幾個(gè)步驟。
1. 動(dòng)畫分解:任何復(fù)雜的動(dòng)畫都可以分解為很多個(gè)原子動(dòng)畫的組合
2. 動(dòng)畫銜接時(shí)機(jī)分析:復(fù)雜動(dòng)畫分解為很多個(gè)原子動(dòng)畫之后,要重新銜接起來
這里其實(shí)就是各個(gè)原子動(dòng)畫的執(zhí)行時(shí)機(jī),誰先誰后還是同時(shí)執(zhí)行
3. 實(shí)現(xiàn)原子動(dòng)畫:將拆解的原子動(dòng)畫依次實(shí)現(xiàn)
4. 動(dòng)畫組裝:上面都準(zhǔn)備好之后,將原子動(dòng)畫按照一定的規(guī)律組裝串聯(lián)起來,整個(gè)復(fù)雜的動(dòng)畫就開始工作了
原子動(dòng)畫:本文指不能再繼續(xù)拆分的動(dòng)畫
拿本文中的動(dòng)畫來說,動(dòng)畫可以分為四個(gè):
a. 手指向上平移動(dòng)畫
b. 手指點(diǎn)擊操作(這里不是動(dòng)畫,也可以當(dāng)做一個(gè)簡單的動(dòng)畫吧)
c. 開關(guān)按鈕原點(diǎn)向右平移動(dòng)畫
d. 手指向下平移動(dòng)畫。
本文動(dòng)畫執(zhí)行時(shí)機(jī)為:
a 先執(zhí)行,a 執(zhí)行完成之后立即執(zhí)行 b,b 執(zhí)行完成之后等待200ms執(zhí)行 c(體現(xiàn)點(diǎn)擊效果)
c 執(zhí)行開始100ms后開始執(zhí)行 d
動(dòng)畫的分解和動(dòng)畫銜接時(shí)機(jī)分析是不太容易的事,因?yàn)閼{借肉眼有時(shí)候沒法觀察出來,所以播放動(dòng)畫的時(shí)候要放慢來看,如果還是不能看出來,最好還是要找公司的UI同事協(xié)助分析。因?yàn)槲覀兡芎唵蔚膮^(qū)分平移動(dòng)畫,縮放動(dòng)畫這種簡單,但是我們不能區(qū)分那種正弦算法動(dòng)畫或者是另外一些其他算法控制的動(dòng)畫。本文中的動(dòng)畫相對(duì)還是比較簡單,實(shí)現(xiàn)起來也比較容易,但是思想確實(shí)一樣的。
源碼下載地址:https://github.com/PopFisher/AndroidAnimationDemos
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
如何利用adb卸載手機(jī)預(yù)裝軟件(系統(tǒng)軟件)
對(duì)于Android手機(jī)通常有很多不必要的預(yù)置軟件,但是又無法卸載,占用桌面有很難受,所以本次使用adb工具來實(shí)現(xiàn)從電腦命令來卸載或停用軟件,下面這篇文章主要給大家介紹了關(guān)于如何利用adb卸載手機(jī)預(yù)裝軟件(系統(tǒng)軟件)的相關(guān)資料,需要的朋友可以參考下2022-09-09
Android Flutter利用貝塞爾曲線畫一個(gè)小海豚
貝塞爾曲線的應(yīng)用填補(bǔ)了計(jì)算機(jī)繪制與手繪之前的差距,更能表達(dá)人想畫出的曲線。本文就將利用貝塞爾曲線繪制一個(gè)可愛的小海豚,需要的可以參考一下2022-04-04
解決webview 第二次調(diào)用loadUrl頁面不刷新的問題
這篇文章主要介紹了解決webview 第二次調(diào)用loadUrl頁面不刷新的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03
Android Root設(shè)備中的su權(quán)限獲取和使用詳解
本篇文章主要介紹了Android Root設(shè)備中的su權(quán)限獲取和使用詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-01-01
android采用FFmpeg實(shí)現(xiàn)音視頻合成與分離
這篇文章主要為大家詳細(xì)介紹了android采用FFmpeg實(shí)現(xiàn)音視頻合成與分離,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
Android編程實(shí)現(xiàn)帶有單選按鈕和復(fù)選按鈕的dialog功能示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)帶有單選按鈕和復(fù)選按鈕的dialog功能,結(jié)合具體實(shí)例形式分析了Android實(shí)現(xiàn)帶有單選按鈕的dialog對(duì)話框及帶有復(fù)選按鈕的dialog對(duì)話框相關(guān)操作技巧,需要的朋友可以參考下2017-09-09
BootStrapValidator與My97日期校驗(yàn)的實(shí)例代碼
這篇文章給大家介紹了bootstrapvalidator與my97日期校驗(yàn)的實(shí)例代碼,代碼簡單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-01-01
Android中使用GridView實(shí)現(xiàn)仿微信圖片上傳功能(附源代碼)
由于工作要求最近在使用GridView完成圖片的批量上傳功能,我的例子當(dāng)中包含仿微信圖片上傳、拍照、本地選擇、相片裁剪等功能,如果有需要的朋友可以看一下2017-08-08


