Android动画:模拟开关按钮点击打开动画(属性动画之平移动画)

在Android里面,一些炫酷的动画确实是很吸引人的地方,让然看了就赏心悦目,一个好看的动画可能会提高用户对软件的使用率。另外说到动画,在Android里面支持3种动画: 逐帧动画(Frame Animation)、补间动画(Tween Animation)和属性动画(Property Animation),至于这几种动画的区别这里不再介绍,希望开发者都能在使用的过程中体会两者的不同。

  本文使用属性动画完成,说到属性动画,肯定要提到 JakeWharton大神写的NineOldAndroids动画库,如果你的app需要在android3.0以下使用属性动画,那么这个库就很有作用了,如果只需要在高版本使用,那么直接使用系统提供的动画API即可。

首先看一下本文要实现的动画效果:手指向上移动到开关按钮处, 然后一个点击动作,开关从关到开动画执行,同时手指向下移动回到原来的位置

点击图片调转到对应Github链接查看动画

动画的使用场景

引导用户去打开某个功能的开关按钮或者去打开系统的某项设置的时候,增加动画可以提高用户的点击率,表达的意思也更明确

实现之前先做好如下准备工作

1. 下载nineoldandroids-2.4.0.jar的库,放到android studio 工程目录的libs文件夹中

  2. 在build.gradle文件中引入

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

 3. 准备好相关的图片资源

接下来封装一个自定义控件来实现整个动画

第一步:先定义一个布局文件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>

布局文件预缆长这样:

第二步:定义自定义控件(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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android 开发者

[译] WindowsInsets 和 Fragment 过渡动画

1743
来自专栏Flutter入门到实战

开发工具总结(4)之Android Studio3.0填坑指南

作为这个世界上走在最前沿的生物“猿”,怎么能对新事物一无所知呢,10月26日,随着Android 8.1 Oreo的预览版发布,Android Studio3....

734
来自专栏向治洪

记一个SwipeMenuListView侧滑删除错乱的Bug

做侧滑删除网上有很多方案,比如重写Listview实现滑动的监听,今天说下一个SwipeListView,这个是之前一个朋友在网上开源的一个封装组件,能够适用于...

1987
来自专栏Sorrower的专栏

动画必须有(一): 属性动画浅谈

803
来自专栏Android Note

Android – 仿微信拍摄视频按钮

1263
来自专栏Android学习之路

Gradle 将 library module 发布到本地仓库,并添加本地依赖

2069
来自专栏张善友的专栏

如何合并Git 代码库中牛人的代码到自己的库

github for Windows使用介绍 这篇文章可以很好带我们入门github,同时还带了一个gitshell,这个工具可以运行github的所有命令,命...

1838
来自专栏何俊林

一款基于ExoPlayer的自定义播放器(已开源)

不少同学前段时间问过我ExoPlayer的选型怎样,以及可以基于ExoPlayer做哪些拓展,今天推荐的这个项目,可以让你彻底了解ExoPlayer。 一、基于...

5147
来自专栏Android随笔

自定义多状态View

MultipleStatusView 继承自 RelativeLayout,所以内容视图也可以直接写在 MultipleStatusView 内部

582
来自专栏非著名程序员

Android开发:最详细的 Toolbar 开发实践总结

? 最详细的 Toolbar 开发实践总结 过年前发了一篇介绍 Translucent System Bar 特性的文章 Translucent Syste...

1889

扫码关注云+社区