【问题标题】:How to put several animations into one widget in flutter?如何将多个动画放入一个小部件中?
【发布时间】:2020-09-19 10:38:54
【问题描述】:

我正在尝试为一个小部件添加多个动画,但由于我是 Flutter 的初学者,我不知道我必须使用什么功能来执行此操作。当每个小部件只有一个动画时,这很好。只需将一个布尔值添加到字段并使用 AnimatedContainer 覆盖小部件。像这样,

bool init = false; //Bool about animation

//Somewhere of codes...
child: AnimatedContainer(
    transform: init ? matrixA : matrixB,
    child: FlatButton(
        child: Text("Press Me"),
        onPressed: () {
            //Change init to true
            setState(() {
                init = true;
            });
        }
     ) //FlatButton
) //AnimatedContainer

我可以使用 Flare 执行图标动画,也可以使用 Future.delayed() 执行惰性动画(对不起,如果这种行为是不好的做法,如果我做错了,请告诉我)。问题是一个小部件必须有几个动画,这样的小部件大约有3个。我曾经尝试首先为一个小部件放置 2 个动画。在我的应用程序中,我放置了耀斑图标动画。此动画结束后,我正在显示按钮。当用户点击此按钮时,按钮将上升到特定位置。由于按钮首先必须位于屏幕下方,因此我需要 3 个 Matrix4 变量。我尝试用上面相同的方式将 2 个动画放到一个小部件上,结果就是这样。

bool init = false; //This will make button show up
bool pressed = false; //This will make button go up

//Somewhere of codes...
children: [
    FlareActor("assets/animation.flr",
        animation: "anim",
        callback: (name) {
            //Notify flare animation is ended
            //and make button show up by calling setState
            if(name == "anim") {
                this.setState(() {
                    init = true;
                });
            }
        }), //FlareActor
    AnimatedContainer(
        transform: init ? pressed ? matrixA : matrixB : matrixC, //This is problem
        duration: pressed ? Duration(milliseconds: 300) : Duration(millisecons: 150),
        child:FlatButton(
            child: Text("Press Me"),
            onPressed: () {
                //Change pressed to true to make it move up
                setState(() {
                    pressed = true;
                });
            }
        ) //FlatButton
    ) //AnimatedContainer
]

我知道这些代码不包含完整的信息(如矩阵A、矩阵B、矩阵C),但我认为这足以让您了解我想要做什么。如果您需要有关代码的更多信息,可以在评论中询问我。我只是将 2 个布尔值放入字段中......它已经开始看起来很复杂,尤其是 AnimatedContainer 中的变换参数。想象一下,每 3 个小部件会有 4 个布尔值,总共有 12 个布尔值,这会使代码变得非常混乱,看起来很复杂。

你可能会问我为什么要尝试将多个动画放到 1 个小部件中,但好吧,仅仅是因为我想让应用更生动,而不仅仅是简单的屏幕切换、对话框、或者无聊的动画。当然,我对此进行了搜索,发现的是 Stream 和 AnimatedBuilder。我不知道 Stream 是否可以帮助我,因为即使我使用 Stream,我也无法直接设置小部件的属性(位置、比例、不透明度)。只需将布尔值发送给侦听器,然后调用 setState(),它就变得和上面一样了。 AnimatedBuilder 看起来不错,但我发现的所有帖子都是关于将动画放入多个小部件,而不是将多个动画放入一个小部件

以下是问题:

  1. 这是对 1 个小部件执行多个动画的唯一方法吗?
  2. 如果第一个问题不是,那么执行此操作的更好(或最佳)方法是什么?

【问题讨论】:

  • 我希望你们不要建议将flare作为UI组件...因为这意味着我必须为整个屏幕制作flare,这比像上面添加bools要花费更多时间。

标签: flutter flutter-animation


【解决方案1】:

好的,我认为没有办法改善这种情况,因为... Flutter 不像 Android Studio 那样流行或稳定,但我只是通过手动添加以某种方式改善这种情况我自己的班级叫AnimationManager。我知道代码仍然很复杂,当我为每个方向放置不同的动画时,它会变得更加复杂(这让我很沮丧......),但是至少比放置许多布尔值要好一个小部件,更好看。我仍然希望有人可以帮助我改善比这更好的方式,但我会举一个例子。

这个AnimationManager 可以通过int 变量设置动画,而不是每个小部件的一堆布尔值。假设我们要放置关于DurationTransformOpacity 的动画。我没有测试为很多小部件放置很多动画,所以我真的不知道它的性能。

class AnimationManager {
    static const int DURATION = 0;
    static const int TRANSFORM = 1;
    static const int OPACITY = 2;

    Map map = HashMap<int, List<int>>();

    List du = new List<Duration>();
    List tr = new List<Matrix4>();
    List o = new List<double>();

    void createIndex({@required int index}) {
        List<int> data = map[index]; //Check if index is already created

        if(data != null) {
            throw Exception("Index $index already exists!");
        }

        data = [-1, -1, -1]; // Duration, Transform, Opacity

        map[index] = data;
    }

    void addDuration({@required Duration duration}) {
        du.add(duration); //Add Duration info to du
    }

    void addTransform({@required Matrix4 transform}) {
        tr.add(transform); //Add Transform info to tr
    }

    void addOpacity({@required double opacity}) {
        o.add(opacity); //Add Opacity info to o
    }

    void setDuration({@required int index, @required int mode}) {
        //Set specific duration mode for specific index data

        List data = map[index];

        data[DURATION] = mode;

        map[index] = data;
    }

    void setTransform({@required int index, @required int mode}) {
        //Do same thing like Duration
    }

    void setOpacity({@required int index, @required int mode}) {
        //Do same thing like Duration
    }

    Duration getDuration({@required int mode}) {
        return mode < 0 ? Duration(milliseconds: 300) : du[mode]; //NOT SAFE
    }

    Matrix4 getTransform({@required int mode}) {
        //Do same thing like getDuration
    }

    double getOpacity({@required int mode}) {
        //Do same thing like getDuration
    }

    Widget build({@required int index, @required Widget child}) {
        //Put child into AnimatedContainer with specific index data

        List data = map[index];

        return AnimatedContainer(
            duration: getDuration(data[DURATION]),
            transform: getTransform(data[TRANSFORM]),
            child: child
        );
    }

    Widget buildWithOpacity({@required int index, @required Widget child}) {
        List data = map[index];

        return AnimatedContainer(
            duration: getDuration(data[DURATION]),
            transform: getTransform(data[TRANSFORM]),
            child: AnimatedOpacity(
                duration: getDuration(data[DURATION]),
                opacity: getOpacity(data[OPACITY]),
                child: child
            ));
    }
}

这段代码缺乏安全检查,但由于我只是给出概念,这就足够了。这个例子只能包含持续时间、变换、不透明度数据,但实际上我也添加了曲线、高度、宽度数据。可以这样使用

AnimationManager am = AnimationManager();

bool initialized = false;

@override Widget build(BuildContext context) {
    if(!initialized) {
        //Initialize data
        am.addDuration(duration: Duration(seconds: 1));

        am.addTransform(transform: matrixA);
        am.addTransform(transform: matrixB);
        am.addTransform(transform: matrixC);

        am.addOpacity(opacity: 0);
        am.addOpacity(opacity: 1);

        am.createIndex(index: 0);

        am.setDuration(index: 0, mode: 0);
        am.setTransform(index: 0, mode: 0);
        am.setOpacity(index: 0, mode: 1);

        initialized = true;
    }

    //Somewhere of codes...
    children: <Widget> [
        am.buildWithOpacity(
            index: 0 //Build AnimatedContainer with specified index data
            child: FlatButton(
                child: Text("Press me"),
                onPressed: () {
                    setState() {
                        am.setTransform(index: 0, mode: 1);
                        am.setOpacity(index:0, mode: 0);
                    }
                }
            )
        ),
        //Add more Widget which needs animation
    ]
}

如果那里有很多小部件,仍然代码会很复杂,但我更喜欢这种方式而不是放置 bool 并制作像 init ? pressed ? done ? matrixA : matrixB : matrixC : matrixD 这样的长 1 行代码。这里唯一的问题是内存处理,因为如果我们使用 bools ,这些Matrix4Duration等会被GC临时创建和收集,但现在AnimationManager保存了每一个动画数据。但仍然更好,因为这种方式可以处理方向变化,或者当出现具有不同方向布局的动画时比放置布尔值更容易。下面的gif图是使用AnimationManager的测试结果,如果setState是可调用的,则可以在任何地方进行控制。

【讨论】:

  • 我不会把它作为公认的答案,因为这种方式只是让代码看起来更好,而不是解决复杂性。代码仍然很长很复杂,或者我可能只是不知道动画这么难。
猜你喜欢
  • 2011-06-10
  • 2011-03-23
  • 2011-01-18
  • 2019-01-30
  • 1970-01-01
  • 1970-01-01
  • 2017-04-09
  • 1970-01-01
  • 2023-01-12
相关资源
最近更新 更多