MonitorUI Example | Scroller Example | View Prototype |
---|---|---|
Launguage:English
Animer 是一款致力于提升 Android 动画体验的 Java 库
你可以把 Animer 理解为基于 View 动画
、强化动画控制、交互性
的动画器,并提供了调试UI
Animer 封装了:
- Android 平台的 DynamicAnimation 和 Interpolator 的曲线
- iOS 平台的 CASpringAnimation 和 UIViewSpring 的曲线
- 贝塞尔函数曲线 (iOS Deafult Easing,Web Default CSS Easing,AE 的关键帧间曲线 都可以用此法实现)
- Principle、Origami、Protopie、FramerJS 等动画工具的曲线
Animer 并没有像 Rebound 那样,通过 Choreographer 或者自构建 Looper ,从头构建一套动画器,而是将上述曲线的算法通过转换器,最终会转换成 Android 原生的 DynamicAnimation 或者 TimingInterpolator,进而提高动画执行性能。
Animer 提供了可实时调节的控制UI和曲线图表,以便设计师和开发者调节参数,节省编译时间。
网页版本(该网页主要功能是可以将其他平台、工具的参数转化为安卓原生动画类的参数) —— Animer_Web
AE 脚本 —— Animer AE
Animer 支持多种风格的动画方法,如果写过 FramerJS、用过 Rebound 库的朋友会非常熟悉,也模拟了安卓原生的动画方法。
// 创建一个 Animer 解算器对象,采用了原生的插值动画类
Animer.AnimerSolver solver = Animer.interpolatorDroid(new AccelerateDecelerateInterpolator(),600)
// 模仿 ObjectAnimator 的构造
Animer animer = new Animer(myView,solver,Animer.TRANSLATION_Y,0,200);
animer.start();
// animer.cancel();
// animer.end();
// 创建一个 Animer 解算器对象,采用了原生的 DynamicAnimation 动画器
Animer.AnimerSolver solver = Animer.springDroid(1000,0.5f);
Animer animer = new Animer();
// 给 Animer 对象添加 solver
animer.setSolver(solver);
// 设置动画的几种可能状态(入场、退场、点击)
animer.setStateValue("stateA",300);
animer.setStateValue("stateB",700);
animer.setStateValue("stateC",200);
// 给动画添加监听,观察数值变化
animer.setUpdateListener(new Animer.UpdateListener() {
@Override
public void onUpdate(float value, float velocity, float progress) {
myView1.setTranslationX(value);
myView2.setScaleX(1.f+value/100);
myView2.setScaleY(1.f+value/100);
}
});
// 立即切换到状态
animer.switchToState("stateA");
// 动画运动到状态
// animer.animateToState("stateB");
// 创建一个 Animer 解算器对象,采用了 Facebook 的 POP 弹性动画器
Animer.AnimerSolver solver = Animer.springOrigamiPOP(5,10);
Animer animer = new Animer(myView,solver,Animer.SCALE);
// 给动画添加监听,观察数值变化,这里将对象 view 的缩放和动画器数值绑定
animer.setUpdateListener(new Animer.UpdateListener() {
@Override
public void onUpdate(float value, float velocity, float progress)
myView.setScaleX(value);
myView.setScaleY(value);
}
});
animer.setCurrentValue(1.f);
boolean isScaled = false;
myView.setOnClickListener(view -> {
if(!isScaled){
animer.setEndValue(0.5);
}
else{
animer.setEndValue(1);
}
isScaled = !isScaled;
});
添加 XML
<com.martinrgb.animer.monitor.AnConfigView
android:id="@+id/an_configurator"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
在 Java中, 将 Animer 对象添加到 AnConfigRegistry 实例中,然后刷新 UI
AnConfigView mAnimerConfiguratorView = (AnConfigView) findViewById(R.id.an_configurator);
AnConfigRegistry.getInstance().addAnimer("Card Scale Animation",cardScaleAnimer);
AnConfigRegistry.getInstance().addAnimer("Card TranslationX Animation",cardTransXAnimer);
mAnimerConfiguratorView.refreshAnimerConfigs();
先需要清空默认的所有预设,然后添加自己的动画预设。
AnConfigRegistry.getInstance().removeAllSolverConfig();
AnConfigRegistry.getInstance().addSolver("Preset1",Animer.springDroid(500.0f,0.96f));
AnConfigRegistry.getInstance().addSolver("Preset2",Animer.flingDroid(400.f,0.95f));
...
mAnimerConfiguratorView.refreshAnimerConfigs();
Animer.TRANSLATION_X
Animer.TRANSLATION_Y
Animer.TRANSLATION_Z
Animer.SCALE // equal to SCALE_X + SCALE_Y
Animer.SCALE_X
Animer.SCALE_Y
Animer.ROTATION
Animer.ROTATION_X
Animer.ROTATION_Y
Animer.X
Animer.Y
Animer.Z
Animer.ALPHA
Animer.springDroid(stiffness,dampingratio) // Android Dynamic SpringAnimation
Animer.flingDroid(velocity,friction) // Android Dynamic FlingAnimation
Animer.springiOSUIView(dampingratio,duration) // iOS UIView SPring
Animer.springiOSCoreAnimation(stiffness,damping) // iOS CASpringAnimation
Animer.springOrigamiPOP(bounciness,speed) // Origami POP
Animer.springRK4(tension,friction) // Framer-RK4
Animer.springDHO(stiffness,damping) // Framer-DHO
Animer.springProtopie(tension,friction) // Protopie
Animer.springPrinciple(tension,friction) // Principle
// Custom Bounce Interpolator(Romain Guy's DropInMotion)
Animer.interpolatorDroid(new CustomBounceInterpolator(),duration)
// Custom Damping Interpolator(Romain Guy's DropInMotion)
Animer.interpolatorDroid(new CustomDampingInterpolator(),duration)
// MocosSpring Interpolator (https://github.com/marcioapaiva/mocos-controlator)
Animer.interpolatorDroid(new CustomMocosSpringInterpolator(),duration)
// Custom Spring Interpolator(https://inloop.github.io/interpolator/)
Animer.interpolatorDroid(new CustomSpringInterpolator(),duration)
// Android Native Interpolator Below
Animer.interpolatorDroid(new PathInterpolator(),duration) // Cubic Bezier Interpolator
...
Animer.interpolatorDroid(new DecelerateInterpolator(),duration) // Android Decelerate Interpolator
...
- 以界面整体状态为考量的状态机系统,而不是单个对象的状态
- 重新设计 API,重新编写文档,提高可用性
- 重写绘制图表的 shader,目前使用了太多条件分歧,参考如何在shader中避免使用if else
- 考虑转场的使用场景
- 考虑 Hook 机制
- 考虑重新设计调试UI,因为不方便连续调试,可能考虑 adb + electron + web桌面客户端的方式
下图是 Animer 大致的原理和设计思路
数据
- FramerJS 的动画状态机概念 ✅
- Animation Converter 中的多平台、工具动画器转换函数 ✅
- 支持读取外部 JSON 编辑动画(???)
算法
- Rebound 的物理动画概念 和 Android 的 DynamicAnimation ✅
- Android 原生的 查找表插值器(LookupTable Interpolator) + RK4 弹性解算器 + DHO 弹性解算器 ✅
- Flutter Physics 的物理模拟 & UIKit Dyanmic 的物理模拟
- 动量传递与保存(通过状态机实现)
- 通过元素面积计算质量
- 通过振荡次数或时间限制弹性动画运动时长
高级动画
- 叠加动画(Addtive animation,将多个动画同时影响到一个对象)
- 链式动画(Chained animation,动画一个接一个开始)
- 视差效果(Parallax animation,相同动画触发,不同动画曲线、时间、延迟)
- 序列动画(Sequencing animation,动画逐次开始)
交互动画
- 支持手势驱动的动画,动画可中断,可在动画过程中重新交互(参考 iOS
CADisplayLink
或 Rebound 的SetEndValue
) ✅ - 内置封装一个手势动画器,提供手势速度自动保留,以便手势交互时动画体验更物理、更流畅。(完成一半)
- 提供易用的动画监听,在动画监听中控制多个对象元素 ✅
性能
- 所有动画最终被转换为 Android 框架原生的 DyanmicAniamtion 和 TimingInterpolator,可调试完解除依赖然后使用原生写法 ✅
- 可考虑将动画数据转换为预存数组,以便节省实时计算开销
- 硬件加速 ✅
设计组件
- Scrollview|Scroller|PageViewer 组件跟案例,提供更好的 Overscroll 和 Fling 效果
- Drag | 拖拽组件跟案例,提供更符合物理直觉的甩手感
- Button 组件和案例
- Transition 组件和案例(考虑如何在不同 Activity 中维护一个元素的属性以便转场)
- Scroll-selector 滑动选择器组件和案例(类似 iOS 的日期选择器,提供平缓的衰减且定位的滚动体验)
- Swipe to delete 滑动删除组件和案例(更自然的滑动删除效果)
开发工具
- 提供 GLSL 异步绘制的图表,展示动画曲线本身 ✅
- 提供 GLSL 异步绘制的图表,展示实时属性变化曲线
- 提供数据与 View 属性绑定的控制 UI,实时切换、修改动画 ✅(目前仍有些许 bugs)
实用工具
- AE 插件,通过赋予关键帧表达式模拟上述曲线 ✅(目前仅为脚本,后面提供 Extension 级插件,增加 UI)
采用了 Apache 许可协议,详细