用Tweeny实现丝滑动效

刘慈欣:“好的程序员不去造轮子。”
刘慈欣:“我没说过这句话。”
刘慈欣:“我说过这句话。”

说得简单一些……因为我不知道怎么说得专业些

Tweeny

简单来说,Tweeny可以用来存储对象transition属性的起始状态、末状态、过渡时间、过渡方式等。
我这里是为了单片机开发,在github上找了个Tweeny的C++库

Download

1
git clone https://github.com/mobius3/tweeny.git

Start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include "tweeny/include/tweeny.h"
#include <bits/stdc++.h>
#include <windows.h>
using namespace std;

void gotoxy(SHORT x, SHORT y)
{
COORD coord;
coord.X = x;
coord.Y = y;
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(h, coord);
}

int main()
{
// 从这里开始看,忽略 Sleep() 和 gotoxy()
auto twe = tweeny::from('H', 'e', 'l', 'l', 'o').to('W', 'o', 'r', 'l', 'd').during(10);
cout << "Hello";
/*for (char c : twe.step(0))
{
cout << c;
Sleep(20);
}*/
Sleep(100); // 花里胡哨
for (int i = 0; i < 10; ++i)
{
gotoxy(0, 0); // 花里胡哨
for (char c : twe.step(1))
{
cout << c;
Sleep(20); // 花里胡哨
}
Sleep(100); // 花里胡哨
}

return 0;
}

在上面的代码中,我们实现了这样一个功能:

  1. 输出Hello
  2. Hello中的每一个字母开始不停地变化
  3. 最终,Hello变成了World

首先,我们创建了一个tween对象(它叫什么无所谓,auto就完事了_(:з」∠)_),并告诉他:这个过渡效果有5个元素,最开始分别是'H' 'e' 'l' 'l' 'o',最终的值分别是'W' 'o' 'r' 'l' 'd',整个过渡的过程使用整数10来度量。

1
auto twe = tweeny::from('H', 'e', 'l', 'l', 'o').to('W', 'o', 'r', 'l', 'd').during(10);

让我们跳过cout<<下面那段被注释掉的代码。
cout<<"Hello"Hello打印到了屏幕上。
接下来,我们希望通过差值步进的方式打印每一次步进后的结果。
1
2
3
4
for (char c : twe.step(1))
{
cout << c;
}

这样的步进一共有10次,所以在外面加上一个循环
1
2
3
4
5
6
7
for (int i = 0; i < 10; ++i)
{
for (char c : twe.step(1))
{
cout << c;
}
}

再搭配上Sleep()gotoxy(),就能实现想要的效果啦。

不过,通过cout<<"Hello"的方式实在是太奇怪了,所以我们可以选择每次步进为0,输出最开始的结果,也就是被注释掉的那段代码

1
2
3
4
5
for (char c : twe.step(0))
{
cout << c;
Sleep(20);
}

Step Further

选择过渡函数

一个简单的线性函数可这样实现

1
2
3
4
int linear(float p, int a, int b)
{
return (b - a) * p + a;
}

使用via将它添加到Tweeny对象
1
auto twe = tweeny::from(0).to(100).during(1000).via(linear);

Tweeny库提供了多种内置过渡函数,但我也不知道从哪儿查看,应该是下面这个网站中的不少吧
好康的缓动曲线动画可在http://easings.net查看。

为每个元素单独设置属性

你只需要在函数里分别一一对应地给出。如果每个元素的属性值都相同,你可以只写一个值。

1
2
auto twe1 = tweeny::from(0, 10, 100).to(10, 100, 1000).during(10, 20, 30).via(easing::exponentialIn, easing::exponentialInOut, easing::backOut);
auto twe2 = tweeny::from(0, 10, 100).to(10, 100, 1000).during(100).via(easing::exponentialInOut);

添加多段过渡

多段过度(multipoint)

To allow for that, each call to tween::to adds a new tweening point. Calls to tween::during and tween::via always refer to the last added point:

1
2
3
auto tween = tweeny::from(0)
.to(100).during(500) // 0 to 100 during 500
.to(200).during(100).via(easing::circularOut); // 100 to 200 during 100 via circularOut

stepping & seeking & jumping

step

就像刚才演示的那样。

Passing a integral quantity (integers) to tween::step will step it in duration units. Passing a float value will step it by a percentage (ranging from 0.0f to 1.0f).

后撤步

后撤步,7777
使用tween::backward

前进步

使用tween:forward

seek

让你跳转到任意你想到达的插值处

1
2
auto tween = tweeny::from(0).to(100).during(1000);
tween.seek(0.5f);

jump

当你使用了多段过度(multipoint),jump可以让你跳转到任意关键点(specific tween point)上

1
2
auto tween = tweeny::from(0).to(100).during(100).to(200).during(100);
tween.jump(1);

返回值说明

tweeny::step()tweeny::seek()tweeny::jump()都有返回值。

  • 如果你用的是单值(single value),那么它直接该返回什么就返回什么
  • 如果有多个同类型的值(multiple values of the same type),那么它会返回一个std::array,例如
    1
    2
    auto tween = tweeny::from(0, 1).to(2, 3).during(100);
    std::array<int, 2> v = tween.step(10);
  • 如果是不同的类型(multiple types),那么它会安徽一个std::tuple,例如
    1
    2
    auto tween = tweeny::from(0, 1.0f).to(2, 3.0f).during(100);
    std::tuple<int, float> v = tween.step(10);

总而言之,如果是多个元素,那么它的返回值一定可迭代。

Callbacks

Tweeny允许用户为step添加回调函数(Callbacks),为特殊点配置可运行的程序。比如,当你播放视频时想要自动跳过片头片尾。
回调函数有三种形式:

  • 如果既需要当前值,又需要操控tween对象
    1
    2
    bool stepped(tween<int, int> & t, int x, int y);
    auto tween = tweeny::from(0, 0).to(100, 200).during(100).onStep(stepped);
  • 如果只需要操控tween
    1
    2
    bool stepped(tween<int, int> & t);
    auto tween = tweeny::from(0, 0).to(100, 200).during(100).onStep(stepped);
  • 如果只需要获取当前值
    1
    2
    bool stepped(int x, int y);
    auto tween = tweeny::from(0, 0).to(100, 200).during(100).onStep(stepped);
    回调函数的返回类型通常是布尔型,若返回true,则该函数将从回调函数表中移除(callback list);若返回false,则保留在队列(queue)中。(我也不知道为什么是队列(queue),上边没提这事儿,估计和“回调函数列表”是一回事儿)

    The return type of a callback is always boolean. If it returns true, it will be dismissed and removed from the callback list. Returning false keeps the callback in the queue.
    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    bool stepped(int x, int y)
    {
    printf("x: %d, y: %d\n", x, y);
    if (x == y)
    return true;
    return false;
    }
    auto tween = tweeny::from(0, 0).to(100, 200).during(100).onStep(stepped);

Any functions usable

只要与接口一致,哪里来的回调函数都能用。

1
2
3
4
5
6
7
8
9
10
struct ftor
{
bool operator()(int x, int y)
{
return false;
}
};
auto tween = tweeny::from(0, 0).to(100, 200).during(100);
tween.onStep([](int, int) { return false; }); // lambdas
tween.onStep(ftor()); // functors

Final

I hope you have fun using Tweeny.

附议。

作者

勇敢梧桐树

发布于

2022-12-21

更新于

2023-01-04

许可协议

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×