0
votes

I'm trying to put several animations for one widget, but as I'm beginner of Flutter, I don't know what feature I have to use to perform this. When there was only one animation per one widget, it was fine. Just add one bool to field and cover widget with AnimatedContainer. Like this,

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

I can perform icon animation using Flare, and also can perform lazy animation using Future.delayed() (Sorry, if this behavior is bad practice, advise me if I'm doing wrong). Problem is that there must be several animations for one widget, and there will be about 3 widgets like this. I once tried to put 2 animation for one widget first of all. In my app, I put flare icon animation. After this animation ends, I'm making button show up. When users tap this button, button will go up to specific position. Since button must be at below screen at first, I will need 3 Matrix4 variables. I tried to put 2 animation to one widget with same way above and this was the result.

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
]

I know this codes don't contain full information (like matrixA, matrixB, matrixC), but I think this is enough to make you understand what I'm trying to do. If you need more information about codes, you can ask me at comment. I just put 2 bools into field and... it already started to look complicated, especially transform parameter in AnimatedContainer. Imagine that there will be 4 bools for each 3 widgets, there will be 12 bools in total, and this will make codes a lot messed up and look like complicated.

You may ask why I'm trying to put several animations to 1 widget, but well, just simply because I'm trying to make app more alive, not just simple screen change, dialog, or boring animation. Of course, I searched about this and what I found was Stream and AnimatedBuilder. I don't know if Stream can help me because even though I use Stream, I can't directly set property (location, scale, opacity) of widgets. Just sending boolean to listener, and calling setState(), and it becomes same like above. AnimatedBuilder looked fine, but all posts I found were about Putting animation to several widgets, not Putting several animation to one widget.

So here are the questions :

  1. Is this the only way to perform several animations to 1 widget?
  2. If no in first question, what will be better (or the best) way to perform this?
1
I hope you guys don't suggest flare as UI component... Because that means I will have to make flare for whole one screen which will take more time than adding bools like above.Mandarin Smell

1 Answers

1
votes

Okay, I think there is no way to improve this situation, because... Flutter isn't yet that popular or stable unlike Android Studio, but I just somehow improved this situation by manually adding my own class called AnimationManager. I know codes are still complex and it becomes a lot more complex when I put different animation for each orientation (and this is making me frustrated...), but it's at least better than putting many bools for one widget and better to look. I'm still hoping that there will be someone who can help me to improve way better than this yet, but I will give an example.

Instead of bunch of bools for each widgets, this AnimationManager can set Animation by int variable. Let's assume that we want to put animation about Duration, Transform, and Opacity. I didn't test putting many animations for many widgets, so I really don't know its performance yet.

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
            ));
    }
}

This code lacks safe checking, but since I'm just giving concept, this will be enough. This example only can contain duration, transform, opacity data, but actually I added curve, height, width data too. It can be used like this

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
    ]
}

Still codes will be complicated if there are many widgets in there, but I prefer this way than putting bools and making long 1 lined codes like init ? pressed ? done ? matrixA : matrixB : matrixC : matrixD, etc. The only problem here will be memory handling because if we use bools, these Matrix4, Duration, etc will be temporarily created and collected by GC, but now AnimationManager holds every animation data. But still better because this way can handle orientation change, or situation when there will be animation with different orientation layout easier than putting bools. Gif picture below is test result using AnimationManager, it can be controlled anywhere if setState is callable.

Multiple animation