Search code examples
flutterdartrive

How do I change the start/end locations of gradient fills in Rive/Flare programatically in Flutter?


I have a simple ball that uses a gradient fill to make it look like it's lit from one side:

Ball

I'd like to modify the gradient fill at runtime in Flutter (for example to make it seem like the light is moving around relative to the ball). I was able to find the coordinates like this:

final fill = (artboard.getNode("Ellipse") as FlutterActorShape).fill as FlutterRadialFill;
print(fill.renderStart);
print(fill.renderEnd);

However, I can't find a way to modify these values. I tried using Vec2D to overwrite the values, however it doesn't change the rendering (perhaps because something was computed from these values that needs invalidating?):

Vec2D.copy(
          _fill.renderStart,
          Vec2D.fromValues(
              200 - _component.x, 200 - _component.y));

Solution

  • There isn't a nice helpful setter for this property (keyframes manipulate it and invalidate the internal state), but with a little bit of extra verbose (we should really make this friendlier) code you can do it:

    
    class MutateGradient extends FlareController {
      // Store the fill so you don't need to look it up each frame.
      FlutterRadialFill _radialFill;
    
      // I chose to drive the end position of the gradient with a sin wave so i use
      // a field to accumulate the phase.
      double _phase = 0;
    
      @override
      bool advance(FlutterActorArtboard artboard, double elapsed) {
        if (_radialFill == null) {
          // Didn't find the fill during init, early out with a false meaning we're
          // done.
          return false;
        }
    
        _phase += elapsed;
    
        // No nice setter on the end so we have to set the values manually and then
        // mark the paint dirty so the update loop updates the actual paint and
        // points used to render. Note that the position here can be either in the
        // path's local transform or world (artboard) transform depending on whether
        // transformAffectsStroke was selected in Rive.
        Vec2D.copy(
            _radialFill.end, Vec2D.fromValues(-100, 100 + sin(_phase*2) * 50));
        _radialFill.markPaintDirty();
    
        // Return true to get another call the next frame...
        return true;
      }
    
      @override
      void initialize(FlutterActorArtboard artboard) {
        // Find the fill we want to manipulate.
        var node = artboard.getNode("Ellipse");
        if (node is FlutterActorShape) {
          var fill = node.fill;
          if (fill is FlutterRadialFill) {
            _radialFill = fill;
          }
        }
      }
    
      @override
      void setViewTransform(Mat2D viewTransform) {
        // Inentionally empty, we don't need to convert from artboard (world) to
        // view space in this example.
      }
    }
    

    Send hatemail to [email protected] or feel free to publicly shame me @luigirosso <3