I am a beginner to rive and flutter. I am building a favorite items page in flutter. If there are not anything added to favorites I need to show a riveAnimation on screen. I already implemented almost everything to show the animation on screen. But I need to toggle a jumping animation when user tap on the animation which is really cool. for now I have the animation on 'Idle' mode
You may want to refer to the rive file => Go to Rive. And I renamed Rive stateMachine name to Bird. Everything else is the same.
summary => I want bird to jump when user tap on him :)
The code and the image may be little bit bigger. Sorry about that
class Favourites extends StatefulWidget {
Favourites({Key? key}) : super(key: key);
#override
State<Favourites> createState() => _FavouritesState();
}
class _FavouritesState extends State<Favourites> {
String animation = 'idle';
SMIInput<String>? _birdInput;
Artboard? _birdArtboard;
void jump() {
setState(() {
_birdInput?.value = 'Pressed';
});
}
#override
void initState() {
super.initState();
rootBundle.load('assets/rive/bird.riv').then(
(data) {
final file = RiveFile.import(data);
final artboard = file.mainArtboard;
var controller = StateMachineController.fromArtboard(
artboard,
'Bird',
);
if (controller != null) {
artboard.addController(controller);
_birdInput = controller.findInput('Pressed');
}
setState(() => _birdArtboard = artboard);
},
);
}
#override
Widget build(BuildContext context) {
final favourite = Provider.of<Favourite>(context);
return Scaffold(
backgroundColor: Colors.grey[300],
appBar: const CustomAppBar(title: 'Favourites'),
body: favourite.items.isEmpty
? Center(
child: Column(
children: [
SizedBox(
width: 300,
height: 500,
child: _birdArtboard == null
? const SizedBox()
: Center(
child: GestureDetector(
onTap: () {},
child: Rive(artboard: _birdArtboard!),
),
),
),
NeumorphicButton(),
],
),
)
: CustomGrid(),
);
}
}
If you open/run rive file on rive site, you can find that it is using Trigger variable for jumping and it is using State Machine 1 state machine.
Next thing comes about declaring variable. You need to use SMITrigger data type for this and use StateMachineController to control the animation.
Use .findSMI(..) instead of .findInput() for SMITrigger.
To start animation on trigger, use
trigger?.fire();
I will encourage you to take a look on editor and check input variable type while performing rive animation.
So the full widget that will provide animation is
class Favourites extends StatefulWidget {
const Favourites({Key? key}) : super(key: key);
#override
State<Favourites> createState() => _FavouritesState();
}
class _FavouritesState extends State<Favourites> {
String animation = 'idle';
Artboard? _birdArtboard;
SMITrigger? trigger;
StateMachineController? stateMachineController;
#override
void initState() {
super.initState();
rootBundle.load('assets/rive/bird.riv').then(
(data) {
final file = RiveFile.import(data);
final artboard = file.mainArtboard;
stateMachineController =
StateMachineController.fromArtboard(artboard, "State Machine 1");
if (stateMachineController != null) {
artboard.addController(stateMachineController!);
trigger = stateMachineController!.findSMI('Pressed');
stateMachineController!.inputs.forEach((e) {
debugPrint(e.runtimeType.toString());
debugPrint("name${e.name}End");
});
trigger = stateMachineController!.inputs.first as SMITrigger;
}
setState(() => _birdArtboard = artboard);
},
);
}
void jump() {
trigger?.fire();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[300],
body: Center(
child: Column(
children: [
SizedBox(
width: 300,
height: 400,
child: _birdArtboard == null
? const SizedBox()
: Center(
child: GestureDetector(
onTap: () {
jump();
},
child: Rive(artboard: _birdArtboard!),
),
),
),
],
),
));
}
}
Related
In my Flutter project for Windows, I want to be able to read locally at least 4 videos simultaneously (up to 1920 x 1080, up to 60 fps).
The videos can be of different formats (mp4, wmv, ...), different sizes and different framerates.
Using dart_vlc, I created a test project with 4 video players:
void main()
{
DartVLC.initialize();
runApp(const DartVLCExample());
}
class DartVLCExample extends StatelessWidget
{
const DartVLCExample({Key? key}) : super(key: key);
#override
Widget build(BuildContext context)
{
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('package:dart_vlc'),
centerTitle: true,
),
body: const PrimaryScreen(),
),
);
}
}
class PrimaryScreen extends StatelessWidget
{
const PrimaryScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context)
{
return Row(
children: const [
Expanded(child: VideoPlayer(index: 1)),
Expanded(child: VideoPlayer(index: 2)),
Expanded(child: VideoPlayer(index: 3)),
Expanded(child: VideoPlayer(index: 4)),
],
);
}
}
class VideoPlayer extends StatefulWidget
{
final int index;
const VideoPlayer({
super.key,
required this.index,
});
#override
VideoPlayerState createState() => VideoPlayerState();
}
class VideoPlayerState extends State<VideoPlayer>
{
late Player player;
MediaType mediaType = MediaType.file;
CurrentState current = CurrentState();
PositionState position = PositionState();
PlaybackState playback = PlaybackState();
GeneralState general = GeneralState();
VideoDimensions videoDimensions = const VideoDimensions(0, 0);
List<Media> medias = <Media>[];
List<Device> devices = <Device>[];
TextEditingController controller = TextEditingController();
TextEditingController metasController = TextEditingController();
double bufferingProgress = 0.0;
Media? metadataCurrentMedia;
#override
void initState()
{
super.initState();
if (mounted) {
player = Player(
id: widget.index,
videoDimensions: const VideoDimensions(640, 360),
);
player.currentStream.listen((value) {
setState(() => current = value);
});
player.positionStream.listen((value) {
setState(() => position = value);
});
player.playbackStream.listen((value) {
setState(() => playback = value);
});
player.generalStream.listen((value) {
setState(() => general = value);
});
player.videoDimensionsStream.listen((value) {
setState(() => videoDimensions = value);
});
player.bufferingProgressStream.listen(
(value) {
setState(() => bufferingProgress = value);
},
);
player.errorStream.listen((event) {
debugPrint('libVLC error.');
});
devices = Devices.all;
Equalizer equalizer = Equalizer.createMode(EqualizerMode.live);
equalizer.setPreAmp(10.0);
equalizer.setBandAmp(31.25, 10.0);
player.setEqualizer(equalizer);
medias.add(
Media.file(File("C:\\test${widget.index}.mp4"),
),
);
}
}
#override
Widget build(BuildContext context)
{
return ListView(
children: [
ClipRRect(
child: Video(
player: player,
width: 640,
height: 650,
volumeThumbColor: Colors.blue,
volumeActiveColor: Colors.blue,
showControls: true,
),
),
ElevatedButton(
onPressed: () => setState(() {
player.open(Playlist(medias: medias));
},
),
child: const Text('PLAY', style: TextStyle(fontSize: 16)),
),
],
);
}
}
But unfortunately, if I play 4 videos (1920 x 1080, 60 fps) simultaneously, the videos are jerky.
I tried to read the same videos with the official VLC player, and it works fine, so it's apparently not a problem with my machine, but rather an optimization issue. And since I'm a beginner when it comes to video players, I don't really know where to start.
So how can I optimize that, using dart_vlc or something else?
Thanks.
I'm trying to scrape a website using the package web_scraper, where I want the user to click a button and the new link opens where new scrapped images can be shown.
class Top2 extends StatefulWidget {
const Top2({Key? key}) : super(key: key);
#override
State<Top2> createState() => _Top2State();
}
class _Top2State extends State<Top2> {
late List<Map<String, dynamic>> top2Wall;
bool top2Loaded = false;
int page = 2;
void top2Fetch() async {
final top2Scraper = WebScraper('https://mobile.alphacoders.com');
if (await top2Scraper
.loadWebPage('/by-category/3?page=$page&quickload=1')) {
top2Wall = top2Scraper.getElement('div.item > a > img', ['src', 'title']);
// ignore: avoid_print
print(top2Wall);
setState(() {
top2Loaded = true;
});
}
}
#override
void initState() {
super.initState();
top2Fetch();
}
#override
Widget build(BuildContext context) {
Size screenSize = MediaQuery.of(context).size;
return Scaffold(
body: top2Loaded
// ignore: sized_box_for_whitespace
? Container(
height: screenSize.height,
width: double.infinity,
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Wrap(children: [
for (int i = 1; i < top2Wall.length; i++)
WallCard(src: top2Wall[i]['attributes']['src']),
InkWell(
onTap: () {
setState(() {
page++;
// ignore: avoid_print
print(page);
});
},
child: Container(
height: 100,
width: 100,
decoration: const BoxDecoration(color: Colors.cyan)),
)
]),
),
)
: const Center(
child: CircularProgressIndicator(color: Colors.cyanAccent),
));
}[image2][1]
}
For a clear explanation I want to change the page link for scraping, so basically the page number=1 i want to increase it by a number, when user clicks on the container. I used SetState and page++ to increment it by a digit everytime the user clicks on it. I used the print statement to check wether the page increments or not and its sucessfully increases but the page remains same, for clear code view please refer these images enter image description here
enter image description here
If I understand what your issue is, after calling increment page ++ in setstate, you have to call top2Fetch() again inside the onTap method. Like this:
onTap: ()async {
setState(()=>page++);
await top2Fetch();
}
Then update your UI.
You can include a bool value called isLoading in the onTap method: such that when you call the top2Fetch method , a loading spinner is shown on the UI till the future is done.
I've created a .riv file with 3 state animations: start, processing, end, which are in "State machine". Rive team recently announced a new feature with dinamically changing animations, it's "State machine". Not sure, how to use it in flutter project, i.e how to dynamically change value of animation. If somebody needs some code, no problem, I could provide. Moreover, link to rive's "state machine" https://www.youtube.com/watch?v=0ihqZANziCk. I didn't find any examples related to this new feature. Please help! Thanks.
The other answer is outdated.
class SimpleStateMachine extends StatefulWidget {
const SimpleStateMachine({Key? key}) : super(key: key);
#override
_SimpleStateMachineState createState() => _SimpleStateMachineState();
}
class _SimpleStateMachineState extends State<SimpleStateMachine> {
SMITrigger? _bump;
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(artboard, 'bumpy');
artboard.addController(controller!);
_bump = controller.findInput<bool>('bump') as SMITrigger;
}
void _hitBump() => _bump?.fire();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Simple Animation'),
),
body: Center(
child: GestureDetector(
child: RiveAnimation.network(
'https://cdn.rive.app/animations/vehicles.riv',
fit: BoxFit.cover,
onInit: _onRiveInit,
),
onTap: _hitBump,
),
),
);
}
}
See the RIVE guide:
https://help.rive.app/runtimes/state-machines
There are examples on rives pub package site. Here is one for state machine.
example_state_machine.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:rive/rive.dart';
/// An example showing how to drive two boolean state machine inputs.
class ExampleStateMachine extends StatefulWidget {
const ExampleStateMachine({Key? key}) : super(key: key);
#override
_ExampleStateMachineState createState() => _ExampleStateMachineState();
}
class _ExampleStateMachineState extends State<ExampleStateMachine> {
/// Tracks if the animation is playing by whether controller is running.
bool get isPlaying => _controller?.isActive ?? false;
Artboard? _riveArtboard;
StateMachineController? _controller;
SMIInput<bool>? _hoverInput;
SMIInput<bool>? _pressInput;
#override
void initState() {
super.initState();
// Load the animation file from the bundle, note that you could also
// download this. The RiveFile just expects a list of bytes.
rootBundle.load('assets/rocket.riv').then(
(data) async {
// Load the RiveFile from the binary data.
final file = RiveFile.import(data);
// The artboard is the root of the animation and gets drawn in the
// Rive widget.
final artboard = file.mainArtboard;
var controller =
StateMachineController.fromArtboard(artboard, 'Button');
if (controller != null) {
artboard.addController(controller);
_hoverInput = controller.findInput('Hover');
_pressInput = controller.findInput('Press');
}
setState(() => _riveArtboard = artboard);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(
title: const Text('Button State Machine'),
),
body: Center(
child: _riveArtboard == null
? const SizedBox()
: MouseRegion(
onEnter: (_) => _hoverInput?.value = true,
onExit: (_) => _hoverInput?.value = false,
child: GestureDetector(
onTapDown: (_) => _pressInput?.value = true,
onTapCancel: () => _pressInput?.value = false,
onTapUp: (_) => _pressInput?.value = false,
child: SizedBox(
width: 250,
height: 250,
child: Rive(
artboard: _riveArtboard!,
),
),
),
),
),
);
}
}
I want to make a reusable button with a container in GestureDetector which will execute some function if I tap it and its color will become dark if I hold it. Any help, hint, tip would be very much appreciated.
I tried writing the GestureDetector in the custom widget file but it gives me errors.
When i try to extract widget on the GestureDetector it gives an Reference to an enclosing class method cannot be extracted error.
(the main page)
import 'package:flutter/material.dart';
import 'ReusableTwoLineList.dart';
import 'Text_Content.dart';
const mainTextColour = Color(0xFF212121);
const secondaryTextColour = Color(0xFF757575);
const inactiveBackgroundCardColor = Color(0xFFFFFFFF);
const activeBackgroundCardColor = Color(0xFFE5E5E5);
enum CardState {
active,
inactive,
}
class SettingsPage extends StatefulWidget {
#override
_SettingsPageState createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
CardState currentCardState = CardState.inactive;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Settings'),
),
body: ListView(
children: <Widget>[
GestureDetector(
onTapDown: (TapDownDetails details) {
setState(() {
currentCardState = CardState.active;
});
},
onTapCancel: () {
setState(() {
currentCardState = CardState.inactive;
});
},
onTap: () {
setState(() {
currentCardState = CardState.inactive;
//some random function
});
},
child: ReusableTwoLineList(
mainTextColor: mainTextColour,
secondaryTextColor: secondaryTextColour,
backgroundCardColor: currentCardState == CardState.active
? activeBackgroundCardColor
: inactiveBackgroundCardColor,
cardChild: TextContent(
mainLabel: 'First Day',
secondaryLabel: 'This is the first day of the week',
),
),
),
ReusableTwoLineList(
mainTextColor: mainTextColour,
secondaryTextColor: secondaryTextColour,
cardChild: TextContent(
mainLabel: '2nd day',
secondaryLabel: 'This is the end day',
),
),
ReusableTwoLineList(
mainTextColor: mainTextColour,
secondaryTextColor: secondaryTextColour,
),
],
),
);
}
}
ReusableTwoLineList.dart (the custom widget i am trying to make)
class ReusableTwoLineList extends StatelessWidget {
ReusableTwoLineList({
#required this.mainTextColor,
#required this.secondaryTextColor,
this.backgroundCardColor,
this.cardChild,
this.onPressed,
});
final Color mainTextColor, secondaryTextColor, backgroundCardColor;
final Widget cardChild;
final Function onPressed;
#override
Widget build(BuildContext context) {
return Container(
color: backgroundCardColor,
padding: EdgeInsets.symmetric(horizontal: 16),
height: 72,
width: double.infinity,
child: cardChild,
);
}
}
This is what i want but in a custom widget so i can use it over and over.
Normal-https://i.imgur.com/lVUkMFK.png
On Pressed-https://i.imgur.com/szuD4ZN.png
You can use extract method instead of extract widget. Flutter will add everything as it is, and instead of a class you will get a reusable function.
I have a GestureDetector that need to launch a url. But if the gesture gets multiple taps, then launch is called multiple times.
In the current code im trying to use a state _isButtonTapped to control the tap. But the .whenComplete is somehow call before the launch is preformed?
_isButtonTapped = false
Widget _buildButton(String key, Text title, String url) {
_onTapped() async {
if (await canLaunch(url)) {
launch(url).whenComplete(
() => setState(() {
_isButtonTapped = false;
}),
);
}
}
return GestureDetector(
onTap: () {
_isButtonTapped ? null : _onTapped();
setState(() {
_isButtonTapped = true;
});
},
child: Container(
child: Padding(
padding: EdgeInsets.all(6.0),
child: Center(child: title),
),
),
);
}
Try this:
class _HomePageState extends State<HomePage> {
bool _isButtonTapped = false;
String _url = "https://google.ca";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
color: Colors.blue,
width: 100,
height: 100,
child: GestureDetector(
onTap: () async {
if (!_isButtonTapped) { // only allow click if it is false
_isButtonTapped = true; // make it true when clicked
if (await canLaunch(_url)) {
await launch(_url);
_isButtonTapped = false; // once url is launched successfully, we again make it false, allowing tapping again
}
}
},
),
),
),
);
}
}
Try this? It should solve your problem.
class SafeOnTap extends StatefulWidget {
SafeOnTap({
Key? key,
required this.child,
required this.onSafeTap,
this.intervalMs = 500,
}) : super(key: key);
final Widget child;
final GestureTapCallback onSafeTap;
final int intervalMs;
#override
_SafeOnTapState createState() => _SafeOnTapState();
}
class _SafeOnTapState extends State<SafeOnTap> {
int lastTimeClicked = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
final now = DateTime.now().millisecondsSinceEpoch;
if (now - lastTimeClicked < widget.intervalMs) {
return;
}
lastTimeClicked = now;
widget.onSafeTap();
},
child: widget.child,
);
}
}
You can wrap any kind of widget if you want.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
children: [
// every click need to wait for 500ms
SafeOnTap(
onSafeTap: () => log('500ms'),
child: Container(
width: double.infinity,
height: 200,
child: Center(child: Text('500ms click me')),
),
),
// every click need to wait for 2000ms
SafeOnTap(
intervalMs: 2000,
onSafeTap: () => log('2000ms'),
child: Container(
width: double.infinity,
height: 200,
child: Center(child: Text('2000ms click me')),
),
),
],
),
),
),
);
}
}
the easiest way is in inkWell widget put doubleTap: () {},
it will do nothing, when user click multiple time
You have a bug in your code.
You are setting _isButtonTapped to true everytime you press it.
Correct you onTap function:
return GestureDetector(
onTap: () {
if (_isButtonTapped == false){
_onTapped();
setState(() {
_isButtonTapped = true;
});
},
}
//...
Regarding why the whenComplete is not beign called when you expected, that's another problem. I never used it but tacking a quick look into the docs (https://api.flutter.dev/flutter/scheduler/TickerFuture/whenComplete.html) show us that are multiple ways of achiving this, including wraping the function in an try block and use thr finally cloused as the whenCompleted. You should take a look at he docs and tried it out. Can't help more with that detail.
Hope it helps you.