在 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 同名,我不想仅仅因为这些行就在代码中的任何地方使用别名。
我有一个小黄人的 .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 同名,我不想仅仅因为这些行就在代码中的任何地方使用别名。