@@ -7,6 +7,7 @@ import 'package:rive_example/main.dart' show RiveExampleApp;
77/// Example using Rive data binding at runtime.
88///
99/// See: https://rive.app/docs/runtimes/data-binding
10+ /// Rive Editor file: https://rive.app/marketplace/25475-47540-data-binding-demo/
1011class ExampleDataBinding extends StatefulWidget {
1112 const ExampleDataBinding ({super .key});
1213
@@ -21,8 +22,17 @@ class _ExampleDataBindingState extends State<ExampleDataBinding> {
2122 late ViewModelInstance viewModelInstance;
2223 late ViewModelInstance coinItemVM;
2324 late ViewModelInstance gemItemVM;
24- late ViewModelInstanceNumber coinValue;
25- late ViewModelInstanceNumber gemValue;
25+ late ViewModelInstanceNumber coinProperty;
26+ late ViewModelInstanceNumber gemProperty;
27+ late ViewModelInstanceNumber energyBarProperty;
28+ late ViewModelInstanceNumber energyBarLivesProperty;
29+ late ViewModelInstanceColor energyBarColorProperty;
30+ late ViewModelInstanceString buttonTitleProperty;
31+ late ViewModelInstanceTrigger buttonPressedProperty;
32+
33+ double _sliderValue = 0.5 ;
34+ late Stream <double > _energyBarStream;
35+ Color _selectedColor = const Color (0xFF4CAF50 );
2636
2737 @override
2838 void initState () {
@@ -36,71 +46,257 @@ class _ExampleDataBindingState extends State<ExampleDataBinding> {
3646 riveFactory: RiveExampleApp .getCurrentFactory,
3747 );
3848 controller = RiveWidgetController (file! );
39- _initViewModel ();
49+ _setupDataBinding ();
4050 setState (() {});
4151 }
4252
43- void _initViewModel () {
53+ void _setupDataBinding () {
54+ // Bind the default view model instance of the artboard to the controller
4455 viewModelInstance = controller! .dataBind (DataBind .auto ());
56+
57+ // Set a random token to reward
4558 _selectRandomToken ();
59+
4660 // Print the view model instance properties
4761 debugPrint (viewModelInstance.properties.toString ());
62+
4863 // Get the rewards view model
4964 coinItemVM = viewModelInstance.viewModel ('Coin' )! ;
5065 gemItemVM = viewModelInstance.viewModel ('Gem' )! ;
51- debugPrint (
52- coinItemVM.toString ()); // Print the view model instance properties
5366
54- coinValue = coinItemVM.number ('Item_Value' )! ;
55- gemValue = gemItemVM.number ('Item_Value' )! ;
56- // Listen to the changes on the Item_Value input
57- coinValue.addListener (_onCoinValueChange);
58- coinValue.value = 1000 ; // set the initial coin value to 1000
67+ // Print the view model instance properties
68+ debugPrint (coinItemVM.toString ());
69+
70+ // Get the Item_Value number properties for the coin and gem
71+ coinProperty = coinItemVM.number ('Item_Value' )! ;
72+ gemProperty = gemItemVM.number ('Item_Value' )! ;
73+
74+ // Listen to the changes on the Item_Value for the gen and coin
75+ coinProperty.addListener (_onCoinValueChange);
76+ gemProperty.addListener (_onGemValueChange);
77+
78+ // Set the initial values for the coin and gem
79+ coinProperty.value = 1000 ;
80+ gemProperty.value = 4000 ;
81+
82+ // Get the Energy_Bar/Energy_Bar number property
83+ energyBarProperty = viewModelInstance.number ('Energy_Bar/Energy_Bar' )! ;
84+ // Create a stream for the energy bar
85+ _energyBarStream = energyBarProperty.valueStream;
86+
87+ // Get the Energy_Bar/Energy_Bar lives number property
88+ energyBarLivesProperty = viewModelInstance.number ('Energy_Bar/Lives' )! ;
89+
90+ // Get the Energy_Bar/Energy_Bar color property
91+ energyBarColorProperty = viewModelInstance.color ('Energy_Bar/Bar_Color' )! ;
5992
60- gemValue.addListener (_onGemValueChange);
61- gemValue.value = 4000 ; // set the initial gem value to 4000
93+ // Get the Button/Button_Title string property
94+ buttonTitleProperty = viewModelInstance.string ('Button/State_1' )! ;
95+
96+ // Get the Button/Button_Trigger trigger property
97+ buttonPressedProperty = viewModelInstance.trigger ('Button/Pressed' )! ;
98+
99+ // Listen to the changes on the Button/Pressed trigger
100+ buttonPressedProperty.addListener (_onButtonPressed);
62101 }
63102
103+ // Randomly select to reward either coins or gems
64104 void _selectRandomToken () {
65105 final random = Random .secure ().nextBool () ? 'Coin' : 'Gem' ;
66- // We randomly select to reward either coins or gems
67106 viewModelInstance
68107 .viewModel ('Item_Selection' )!
69108 .enumerator ('Item_Selection' )!
70109 .value = random;
71110 }
72111
112+ // Listener for the changes on the Item_Value for the coin
73113 void _onCoinValueChange (double value) {
74114 debugPrint ('New coin value: $value ' );
75115 }
76116
117+ // Listener for the changes on the Item_Value for the gem
77118 void _onGemValueChange (double value) {
78119 debugPrint ('New gem value: $value ' );
79120 }
80121
122+ void _onButtonPressed (bool value) {
123+ debugPrint ('Button pressed' );
124+ }
125+
81126 @override
82127 void dispose () {
83- coinValue.removeListener (_onCoinValueChange);
84- gemValue.removeListener (_onGemValueChange);
85- coinValue.dispose ();
86- gemValue.dispose ();
128+ // Listeners must be removed
129+ coinProperty.removeListener (_onCoinValueChange);
130+ gemProperty.removeListener (_onGemValueChange);
131+
132+ // Disposing the properties would also remove the listeners.
133+ // This is a best practice to immediately remove allocated native resources.
134+ // However, they will get garbage collected by the Dart runtime as they are
135+ // Finalizers, and it's not strictly necessary to dispose here.
136+ coinProperty.dispose ();
137+ gemProperty.dispose ();
87138 coinItemVM.dispose ();
88139 gemItemVM.dispose ();
140+ energyBarProperty.dispose ();
141+ energyBarLivesProperty.dispose ();
142+ energyBarColorProperty.dispose ();
143+ buttonTitleProperty.dispose ();
144+ buttonPressedProperty.dispose ();
145+
89146 controller? .dispose ();
90147 file? .dispose ();
91148 super .dispose ();
92149 }
93150
151+ void _showConfigSheet (BuildContext context) {
152+ if (controller == null ) return ;
153+
154+ showModalBottomSheet (
155+ context: context,
156+ builder: (context) => StatefulBuilder (
157+ builder: (context, setSheetState) => Container (
158+ padding: const EdgeInsets .all (24 ),
159+ child: SingleChildScrollView (
160+ child: Column (
161+ mainAxisSize: MainAxisSize .min,
162+ crossAxisAlignment: CrossAxisAlignment .start,
163+ children: [
164+ Text (
165+ 'Configuration' ,
166+ style: Theme .of (context).textTheme.titleLarge,
167+ ),
168+ const SizedBox (height: 24 ),
169+ // This is just an example of using a value stream
170+ StreamBuilder <double >(
171+ stream: _energyBarStream,
172+ builder: (context, snapshot) => Text (
173+ 'Energy: ${snapshot .data ?.toStringAsFixed (2 ) ?? '0.00' }' ,
174+ style: Theme .of (context).textTheme.bodyMedium,
175+ ),
176+ ),
177+ Slider (
178+ value: _sliderValue,
179+ onChanged: (value) {
180+ setSheetState (() => _sliderValue = value);
181+ energyBarProperty.value = value * 100 ;
182+ },
183+ ),
184+ const SizedBox (height: 24 ),
185+ Text (
186+ 'Bar Color' ,
187+ style: Theme .of (context).textTheme.bodyMedium,
188+ ),
189+ const SizedBox (height: 12 ),
190+ Wrap (
191+ spacing: 8 ,
192+ runSpacing: 8 ,
193+ children: [
194+ const Color (0xFF4CAF50 ), // Green
195+ const Color (0xFF2196F3 ), // Blue
196+ const Color (0xFFF44336 ), // Red
197+ const Color (0xFFFF9800 ), // Orange
198+ const Color (0xFF9C27B0 ), // Purple
199+ const Color (0xFFFFEB3B ), // Yellow
200+ const Color (0xFF00BCD4 ), // Cyan
201+ const Color (0xFFE91E63 ), // Pink
202+ ].map ((color) {
203+ // ignore: deprecated_member_use
204+ final isSelected = _selectedColor.value == color.value;
205+ return GestureDetector (
206+ onTap: () {
207+ setSheetState (() => _selectedColor = color);
208+ energyBarColorProperty.value = color;
209+ },
210+ child: Container (
211+ width: 40 ,
212+ height: 40 ,
213+ decoration: BoxDecoration (
214+ color: color,
215+ shape: BoxShape .circle,
216+ border: Border .all (
217+ color:
218+ isSelected ? Colors .white : Colors .transparent,
219+ width: 3 ,
220+ ),
221+ boxShadow: isSelected
222+ ? [
223+ BoxShadow (
224+ // ignore: deprecated_member_use
225+ color: color.withOpacity (0.6 ),
226+ blurRadius: 8 ,
227+ spreadRadius: 2 ,
228+ ),
229+ ]
230+ : null ,
231+ ),
232+ ),
233+ );
234+ }).toList (),
235+ ),
236+ const SizedBox (height: 16 ),
237+ Text (
238+ 'Lives: ${energyBarLivesProperty .value }' ,
239+ style: Theme .of (context).textTheme.bodyMedium,
240+ ),
241+ const SizedBox (height: 16 ),
242+ Slider (
243+ value: energyBarLivesProperty.value,
244+ onChanged: (value) {
245+ energyBarLivesProperty.value = value;
246+ setSheetState (() {});
247+ },
248+ min: 0 ,
249+ max: 10 ,
250+ divisions: 10 ,
251+ label: 'Lives' ,
252+ ),
253+ const SizedBox (height: 16 ),
254+ Text (
255+ 'Button Title: ${buttonTitleProperty .value }' ,
256+ style: Theme .of (context).textTheme.bodyMedium,
257+ ),
258+ const SizedBox (height: 16 ),
259+ TextField (
260+ onChanged: (value) {
261+ buttonTitleProperty.value = value;
262+ setSheetState (() {});
263+ },
264+ decoration: const InputDecoration (
265+ labelText: 'Button Title' ,
266+ border: OutlineInputBorder (),
267+ ),
268+ ),
269+ ],
270+ ),
271+ ),
272+ ),
273+ ),
274+ );
275+ }
276+
94277 @override
95278 Widget build (BuildContext context) {
96279 final controller = this .controller;
97280 if (controller == null ) {
98281 return const Center (child: CircularProgressIndicator ());
99282 }
100- return RiveWidget (
101- controller: controller,
102- fit: Fit .layout, // for responsive layouts
103- layoutScaleFactor: 1 / 2.0 ,
283+ return Stack (
284+ children: [
285+ RiveWidget (
286+ controller: controller,
287+ fit: Fit .layout, // for responsive layouts
288+ layoutScaleFactor: 1 / 2.0 ,
289+ ),
290+ Positioned (
291+ bottom: 16 ,
292+ right: 16 ,
293+ child: IconButton .filled (
294+ icon: const Icon (Icons .tune),
295+ onPressed: () => _showConfigSheet (context),
296+ tooltip: 'Configuration' ,
297+ ),
298+ ),
299+ ],
104300 );
105301 }
106302}
0 commit comments