在 rive 中改变颜色(耀斑)

Change color in rive (flare)

我有一个小黄人的 .flr 动画。是否可以在 flutter 应用程序中动态地单独更改他的 body、裤子、眼睛等的颜色?

PS 小黄人只是我在 rive.app 上找到的一个例子。会有另一个角色有很多不同的部分。

PPS也许有更好的方法可以在flutter中制作一个简单的动画角色?现在,我有一个带有定位 colorfilterd 图像的堆栈,但我想使用 rive 应该更容易。

是的,你可以。 Flare github中有一个例子: https://github.com/2d-inc/Flare-Flutter/tree/master/example/change_color

import 'package:flare_flutter/flare_controller.dart';
import 'package:flare_flutter/flare.dart';
import 'package:flare_dart/math/mat2d.dart';
import 'package:flare_flutter/flare_actor.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

List<Color> exampleColors = <Color>[Colors.red, Colors.green, Colors.blue];

class _MyHomePageState extends State<MyHomePage> with FlareController {
  FlutterColorFill _fill;
  void initialize(FlutterActorArtboard artboard) {
    // Find our "Num 2" shape and get its fill so we can change it programmatically.
    FlutterActorShape shape = artboard.getNode("Num 2");
    _fill = shape?.fill as FlutterColorFill;
  }

  void setViewTransform(Mat2D viewTransform) {}

  bool advance(FlutterActorArtboard artboard, double elapsed) {
    // advance is called whenever the flare artboard is about to update (before it draws).
    Color nextColor = exampleColors[_counter % exampleColors.length];
    if (_fill != null) {
      _fill.uiColor = nextColor;
    }
    // Return false as we don't need to be called again. You'd return true if you wanted to manually animate some property.
    return false;
  }

  // We're going to use the counter to iterate the color.
  int _counter = 0;
  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
      // advance the controller
      isActive.value = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: FlareActor("assets/change_color_example.flr", // You can find the example project here: https://www.2dimensions.com/a/castor/files/flare/change-color-example
          fit: BoxFit.contain, alignment: Alignment.center, controller: this),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

如果您使用的是 Rive 的测试版,情况会有所不同。我不确定这是否是最好的方法,但我正在执行以下操作:

artboard.forEachComponent((child){
    if (child.name == 'Fill') {
      Fill fill = child;
      fill.paint.color = Colors.red.withOpacity(0.25);
    }
    else if (child.name == 'Stroke') {
      Stroke stroke = child;
      stroke.paint.color = Colors.red;
    }
  });

正如 Kretin 已经在 Flutter rive package 中概述的那样,即使在撰写本文时它是测试版,它也是可能的。

要使其正常工作,您必须了解您的动画,例如您要更改的形状的名称(第一个),以及您要更改的填充颜色。

让我们假设,我有一个形状名称为 'Button1' 的动画。
这个形状有一种我想更改的填充颜色,那么我的代码如下所示:

artboard.forEachComponent((child) {
      if (child is Shape && child.name == 'Button1') {
        final Shape shape = child;
        shape.fills.first.paint.color = Colors.red;
      }
    });

我已经尝试了这里其他答案提到的 forEachComponent 方法,但它并没有像我预期的那样工作。它一遍又一遍地迭代组件,耗费了大量的处理能力,而且因为我正在测试合并颜色,它最终合并了一次又一次。

所以我最终检查了 classes 的属性,以找到如何精确地获取颜色并进行更改。

在我的代码中,我正在更改线性渐变颜色的一部分。但是要获得纯色,您可以更改

shape.fills.first.paintMutator.children.[first/last].color

shape.fills.first.paint.color

我的代码是这样的:

final rootBone =
        artboard.children.firstWhereOrNull((e) => e.name == 'Root Bone');
    final rootBoneChildren = rootBone?.artboard?.drawables;

    // Changing hair color
    final hair =
        rootBoneChildren?.firstWhereOrNull((e) => e.name == 'hair');
    if (hair is Shape) {
      ((hair.fills.first.paintMutator as dynamic)?.children.first
              as dynamic)
          .color = AppColors.accent;
    }

    // Changing head color
    final head =
        rootBoneChildren?.firstWhereOrNull((e) => e.name == 'head');
    if (head is Shape) {
      final colorParts =
          (head.fills.first.paintMutator as dynamic)?.children;
      const mergeColor = AppColors.brown;
      const timeline = 0.9;
      // center
      colorParts.first.color =
          Color.lerp(colorParts.first.color, mergeColor, timeline);
      // border
      colorParts.last.color =
          Color.lerp(colorParts.last.color, mergeColor, timeline);
    }

ps:我将其转换为动态,因为它是一个名为 LinearGradient 的 class,从 paintMutator 原始 class 扩展而来,而 class 来自 rive 的 LinearGradient与 flutter 中的 LinearGradient 同名,我不想仅仅因为这些行就在代码中的任何地方使用别名。