I'm using this character animation in my Flutter app (with the animated background):
https://rive.app/community/1201-5354-lumberjack-walk-cycle/
(if you hit download on that link you will only get the character animation without the background.
to get the character plus background you have to open it in editor and hit download in there)
Now in Flutter I can run the animation forever like this:
RiveAnimation.asset(
'assets/animations/lumberjack.riv',
),
The Character and the background is animated. Now I want to customize the animation speed. For that I looked at this example: https://github.com/rive-app/rive-flutter/blob/master/example/lib/custom_controller.dart
So I put in this class:
class SpeedController extends SimpleAnimation {
final double speedMultiplier;
SpeedController(
String animationName, {
double mix = 1,
this.speedMultiplier = 1,
}) : super(animationName, mix: mix);
#override
void apply(RuntimeArtboard artboard, double elapsedSeconds) {
if (instance == null || !instance!.keepGoing) {
isActive = false;
}
instance!
..animation.apply(instance!.time, coreContext: artboard, mix: mix)
..advance(elapsedSeconds * speedMultiplier);
}
}
and added the controller to my animation:
RiveAnimation.asset(
animations: const ['idle'],
controllers: [SpeedController('curves', speedMultiplier: 5)],
'assets/animations/lumberjack.riv',
),
Not only is the animation speed not changing (character is animated in default speed), but also is the background not animated at all anymore.
Instead of direct download, open in rive
Now make sure to download on V7
The walk animation is on a different artBoard,
You can find it by
late SpeedController walkController = SpeedController(
'walk',
speedMultiplier: .1,
);
SizedBox(
height: 200,
child: RiveAnimation.asset(
animations: ["walk"],
artboard: "Lumberjack",
controllers: [walkController],
fileLoc,
),
),
Also you need to change the animation name according to rive animation LumberingLumberjack
late SpeedController _controller =
SpeedController('LumberingLumberjack', speedMultiplier: .3);
RiveAnimation.asset(
animations: ["LumberingLumberjack"],
controllers: [_controller],
fileLoc,
),
Related
In my application, I am showing a video on the screen.
When application starts, it show full screen of the video, overlay with some information about the video.
This screen is called glass screen.
When the user click a button 3/4 of the screen is adjusted to show the video and bottom part show some other information.
The screen height adjustment is done with "Expanded" flex set to 3/4, by the parent widget and it is working fine.
In both cases the video must fill the height of the screen and user can zoom and pan to see parts outside the screen. I am using "InteractiveViewer" for that.
"WidgetSize" allow me to get the size of the area a Widget occupies.
I have several factors to consider area size video going to display and size of the video
I need to maintain the aspect ratio of the video.
When the glass door is closed, it seems the video is displaying correctly.
But when it is closed (3/4 area) I can't figure out how to find out the correct scale for the video and how to adjust the position of it with "InteractiveViewer".
This is just a test to figure out how to use it
_viewTransformationController.value.setIdentity();
_viewTransformationController.value.setEntry(0, 0, 2);
_viewTransformationController.value.setEntry(1, 1, 2);
_viewTransformationController.value.setEntry(2, 2, 2);
_viewTransformationController.value.setEntry(1, 3, -75);
zoom level 2 is not correct value. -75 is to adjust the Y position (not really sure about this)
Following is the full code.
ScaleVideoController _scaleController;
final _viewTransformationController =
TransformationController(Matrix4.identity());
#override
void initState() {
super.initState();
_scaleController = widget.controller;
this._scaleController.videoSizeChanged = (double width, double height) {
this._videoSize = Size(width, height);
this._updateScale();
};
this._scaleController.glassDoorOpenTriggered = () {
this._glassDoorClosed = false;
this._updateScale();
};
}
_updateScale() {
final physicalScreenSize = window.physicalSize;
if (this._glassDoorClosed) {
this._displayVideoHeight = this._areaSize.height;
this._displayVideoWidth =
this._areaSize.width * this._displayVideoHeight / _areaSize.height;
this._scale = physicalScreenSize.height / this._displayVideoHeight;
} else {
_viewTransformationController.value.setIdentity();
_viewTransformationController.value.setEntry(0, 0, 2);
_viewTransformationController.value.setEntry(1, 1, 2);
_viewTransformationController.value.setEntry(2, 2, 2);
_viewTransformationController.value.setEntry(1, 3, -75);
}
setState(() {});
}
Widget _buildScaledRemoteVideo() {
return WidgetSize(
onChange: (Size s) {
this._areaSize = s;
this._updateScale();
},
child: InteractiveViewer(
transformationController: _viewTransformationController,
scaleEnabled: !_glassDoorClosed,
minScale: 0.1,
constrained: false,
child: Transform.scale(
scale: _glassDoorClosed ? _scale : 1,
child: Container(
width: this._displayVideoWidth,
height: this._displayVideoHeight,
child: widget.remoteVideoRenderer(),
),
),
),
);
}
This is how it should look like,
The read area is the video..
Can anyone provide some advice on how to adjust the screen when it
I have a panel as a tabbar at the bottom with custom animation and with page names. I have 5 pages that can be switched with the next animation: the new page should slowly increase visibility (FadeTransition) and the old page should move right or left depending on what page should be next. If the new page is placed on the right hand of the current page then the slide animation should go left, if it's placed on the left hand then the slide animation should go right.
As far as I know, there is no possibility of using secondaryAnimation to use different animations based on the next page. Because when we create the current page, we don't know what closing animation for the current page should be applied, we can only use one behavior for all transitions.
In this case, maybe anyone has suggestions on how can I implement this behavior?
I've drawn a scheme for the easiest understanding.
I've made this behavior by using the provider.
Before calling Navigator.pushReplacementNamed, when I know what the next page should be, I've defined the necessary direction that the old page should move. Remember it in my model variable. I've taken then this value right in my custom PageRouteBuilder with help of the Provider.
class MainPageRoute extends PageRouteBuilder {
// ignore: unused_field
final Widget _page;
MainPageRoute(this._page)
: super(
pageBuilder: (context, animation, secondaryAnimation) => _page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
//here I've take necessary direction true = right, false = left
final _isAnimationDirectionRight =
context.read<ContextHandlingModel>().isAnimationDirectionRight;
const _begin = Offset.zero;
final _end = (_isAnimationDirectionRight)
? const Offset(0.1, 0.0)
: const Offset(-0.1, 0.0);
const _offsetCurve = Curves.fastOutSlowIn;
const _opacityCurve = Curves.easeIn;
final _offcetTween = Tween(begin: _begin, end: _end).chain(
CurveTween(curve: _offsetCurve),
);
final _opacityOldTween = Tween(begin: 1.0, end: 0.0)
.chain(CurveTween(curve: _opacityCurve));
final _opacityNewTween = Tween(begin: 0.0, end: 1.0)
.chain(CurveTween(curve: _opacityCurve));
return SlideTransition(
position: _offcetTween.animate(secondaryAnimation),
child: FadeTransition(
opacity: _opacityOldTween.animate(secondaryAnimation),
child: FadeTransition(
opacity: _opacityNewTween.animate(animation),
child: child,
),
),
);
},
);
}
I am working on a flutter app and using chewie videoplayer plugin.So when we started playing a video it shows controls at bottom and after few seconds it vanishes and when we move the mouse again it shows again. so is there any method to find when the controls are shown on screen and when its not shown.
actually i am giving close button on the video player. but it doesnt vanishes with those video player controls. it still stay on screen so to hide that close button along with video controls i need to get which process hides the control.
Please help me...
This process is inside the source code of chewie player. So if you need to customize and synchronise your own custom controls and options, either you have to explicitly fetch the source code and edit in that or you need to make your own controllers from scratch using video_player package as the core.
You need to customize the source code of the chewie player.
You can find MaterialControl class inside the source code of this package there is a function called _buildHitArea() just change notifier.hideStuff boolean according to your preference.
Widget _buildHitArea() {
final bool isFinished = _latestValue.position >= _latestValue.duration;
final bool showPlayButton = widget.showPlayButton && !_dragging && !notifier.hideStuff;
return GestureDetector(
onTap: () {
if (_latestValue.isPlaying) {
if (_displayTapped) {
setState(() {
notifier.hideStuff = false;
});
} else {
_cancelAndRestartTimer();
}
} else {
_playPause();
setState(() {
notifier.hideStuff = false;
});
}
},
child: CenterPlayButton(
backgroundColor: Colors.black54,
iconColor: Colors.white,
isFinished: isFinished,
isPlaying: controller.value.isPlaying,
show: showPlayButton,
onPressed: _playPause,
),
);
}
I am currently using a SpriteAnimationWidget from Flame, and I want to have different animations by clicking buttons.
To test out if this works, I am changing the animation parameter of the SpriteAnimationWidget with a button click. After 1 second, the widget has to return to its original animation.
Here is my code:
Future<void> playReaction() async {
setState(() {
isPlaying = true;
state = 'reaction';
});
await Future.delayed(Duration(milliseconds: 1000));
setState(() {
isPlaying = false;
state = 'idle';
});
}
And inside the build, I am returning:
if(state == 'reaction') {
spriteAnimation = reactionSprite!.createAnimation(row: 0, stepTime: 0.1, from: 0, to: 10);
print('reaction');
}
else if(state == 'idle'){
spriteAnimation = sprite!.createAnimation(row: 1, stepTime: 0.2, from: 0, to: 4);
print('idle');
}
return Container(
height: widget.size,
child: Stack(
fit: StackFit.expand,
children: [
Positioned.fill(
child: SpriteAnimationWidget(
animation: spriteAnimation!,
anchor: Anchor.center,
)
),
]
),
);
The variables sprite and reactionSprite are sprite sheets that I preload using SpriteSheet.fromColumnsAndRows().
The problem is that, although my app successfully runs playReaction() and reaches print('reaction'), the SpriteAnimationWidget is still playing the idle animation instead of the reaction animation. I know that it's being rebuilt somehow because I can see that there is a slight unnatural discontinuity in the animation when I hit the button (plus the print messages).
Why is setState() not updating the SpriteAnimationWidget with a new animation parameter?
I am relatively new to Flutter and Flame, so I would love some help or advice.
ps. I tried using the same sprite sheet for the change with a different row (instead of using a completely different sprite sheet as in the code above), but the result was the same.
This was actually a bug, we are fixing it here.
Meanwhile, as a workaround, you can change the key of the widget every time that you change the animation and it should update the widget.
I'm new to Flutter and have some performance concerns. For my app, I have created a custom sidebar menu, inspired by
For this purpose, I created a stateful top-level widget that acts as the parent screen. It contains a Stack widget with the navigation screen on the button, and a content screen on top. The user should be able to open/close the menu in two ways:
By pressing the hamburger menu icon at the top left of the content screen (either when it is fully opened, or moved to the side as in the first pic)
By swiping right when the menu is open, and left when the menu is closed.
To satisfy point 2, I added a GestureDetector on the parent screen, such that the swipes are detected in the entire screen, which animates the content screen to the side/back in full view. To satisfy point 1, I pass an onPress callBack to the content screen (which passes it to the hamburger iconButton), which also does the top level animation. However, reading the documentation (stateful performance considerations), it seems that such a top-level stateful widget can be harmful for performance, as the rebuild passes down. I can't make my content screen a const widget (which is a proposed solution) because of the callback. This is obviously suboptimal, since in the content screen, only the icon has an animated change when the menu opens (the icon changes from a hamburger to an arrow).
How can I minimize the number of rerenders in the subtree? Is there a way to pass the screen as a const widget, even though it has a callback? Or is the current approach satisfactory?
The code, as I have it currently, is as follows:
class ParentScreen extends StatefulWidget {
const ParentScreen({Key? key}) : super(key: key);
#override
_ParentScreenState createState() => _ParentScreenState();
}
class _ParentScreenState extends State<ParentScreen> {
bool isMenuOpen = false;
double xOffset = 0;
double yOffset = 0;
double rotationAngle = 0;
double scaleFactor = 1;
double toRadians(double degrees) => degrees * math.pi / 180.0;
void animateMenu() {
setState(() {
...
isMenuOpen = !isMenuOpen;
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
// Detect user swipe to navigate between the screens
child: GestureDetector(
onHorizontalDragEnd: (DragEndDetails details) {
if (details.primaryVelocity != null) {
if (details.primaryVelocity! > 0) {
// Right swipe, close menu if open
if (isMenuOpen) animateMenu();
} else if (details.primaryVelocity! < 0) {
// Left swipe, open menu if closed
if (!isMenuOpen) animateMenu();
}
}
},
child: Scaffold(
body: Stack(
children: <Widget>[
const DrawerScreen(), // Screen with navigation information
AnimatedContainer(
transform: Matrix4.translationValues(xOffset, yOffset, 0)
..scale(scaleFactor)
..rotateZ(toRadians(rotationAngle)),
duration: const Duration(milliseconds: 300),
child: HomeScreen( // Screen with custom content
onMenuPress: animateMenu,
),
),
],
),
),
),
);
}
}
Well, you don't need to make the parent widget a stateful widget.
First make the actual menu including it's animation it's on widget which draws over everything else.. (Similar to what you said in a Stack widget).
Then create a object (typically called a BLoC in flutter-world) which lives outside the widget tree - either a ChangeNotifier or a Stream and inject it into the stateless widgets (easiest is by using the provider package, but you can also use an InheritedWidget.
When you want to show the menu you would just change the state of this external object which will notify the menu widget to expand.