Android模拟开关按钮点击打开动画(属性动画之平移动画)
作者:popfisher 发布时间:2021-09-05 17:48:52
在Android里面,一些炫酷的动画确实是很吸引人的地方,让然看了就赏心悦目,一个好看的动画可能会提高用户对软件的使用率。另外说到动画,在Android里面支持两种动画:补间动画和属性动画,至于这两种动画的区别这里不再介绍,希望开发者都能在使用的过程中体会两者的不同。
本文使用属性动画完成,说到属性动画,肯定要提到 JakeWharton大神写的NineOldAndroids动画库,如果你的app需要在android3.0以下使用属性动画,那么这个库就很有作用了,如果只需要在高版本使用,那么直接使用系统提供的动画API即可。
首先看一下本文要实现的动画效果:手指向上移动到开关按钮处, 然后一个点击动作,开关从关到开动画执行,同时手指向下移动回到原来的位置
点击图片调转到对应链接查看动画
动画的使用场景
引导用户去打开某个功能的开关按钮或者去打开系统的某项设置的时候,增加动画可以提高用户的点击率,表达的意思也更明确
实现之前先做好如下准备工作
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);
}
@Override
protected void onResume() {
super.onResume();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mSwitchOnAnimView.startAnim();
}
}, 500);
}
@Override
protected void onPause() {
super.onPause();
mSwitchOnAnimView.stopAnim();
}
}
动画实现总结:
掌握Android的动画并不难,难的时候怎么实现一些复杂的动画,这里总结一下实现复杂动画的几个步骤。
1. 动画分解:任何复杂的动画都可以分解为很多个原子动画的组合
2. 动画衔接时机分析:复杂动画分解为很多个原子动画之后,要重新衔接起来
这里其实就是各个原子动画的执行时机,谁先谁后还是同时执行
3. 实现原子动画:将拆解的原子动画依次实现
4. 动画组装:上面都准备好之后,将原子动画按照一定的规律组装串联起来,整个复杂的动画就开始工作了
原子动画:本文指不能再继续拆分的动画
拿本文中的动画来说,动画可以分为四个:
a. 手指向上平移动画
b. 手指点击操作(这里不是动画,也可以当做一个简单的动画吧)
c. 开关按钮原点向右平移动画
d. 手指向下平移动画。
本文动画执行时机为:
a 先执行,a 执行完成之后立即执行 b,b 执行完成之后等待200ms执行 c(体现点击效果)
c 执行开始100ms后开始执行 d
动画的分解和动画衔接时机分析是不太容易的事,因为凭借肉眼有时候没法观察出来,所以播放动画的时候要放慢来看,如果还是不能看出来,最好还是要找公司的UI同事协助分析。因为我们能简单的区分平移动画,缩放动画这种简单,但是我们不能区分那种正弦算法动画或者是另外一些其他算法控制的动画。本文中的动画相对还是比较简单,实现起来也比较容易,但是思想确实一样的。
来源:http://www.cnblogs.com/popfisher/p/5840791.html


猜你喜欢
- 在派生类中对基类成员访问应该是唯一的,但是在多继承时,可能会导致对基类某成员访问出现不一致的情况,这就是C++多继承中的二义性。有两种继承的
- 本文实例讲述了Java基于分治算法实现的棋盘覆盖问题。分享给大家供大家参考,具体如下:在一个2^k * 2^k个方格组成的棋盘中,有一个方格
- 需求:之前项目一个变动,需要对3张mysql数据库表数据进行清洗,3张表表名不同,表结构完全相同,需要对这3张表进行相同的增、改、查动作,一
- 一、this关键字this是一个引用,它指向自身的这个对象。看内存分析图:假设我们在堆内存new了一个对象,在这个对象里面你想象着他有一个引
- 在 Java 中,方法的重载(Overloading)和覆盖(Overriding)是两个重要的概念。它们都涉及到方法的定义与使用,但作用和
- 本文实例讲述了Android开发之全屏与非全屏的切换设置方法。分享给大家供大家参考,具体如下:静态方法1. 代码方式在Activity类On
- 一、概述定义一个值类型,其中包含固定值集合。枚举类型变量可以是此集合中的任意一个或多个值。枚举使用enum关键字来声明,与类同级。枚举本身可
- Java 堆是用来存储对象实例的, 因此如果我们不断地创建对象, 并且保证 GC Root 和创建的对象之间有可达路径以免对象被垃圾回收,
- 在开发应用程序的时候,经常会遇到这样的情况,会在运行时动态根据条件来决定显示哪个View或某个布局。那么最通常的想法就是把可能用到的View
- Android 媒体库数据更新方法总结在项目中,我们经常要创建个自己的目录,里面存放一些图片啊文件之类,比如:我在SD卡中刚创建了一个文件夹
- 冒泡排序算法演示图:public static void bubbleSort(int[] array) { &
- Collectors.toMap空指针问题在工作中遇到了一个List转Map的时候的一个NullPointException.情形很简单,问
- Object.MemberwiseClone 方法创建当前 Object 的浅表副本。protected Object Memberwise
- 本文实例为大家分享了QT实现用户登录注册的具体代码,供大家参考,具体内容如下#include "widget.h"#in
- 废话不多说了,具体代码如下所述:staticvoid Main(string[] args){ st
- 一、项目简述功能: 主页显示商品; 所有蛋糕商品展示,可进行商品搜索; 点击商品进入商品详情页,具有立即购买功能,可增减购买商品数量亦可手动
- 本文实例为大家分享了Android实现3D云标签效果的具体代码,供大家参考,具体内容如下一、自定义Viewpublic class TagC
- 线程池中ThreadGroup的坑在Java中每一个线程都归属于某个线程组管理的一员,例如在主函数main()主工作流程中产生一个线程,则产
- 安卓验证码的简单实现我们经常在登录或者注册的时候要求输入验证码,这里简单介绍一下一种方法 效果如下首先是要获取 随机的四个字母组合,我这里是
- 基本环境语言:Java 8 数据库:Oracle ORM 框架:MyBatis 3.4.5需求批量插入数据,数据需要有自增 id。每次插入有