详解Android如何实现自定义的动画曲线

目录
  • 前言
  • Curve 类定义
  • 实例解析
  • 正弦动画曲线
  • 总结

前言

最近在写动画相关的篇章,经常会用到 Curve 这个动画曲线类,那这个类到底怎么实现的?如果想自己来一个自定义的动画曲线该怎么弄?本篇我们就来一探究竟。

曲线

Curve 类定义

查看源码, Curve 类定义如下:

abstract class Curve extends ParametricCurve<double> {
  const Curve();

  @override
  double transform(double t) {
    if (t == 0.0 || t == 1.0) {
      return t;
    }
    return super.transform(t);
  }
  
  Curve get flipped => FlippedCurve(this);
}

看上去好像没定义什么, 实际这里只是做了两个处理,一个是明确的数据类型为 double,另一个是对 transform 做了重载,也只是对参数 t 做了特殊处理,保证参数 t 的范围在0-1之间,且起点值0.0和终点值1.0不被转换函数转换。主要定义在上一层的ParametricCurve。文档是建议子类重载transformInternal方法,那我们就继续往上看ParametricCurve这个类的实现,代码如下:

abstract class ParametricCurve<T> {
  const ParametricCurve();

  T transform(double t) {
    assert(t != null);
    assert(t >= 0.0 && t <= 1.0, 'parametric value $t is outside of [0, 1] range.');
    return transformInternal(t);
  }

  @protected
  T transformInternal(double t) {
    throw UnimplementedError();
  }

  @override
  String toString() => objectRuntimeType(this, 'ParametricCurve');
}

可以看到,实际上 transform 方法除了做参数合法性验证以外,其实就是调用了transformInternal方法,因此子类必须要实现该方法,否则会抛出UnimplementedError异常。

实例解析

上面的源码可以看到,关键在于参数 t。这个参数 t 代表什么呢?注释里说的是:

Returns the value of the curve at point t. — 返回 t 点的曲线对应的值。

因此 t 可以认为是曲线的横坐标,而为了保证曲线的一致性,做了归一化处理,也就是t的取值都是在0-1之间。这么说可能有点抽象,我们来看2个例子来对比就明白了,先看最简单 Curves.linear 的实现。

class _Linear extends Curve {
  const _Linear._();

  @override
  double transformInternal(double t) => t;
}

超级简单吧,直接返回 t,其实对应我们的数学的函数就是:

y = f(t) = t

对应的曲线就是一条斜线。也就是说在设定的动画时间内,会完成从0-1的线性转变,也就是变化是均匀的。线性这个很好理解,我们再来看一个减速曲线decelerate的实现。

class _DecelerateCurve extends Curve {
  const _DecelerateCurve._();

  @override
  double transformInternal(double t) {
    t = 1.0 - t;
    return 1.0 - t * t;
  }
}

我们先看一下_DecelerateCurve 的计算表达式是什么。

回忆一下我们高中物理学的匀减速运动,加速度为负(即减速)的距离计算公式:

上面的减速曲线其实就可以看做是初始速度是2,加速度也是2的减速运动。为什么要是2这个值呢,这是因为 t 的取值范围是0-1,这样计算完的结果的取值范围还是0-1。你肯定会问,为什么要保证曲线的计算结果要是0-1?我们来假设计算结果不为0-1会发生什么情况,比如我们要在屏幕上移动一个组件为60像素。假设动画曲线初始值不为0。那就意味着一开始的移动距离是跳变的。同样的,如果结束值不为1.0,意味着在最后一个点的距离值不是60.0,那么就意味着结束时需要从最后一个点跳到最终的60像素的位置(动画需要保证最终的移动距离是60像素)这样意味着动画会出现跳变的效果,绘制曲线的话会是下面的样子(绿色是正常的,红线是异常的)。这样的动画体验是很糟糕的!因此,这是一个关键点,如果你的自定义曲线的 transformInternal 方法的返回值范围不是0-1,就意味着动画会出现跳变,导致动画缺帧的感觉。

image.png

有了这个基础,我们就可以解释动画曲线的基本机制了,实际上就是在给定的动画时间(Duration)范围内,完成组件的初始状态到结束状态的转变,这个转变是沿着设定的 Curve 类完成的,而其横坐标是0-1.0,曲线的初始值和结束值分别是0和1.0,而至于中间值是可以低于0或超过1的。我们可以想像是我们沿着设定的曲线运动,最终无论如何都会达到设定的目的地,而至于怎么走,拐多少道弯,速度怎么变化都是曲线控制的。但是,如果你的曲线初始值不为0或结束值不为1,就像是跳悬崖的那种感觉!

正弦动画曲线

我们来一个正弦曲线的动画验证一下上面的说法。

class SineCurve extends Curve {
  final int count;
  const SineCurve({this.count = 1}) : assert(count > 0);

  @override
  double transformInternal(double t) {
    return sin(2 * count* pi * t);
  }
}

count 参数用于控制周期,即达到目的地之前可以多来几个来回。这里我们发现,初始值是0,但是一个周期(2π)结束值也是0,这样在动画结束前会出现跳变的结果。来看一下示例代码,这个示例是让圆形向下移动60像素。

AnimatedContainer(
  decoration: BoxDecoration(
    color: Colors.blue,
    borderRadius: BorderRadius.circular(30.0),
  ),
  transform: Matrix4.identity()..translate(0.0, up ? 60.0 : 0.0, 0.0),
  duration: Duration(milliseconds: 3000),
  curve: SineCurve(count: 1),
  child: ClipOval(
    child: Container(
      width: 60.0,
      height: 60.0,
      color: Colors.blue,
    ),
  ),
)

运行效果如下,注意看最后一帧从0的位置直接跳到了60的位置。

跳动动画

这个怎么调呢,我们来看一下正弦曲线的样子。

正弦曲线

如果我们要满足0-1范围的要求,那么要往后再移动90度才能够达到。但是,这样还有个问题,这样破坏了周期性,比如设置 count=2的时候结果又不对了。我们来看一下规律,实际上只有第一个周期需要多移动90度(图中箭头指向的点),后面的都是按360度(即2π)为周期了。也就是角度其实是按2.5π,4.5π,6.5π……规律来的,对应的角度公式其实就是:

所以调整后的正弦曲线代码为:

class SineCurve extends Curve {
  final int count;
  const SineCurve({this.count = 1}) : assert(count > 0);

  @override
  double transformInternal(double t) {
    // 需要补偿pi/2个角度,使得起始值是0.终止值是1,避免出现最后突然回到0
    return sin(2 * (count + 0.25) * pi * t);
  }
}

再看调整后的效果,是不是丝滑般地过渡了?

总结

本篇介绍了 Flutter 动画曲线类的原理和控制动画的机制,实际上 Curve 类就是在指定的时间内,沿曲线完成从起点到终点的过渡。但是为了保证动画平滑过渡,应该保证自定义曲线的transformInternal方法返回值的起始值和结束值分别是0和1。

到此这篇关于详解Android如何实现自定义的动画曲线的文章就介绍到这了,更多相关Android动画曲线内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android利用二阶贝塞尔曲线实现添加购物车动画详解

    一.引入 其实之前一直以为像饿了么或者是美团外卖那种把商品添加到购物车的动画会很难做,但是实际做起来好像并没有想象中的那么难哈哈. 布局主要使用CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout+TabLayout+ViewPager 动画主要使用二阶贝塞尔曲线与属性动画 消息传递使用EventBus普通事件 二.大致思路 1.如图所示主要有三个点,起点.终点.以及贝塞尔曲线的控制点 2.起点即点击的View的位置,一般来说用如下方式即可

  • Android 使用cos和sin绘制复合曲线动画

    前言 前两周在开发新需求的时候,设计给了一份类似这样的动画: 看着不难,即使一遍看不懂,嘿嘿,不还有设计稿. 作为一个平时很少写动画的 Android 开发仔,看到一段段的缓入缓出曲线的设计稿时,我的心情是这样的: 虽然,Android 动画默认的插值器 AccelerateDecelerateInterpolator 有这样缓入缓出的效果: 我总不能一整个动画给它拆成4段动画来写,还别说,我第一次写的代码还真的是这么干的. 第一次分析 本着能少写一行绝不多写一字的原则,询问了大佬同事的意见,大

  • Android把商品添加到购物车的动画效果(贝塞尔曲线)

    当我们写商城类的项目的时候,一般都会有加入购物车的功能,加入购物车的时候会有一些抛物线动画,具体代码如下: 实现效果如图: 思路: 确定动画的起终点 在起终点之间使用二次贝塞尔曲线填充起终点之间的点的轨迹 设置属性动画,ValueAnimator插值器,获取中间点的坐标 将执行动画的控件的x.y坐标设为上面得到的中间点坐标 开启属性动画 当动画结束时的操作 难点: PathMeasure的使用 - getLength() - boolean getPosTan(float distance, f

  • Android仿百度地图小度语音助手的贝塞尔曲线动画

    本文为大家分享了Android仿小度语音助手的贝塞尔曲线动画,供大家参考,具体内容如下 废话不多说,看下面的动图,和百度的还是有点点差别,我也不修改了,很简单,我实在是没有多余的时间,还要学习其他的东西. package com.example.helang.volumewave; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; impo

  • 详解Android如何实现自定义的动画曲线

    目录 前言 Curve 类定义 实例解析 正弦动画曲线 总结 前言 最近在写动画相关的篇章,经常会用到 Curve 这个动画曲线类,那这个类到底怎么实现的?如果想自己来一个自定义的动画曲线该怎么弄?本篇我们就来一探究竟. 曲线 Curve 类定义 查看源码, Curve 类定义如下: abstract class Curve extends ParametricCurve<double> {   const Curve();   @override   double transform(dou

  • 详解Android Material Design自定义动画的编写

    新的动画Api,让你在UI控件里能创建触摸反馈,改变View的状态,切换activity的一系列自定义动画 具体有: 响应View的touch事件的触摸反馈动画 隐藏和显示View的循环展示动画 两个Activity间的切换动画 更自然的曲线运动的动画 使用View的状态更改动画,能改变一个或多个View的属性 在View的状态更改时显示状态列表动画 这些new animations Api,已内置在标准Widget中,如Button.在自定义view时也可使用这些api 动画在Material

  • 详解Android(共享元素)转场动画开发实践

    最近零碎时间一直在研究OpenGL,所以没怎么进行分享,以后可能大部分时间会学习系统底层\NDK\VR\AR等领域,话不多少,今天来分享个小的动画效果. 效果如下 基本知识 其实Android的转场动画由来已久,比如平常开发安卓的时候界面切换 都是右进右出,这样的效果,就是早期的转场动画,在5.0之后安卓官方支持了共享元素的效果,那么问题来了,5.0以后该怎么适配? 准备步骤 定义两个activity,界面跳转是从A到B. ActivityA定义一个控件View,在跳转时传入到Pair里面,详细

  • 详解Android如何自定义view实现圆形进度条

    Android中实现进度条有很多种方式,自定义进度条一般是继承progressBar或继承view来实现,本篇中讲解的是第二种方式. 先上效果图: 实现圆形进度条总体来说并不难,还是跟往常一样继承view,初始化画笔,按下面的步骤一步步来就好了.对初学者来说动画效果可能比较陌生,我们可以使用属性动画中的valueAnimator来实现动画效果. 实现步骤: 1.画出一个灰色的圆环作为背景. 2.画出上层的圆环覆盖下方的圆环. 3.加入动画效果 值得注意的是怎么设置圆环和文字的位置. 画出矩形只需

  • 详解 Android中Libgdx使用ShapeRenderer自定义Actor解决无法接收到Touch事件的问题

    详解 Android中Libgdx使用ShapeRenderer自定义Actor解决无法接收到Touch事件的问题 今天在项目中实现了一个效果,主要是画一个圆.为了后续使用方便,将这个圆封装在一个自定义Actor(CircleActot)中,后续想显示一个圆的时候,只要创建一个CircleActor中即可. 部分代码如下所示: package com.ef.smallstar.unitmap.widget; import android.content.res.Resources; import

  • 详解Android更改APP语言模式的实现过程

    一.效果图 二.描述 更改Android项目中的语言,这个作用于只用于此APP,不会作用于整个系统 三.解决方案 (一)布局文件 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" a

  • 详解Android框架MVVM分析以及使用

    Android MVVM 分析以及使用 首先我们需要知道什么是MVVM,他的功能和优点,以及他的缺点. MVVM是Model-View-ViewModel的简写.它本质上就是MVC 的改进版.MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开.当然这些事 ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑.微软的WPF带来了新的技术体验,如Silverlight.音频.视频.3D.动画-

  • 详解Android中motion_toast的使用

    目录 前言 motion_toast 介绍 示例 最简单用法 其他内置的提醒 自定义 toast 总结 前言 我们通常会用 toast(也叫吐司)来显示提示信息,例如网络请求错误,校验错误等等.大多数 App的 toast 都很简单,简单的半透明黑底加上白色文字草草了事,比如下面这种. 说实话,这种toast 的体验很糟糕.假设是新手用户,他们并不知道 toast 从哪里出来,等出现错误的时候,闪现出来的时候,可能还没抓住内容的重点就消失了(尤其是想截屏抓错误的时候,更抓狂).这是因为一个是这种

  • 详解Android中Intent对象与Intent Filter过滤匹配过程

    如果对Intent不是特别了解,可以参见博文<详解Android中Intent的使用方法>,该文对本文要使用的action.category以及data都进行了详细介绍.如果想了解在开发中常见Intent的使用,可以参见<Android中Intent习惯用法>. 本文内容有点长,希望大家可以耐心读完. 本文在描述组件在manifest中注册的Intent Filter过滤器时,统一用intent-filter表示. 一.概述 我们知道,Intent是分两种的:显式Intent和隐式

  • 详解Android获得系统GPU参数 gl.glGetString

    详解Android获得系统GPU参数 gl.glGetString 通过文档的查找,以及源码的剖析,Android的GPU信息需要通过OpenGL来获取,android framework层提供GL10来获取相应的参数,而GL10要在使用自定义的View时才可以获得,下面是获得GPU信息的例子: 1.实现Render类 class DemoRenderer implements GLSurfaceView.Renderer { public void onSurfaceCreated(GL10

随机推荐