CubixD,一个 3D 立方体
安装
将 cubixd 添加到您的 pubspec.yaml 依赖项中
dependencies:
cubixd: ^0.1.1
然后导入它
import 'package:cubixd/cubixd.dart';
功能
- 将此添加到您的 Flutter 应用中即可获得一个 3D 立方体!
入门
此软件包包含 2 个小部件
- AnimatedCubixD
- CubixD
AnimatedCubixD 是动画化的 3D 立方体,此小部件使用 3 个控制器(AnimationController)来实现 3 种不同的动画。包括阴影、彩色星星、所有动画以及选择面的功能
CubixD 是静态 3D 立方体,此小部件包括选择面的功能(无动画)
您在开头看到的示例可以使用以下代码实现
import 'package:cubixd/cubixd.dart';
Center(
child: AnimatedCubixD(
onSelected: ((SelectedSide opt) => opt == SelectedSide.bottom ? false : true),
size: 200.0,
left: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/graphql.png"),
fit: BoxFit.cover,
),
),
),
front: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/nestjs.png"),
fit: BoxFit.cover,
),
),
),
back: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/mongodb.png"),
fit: BoxFit.cover,
),
),
),
top: ...,
bottom: ...,
right: ...,
),
),
AnimatedCubixD
参数
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| advancedXYposAnim | AnimRequirements | – | 高级 XY 位置动画。如果您想对 AnimationController 和所需的 2 种动画进行更多控制,可以设置此参数。请注意,当您设置此选项时,AnimationController 将不会自动前进和处置。您可以在下方阅读更多信息和示例 |
| afterRestDel | 持续时间 | Duration(miliseconds: 50) |
恢复后延迟。此参数表示 cubixd 恢复动画执行后,为使主动画再次出现而产生的延迟 |
| afterSelDel | 持续时间 | Duration(seconds: 4) |
选择后延迟。此参数表示 cubixd 在面被选中后等待的持续时间,之后将 cubixd 恢复到主动画 |
| back * | Widget | – | 应显示在背面一侧的小部件 |
| bottom * | Widget | – | 应显示在底部一侧的小部件 |
| buildOnSelect | Widget Function(double, AnimationController) | 空 | 如果您不喜欢用户选择面时触发的默认星星动画。您可以使用此参数,以极大的自由度设置不同的动画。这个参数相当复杂,所以您可以在下方阅读更多相关信息 |
| debounceTime | 持续时间 | Duration(miliseconds: 500) |
防抖时间。cubixd 使用防抖器,这意味着当您不断移动 cubixd 以选择一个面时,它不会执行选择,直到您将其保持静止且面有效,并等待此处指定的持续时间,它将触发选择,否则如果在时间运行之前移动它,它将“反弹”选择并从 0 开始重新计算此指定时间 |
| front * | Widget | – | 应显示在正面一侧的小部件 |
| left * | Widget | – | 应显示在左侧一侧的小部件 |
| onPanUpdate | void Function() | 空 | 更新时调用。这是用户移动 cubixd 以选择面时执行的回调 |
| onRestCurve | Curve | Curves.fastOutSlowIn |
恢复曲线。此参数设置恢复动画应具有的曲线。将恢复动画理解为选择面后执行的动画,用于将 cubixd 恢复到其初始位置 |
| onSelecCurve | Curve | Curves.fastOutSlowIn |
选择曲线。此参数设置选择动画应具有的曲线。将选择动画理解为防抖计时器结束时触发并触发选择的动画 |
| onSelect | bool Function(SelectedSide) | 空 | 选择时调用。应在用户选择面时触发的回调 |
| restDuration | 持续时间 | Duration(miliseconds: 800) |
恢复持续时间。恢复动画应花费的持续时间 |
| right * | Widget | – | 应显示在右侧一侧的小部件 |
| selDuration | 持续时间 | Duration(miliseconds: 400) |
选择持续时间。选择动画应花费的持续时间。将选择动画理解为防抖器触发后立即发生的动画 |
| sensitivityFac | 双精度 | 1.0 | 灵敏度因子。就像鼠标移动时有灵敏度一样。cubixd 也有灵敏度。理想情况下,此值应接近 1,而不是 0 或更低。其值越大,灵敏度也越高 |
| shadow | 布尔值 | 真 | 阴影。定义 cubixd 是否应具有阴影。请注意,如果没有阴影,cubixd 将无法很好地上下移动(并且此小部件占据的最终高度将减小) |
| simplePosAnim | SimpleAnimRequirements | SimpleAnimRequirements(duration: const Duration(seconds: 10), xBegin: -pi / 4, xEnd: (7*pi)/4, yBegin: pi / 4, yEnd: pi / 4, reverseWhenDone: false, infinite: true) |
如果您不想使用 AnimationController 设置高级选项,可以使用此参数设置一些参数以使 cubixd 移动 c:您可以在下方阅读更多信息 |
| size * | 双精度 | – | 每个面应具有的宽度和高度 |
| stars | 布尔值 | 真 | 选择面后是否应出现彩色星星 |
| top * | Widget | – | 应显示在顶面一侧的小部件 |
onSelect
一个 3D 立方体应始终有 6 个面,但也许您只需要 5 个面。您可以准备 5 个面以供选择,而 1 个面处于停用状态。这正是考虑此参数时所想到的:一个回调,发送被选中的面,如果此回调返回 false,则该面无法被选中,否则可以。
AnimatedCubixD(
...
onSelected: (SelectedSide opt) {
switch (opt) {
case SelectedSide.back:
return true;
case SelectedSide.top:
return true;
case SelectedSide.front:
return true;
case SelectedSide.bottom:
return false; // out of service
case SelectedSide.right:
return true;
case SelectedSide.left:
return true;
case SelectedSide.none:
// You can do something else
return false; // Nothing will happend if you return true at this point
default:
throw Exception("Unimplemented option");
}
}
...
),
如果未设置此参数(null),则用户将无法移动 cubixd
结果是
advancedXYposAnim
AnimRequirements
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| controller * | AnimationController | – | 应在主动画中使用 AnimationController。您可以在此处设置主动画的持续时间 |
| xAnimation * | 动画 | – | 应在水平轴上使用的动画。您可以在此处设置 x 的起始角度和结束角度(以弧度为单位) |
| yAnimation * | 动画 | – | 应在垂直轴上使用的动画。您可以在此处设置 y 的起始角度和结束角度(以弧度为单位) |
示例
...
late final AnimationController _mainCtrl;
late final Animation<double> _xAnimation;
late final Animation<double> _yAnimation;
...
@override
void initState(){
_mainCtrl = AnimationController(vsync: this, duration: const Duration(seconds: 10));
_xAnimation = Tween<double>(begin: -pi / 4, end: pi * 2 - pi / 4).animate(_mainCtrl);
_yAnimation = Tween<double>(begin: pi / 4, end: pi / 4).animate(_mainCtrl);
_mainCtrl.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_mainCtrl.reverse();
} else if (status == AnimationStatus.dismissed) {
_mainCtrl.forward();
}
print(status);
});
_mainCtrl.forward();
super.initState();
}
...
AnimatedCubixD(
...
advancedXYposAnim: AnimRequirements(
controller: _mainCtrl,
xAnimation: _xAnimation,
yAnimation: _yAnimation,
),
...
),
...
@override
void dispose(){
_mainCtrl.dispose();
super.dispose();
}
...
buildOnSelect
这可能是此软件包中最复杂的参数,因此我建议您根据需要多次阅读此部分
如果您想在选择面时获得另一个启动动画怎么办?通过此参数,您可以做到。当用户选择一个面时,另一个动画正在运行,将 cubixd 放置到选定的面,我称之为“选择动画”,此动画与主动画完全不同,因此它具有另一个 AnimationController。
AnimatedCubixD 使用 3 个不同的控制器来实现 3 种不同的动画
-
主动画。它使用您可能已从
advancedXYposAnim参数传递给 AnimatedCubixD 的控制器,或者未传递任何控制器。如果您根本没有传递任何控制器,它将自己创建控制器并自动执行 forward 和 dispose 方法。 -
选择动画。仅当
onSelect参数不为 null 时,它才会创建自己的控制器。这用于执行播放以调整选定面的精确角度的动画 -
恢复动画。仅当
onSelect参数不为 null 时,它才会创建自己的控制器。这用于执行播放以在用户选择面后将 cubixd 调整到其初始位置的动画
考虑到这一点,此参数的回调会发送 2 个参数:size(double)和 select 动画控制器(AnimationController),此回调期望您返回一个将在用户选择面后显示的小部件
import 'dart:math';
import 'package:cubixd/cubixd.dart';
...
AnimatedCubixD(
...
buildOnSelect: (double size, AnimationController ctrl) => CircleStar(ctrl: ctrl, size: size),
stars: false,
...
),
...
class _Animations {
final Animation<double> xAnim;
final Animation<double> yAnim;
final double size;
_Animations(this.xAnim, this.yAnim, this.size);
}
class CircleStar extends StatelessWidget {
final CurvedAnimation _curvedA;
final double overflowQ = 0.4;
final List<_Animations> _starsA = [];
final List<int> _minMax = [20, 35];
CircleStar({
Key? key,
required AnimationController ctrl,
required double size,
}) : _curvedA = CurvedAnimation(parent: ctrl, curve: Curves.easeOutCubic),
super(key: key) {
_initParams(size);
ctrl.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_initParams(size);
}
});
}
void _initParams(double size) {
_starsA.clear();
final int length = Random().nextInt(_minMax[1] - _minMax[0]) + _minMax[0];
final double overflow = overflowQ * size;
for (int i = 0; i < length; i++) {
final double shapeSize = Random().nextDouble() * size * 0.8;
final double lPos = Random().nextDouble() * size;
final double tPos = Random().nextDouble() * size;
final double xEnd;
final double yEnd;
if (-lPos.abs() % size < -tPos.abs() % size) {
xEnd = lPos > size / 2 ? size + overflow : -overflow;
yEnd = xEnd * (tPos / xEnd);
} else {
yEnd = tPos > size / 2 ? size + overflow : -overflow;
xEnd = yEnd / (tPos / lPos);
}
_starsA.add(_Animations(
Tween<double>(begin: lPos, end: xEnd).animate(_curvedA),
Tween<double>(begin: tPos, end: yEnd).animate(_curvedA),
shapeSize,
));
}
}
List<Widget> get _buildList {
final List<Widget> list = [];
final Color color = Color((Random().nextDouble() * 0xFFFFFF).toInt());
for (int i = 0; i < _starsA.length; i++) {
list.add(Positioned(
left: 0,
top: 0,
child: Transform.translate(
offset: Offset(_starsA[i].xAnim.value, _starsA[i].yAnim.value),
child: Transform.rotate(
angle: -4 * pi * _curvedA.value,
child: ClipPath(
clipper: _CircleStarClip(),
child: Container(
color: color.withOpacity(1 - _curvedA.value),
height: _starsA[i].size,
width: _starsA[i].size,
),
),
),
),
));
}
return list;
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _curvedA,
builder: (_, __) {
return Stack(children: _buildList);
},
);
}
}
class _CircleStarClip extends CustomClipper<Path> {
static const _starShrink = 2;
static const _starSides = 5;
static const _deg90 = pi / 2;
@override
Path getClip(Size size) {
final double bigRad = size.width / 2;
final double centerX = size.width / 2;
final double centerY = size.height / 2;
final double smallRad = bigRad / _starShrink;
const double sides = 2 * pi / _starSides;
final Path path = Path()..moveTo(size.width / 2, 0);
for (int i = 0; i < _starSides + 1; i++) {
path.lineTo(cos(sides * i + _deg90) * bigRad + centerX,
sin(sides * i + _deg90) * bigRad + centerY);
path.lineTo(cos(sides * i + _deg90) * smallRad + centerX,
sin(sides * i + _deg90) * smallRad + centerY);
}
return path..close();
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
}
...
提示
- 您可以拥有您的自定义动画和默认动画(星星)一起运行
- 您可以将自定义动画与
StatefulWidget而不是StatelessWidget编码,并使用更传统的方法
这是结果的慢动作
simplePosAnim
之前,我们提到此参数的默认值是
import 'package:cubixd/cubixd.dart';
...
simplePosAnim: SimpleAnimRequirements(
duration: const Duration(seconds: 10),
infinite: true,
reverseWhenDone: false,
xBegin: -pi / 4,
xEnd: (7*pi)/4,
yBegin: pi / 4,
yEnd: pi / 4,
),
...
仅当未设置此参数(simplePosAnim)和 advancedXYposAnim 时,cubixd 才将这些值作为默认动画
另一个例子
import 'package:cubixd/cubixd.dart';
...
simplePosAnim: SimpleAnimRequirements(
duration: const Duration(seconds: 11),
infinite: true,
reverseWhenDone: true,
xBegin: pi / 4,
xCurve: Curves.ease,
xEnd: 2 * pi,
yBegin: -pi / 4,
yCurve: Curves.ease,
yEnd: 4 * pi,
),
...
SimpleAnimRequirements
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| duration * | 持续时间 | – | 主动画应具有的持续时间 |
| infinite | 布尔值 | 真 | 主动画是否应无限播放 |
| reverseWhenDone | 布尔值 | 假 | 完成时 cubixd 是否应向后播放 |
| xBegin * | 双精度 | – | 动画开始时应设置的水平角度(以弧度为单位) |
| xCurve | Curve | Curves.linear |
主动画在水平轴上应具有的曲线 |
| xEnd * | 双精度 | – | 动画结束时应设置的水平角度(以弧度为单位) |
| yBegin * | 双精度 | – | 动画开始时应设置的垂直角度(以弧度为单位) |
| yCurve | Curve | Curves.linear |
主动画在垂直轴上应具有的曲线 |
| yEnd * | 双精度 | – | 动画结束时应设置的垂直角度(以弧度为单位) |
CubixD
CubixD 是显示 3D 立方体的小部件。阴影和旋转动画不是此小部件的一部分,但选择面是此小部件的一部分(几乎),除了用于将 cubixd 放置到其精确正确位置的动画不是此小部件的一部分
参数
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| back * | Widget | – | 应显示在背面一侧的小部件 |
| bottom * | Widget | – | 应显示在底部一侧的小部件 |
| debounceTime | 持续时间 | 持续时间(毫秒: 500) |
防抖时间。cubixd 使用防抖器,这意味着当您不断移动 cubixd 以选择一个面时,它不会执行选择,直到您将其保持静止且面有效,并等待此处指定的持续时间,它将触发选择,否则如果在时间运行之前移动它,它将“反弹”选择并从 0 开始重新计算此指定时间 |
| delta * | Vector2 | – | cubixd 的水平和垂直角度(以弧度为单位)。您可以在下方阅读更多相关信息 |
| front * | Widget | – | 应显示在正面一侧的小部件 |
| left * | Widget | – | 应显示在左侧一侧的小部件 |
| onPanUpdate | VoidCallback | 空 | 更新时调用。这是用户移动 cubixd 以选择面时执行的回调 |
| onSelected | void Function(SelectedSide opt, Vector2 delta) | 空 | 选中时调用。应在用户选择面时触发的回调 |
| right * | Widget | – | 应显示在右侧一侧的小部件 |
| sensitivityFac | 双精度 | 1.0 | 灵敏度因子。就像鼠标移动时有灵敏度一样。cubixd 也有灵敏度。理想情况下,此值应接近 1,而不是 0 或更低。其值越大,灵敏度也越高 |
| size * | 双精度 | – | 每个面应具有的宽度和高度 |
| top * | Widget | – | 应显示在顶面一侧的小部件 |
delta
此参数表示 cubixd 的水平和垂直角度(以弧度为单位)。AnimatedCubixD 使用此参数与 AnimatedBuilder 一起运行动画,通过更新其各自的控制器每次指示它
import 'package:vector_math/vector_math_64.dart' show Vector2;
import 'package:cubixd/cubixd.dart';
...
CubixD(
...
delta: Vector2(verticalAngle, horizontalAngle)
...
),
...
这是一个例子
import 'package:cubixd/cubixd.dart';
...
CubixD(
size: 200.0,
delta: Vector2(pi / 4, pi / 4),
onSelected: (SelectedSide opt, Vector2 delta) {
print('On selected callback:\n\topt = ${opt}\n\tdelta = ${delta}');
},
front: ...,
back: ...,
right: ...,
left: ...,
top: ...,
bottom: ...,
),
...
结果是
附加
SelectedSide
SelectedSide 是一个枚举,有助于了解哪个面被选中
enum SelectedSide { front, back, right, left, top, bottom, none }
计算不精确
请注意,当您想获得特定角度时。水平角度会根据垂直角度“改变”方向,例如
如果垂直角度在 -90° 和 90° 之间。大于 0(正)的水平角度方向是从右到左。
否则,如果垂直角度大于 90°。大于 0(正)的水平角度方向是从左到右。
待办事项
-
也许使用小部件列表
List<Widget>来附加前面、后面、右面、左面、上面和下面的小部件会更好 -
提供控制上下动画的可能性




