Bubble effect animation on tap:
How can I make this bubble effect animation using the Flutter app?
You can create the bubble effect similar to this using pimp_my_button package. This package is not on pub.dev so you need to install it via github.
You can install it as following
pimp_my_button:
git:
url: git://github.com/Norbert515/pimp_my_button.git
After that you need to implement the button as following
PimpedButton(
particle: DemoParticle(),
pimpedWidgetBuilder: (context, controller) {
return FloatingActionButton(onPressed: () {
controller.forward(from: 0.0);
},);
},
),
You can test it and you can see you have this fireworks effect on you button.
Now to create a custom animation you have to create your own particle animation.
I am attaching a simple bubble animation below you can tweak it to your need.
class MyParticle extends Particle {
#override
void paint(Canvas canvas, Size size, progress, seed) {
int randomMirrorOffset = 6;
CompositeParticle(children: [
// Firework(),
CircleMirror(
numberOfParticles: 16,
child: AnimatedPositionedParticle(
begin: Offset(0.0, 20.0),
end: Offset(0.0, 60.0),
child: FadingCircle(radius: 3.0, color: Colors.pink),
),
initialRotation: -pi / randomMirrorOffset),
CircleMirror.builder(
numberOfParticles: 16,
particleBuilder: (index) {
return IntervalParticle(
child: AnimatedPositionedParticle(
begin: Offset(0.0, 30.0),
end: Offset(0.0, 50.0),
child: FadingCircle(radius: 3.0, color: Colors.pink),
),
interval: Interval(
0.5,
1,
));
},
initialRotation: -pi / randomMirrorOffset ),
]).paint(canvas, size, progress, seed);
}
}
Now replace the DemoParticle() with MyParticle() & you will have a bubble ripple effect.
To keep repeating the bubble animation, do following.
Change
controller.forward(from: 0.0);
to
controller.repeat(period: Duration(seconds: 1));
Here you can change or skip the animation duration using period property.
Related
I am currently working on a layout that displays a Positioned widget on the entire screen.
It's positioning itself close to the detected barcode, Look at the image below for an example.
But when the barcode moves to close the the left edge of the screen, the UI elements are drawn partially offscreen. Is there a way I can fix this without having to calculate when I am going out of bounds each frame?
Here is the code that I use to set this up:
Widget _buildImage() {
return Container(
constraints: const BoxConstraints.expand(),
child: _controller == null
? const Center(
child: Text(
'Initializing Camera...',
style: TextStyle(
color: Colors.green,
fontSize: 30.0,
),
),
)
: Stack(
fit: StackFit.expand,
children: <Widget>[
CameraPreview(_controller!),
_buildResults(),
if (_scanResults.isNotEmpty)
_buildUIElements()
],
),
);
}
Widget _buildUIElements() {
Barcode barcode = _scanResults[0];
final Size imageSize = Size(
_controller!.value.previewSize!.height,
_controller!.value.previewSize!.width,
);
var boundingBox = barcode.boundingBox!;
var rect = scaleRect(rect: boundingBox, imageSize: imageSize, widgetSize: MediaQuery.of(context).size);
return AnimatedPositioned(
top: rect.bottom,
left: rect.left,
child: Card(
child: Text('This is an amaizing product'),
),
duration: const Duration(milliseconds: 500),
);
}
Maybe there is a better way to achieve this?
Don't mind the excessive use of ! still learning the whole null-safety thing :)
EDIT 1:
As suggested by pskink I have looked at how the tooltips in flutter work and made use of the SingleChildLayoutDelegate in combination with a CustomSingleChildLayout and this works perfectly for tracking the position but now there is no option to animate this.
My delegate class is as follows:
class CustomSingleChildDelegate extends SingleChildLayoutDelegate {
CustomSingleChildDelegate ({
required this.target,
required this.verticalOffset,
required this.preferBelow,
});
final Offset target;
final double verticalOffset;
final bool preferBelow;
#override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints.loosen();
#override
Offset getPositionForChild(Size size, Size childSize) {
return positionDependentBox(
size: size,
childSize: childSize,
target: target,
verticalOffset: verticalOffset,
preferBelow: preferBelow,
);
}
#override
bool shouldRelayout(CustomSingleChildDelegate oldDelegate) {
return target != oldDelegate.target
|| verticalOffset != oldDelegate.verticalOffset
|| preferBelow != oldDelegate.preferBelow;
}
}
And then updated my builder function with:
return CustomSingleChildLayout(
delegate: CustomSingleChildDelegate (target: rect.bottomCenter, verticalOffset: 20, preferBelow: true),
child: Card(
child: Text('This is an amaizing product'),
),
)
Having the AnimatedPositioned as child of the layout causes an exception.
i created my staggered animation following the official tutorial : https://flutter.dev/docs/development/ui/animations/staggered-animations.
The issue is that the number of ticks the controller goes thought (and thus the frame rate) is low, about 50 fps (it can be even lower in some other exemples i tried).
Is there a way to increase the number of ticks an (animation)Controller goes through, or is there a better solution for this ?
Thanks !
Here's a gif of the laggy widget: https://i.stack.imgur.com/aXdef.gif
==> (StackOverflow does not want me to put images yet, so you got a link instead)
I printed each frame/tick, here's what i got:
flutter: 100.0 -> frame 0
flutter: 100.0 -> frame 1
flutter: 100.32715344429016 -> frame 2
flutter: 100.72060895198956 -> frame 3
...
flutter: 199.6830144248088 -> frame 40
flutter: 200.0 -> frame 41
flutter: 200.0 -> frame 42
flutter: 200.0 -> frame 43
flutter: 200.0 -> frame 44
flutter: 200.0 -> frame 45
flutter: 200.0 -> frame 46
Here is my full code:
class VerificationBox extends StatefulWidget {
#override
_VerificationBoxState createState() => _VerificationBoxState();
}
class _VerificationBoxState extends State<VerificationBox>
with TickerProviderStateMixin {
bool _isOpened = false;
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this);
}
Future<void> _playAnimation() async {
try {
_isOpened
? await _controller.reverse().orCancel
: await _controller.forward().orCancel;
} on TickerCanceled {
// the animation got canceled, probably because it was disposed of
}
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
_playAnimation();
_isOpened = !_isOpened;
},
child: StaggerAnimation(controller: _controller.view),
);
}
}
class StaggerAnimation extends StatelessWidget {
StaggerAnimation({Key key, this.controller}):
opacity = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.9,
1,
curve: Curves.linear,
),
),
),
width = Tween<double>(
begin: 100.0,
end: 200.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.0,
0.9,
curve: Curves.easeInOutCirc,
),
),
),
super(key: key);
final AnimationController controller;
final Animation<double> opacity;
final Animation<double> width;
int counter = 0;
Widget _buildAnimation(BuildContext context, Widget child) {
print('${width.value} -> frame $counter');
counter++;
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(35),
color: Colors.green,
),
// curve: Curves.easeInOut,
height: 25,
width: width.value,
// duration: Duration(milliseconds: 500),
child: Row(
children: [
Container(
alignment: Alignment.center,
width: 80,
height: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(35),
color: Colors.red[300],
),
child: Text(
'verification',
style: TextStyle(fontSize: 13),
textAlign: TextAlign.center,
),
),
Expanded(
child: Opacity(
opacity: opacity.value,
child: Text(
'BCDEFG',
textAlign: TextAlign.center,
),
),
)
],
));
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
builder: _buildAnimation,
animation: controller,
);
}
}
TickerProvider provides Ticker which simply means it tells our app that the device has updated the (or screen is updated), so that our AnimationController can generate a new value and we can redraw our widget according to that.
Generally speaking you can't really decide how many times the TickerProvider provides the Ticker. It entirely depends on the device and how many times it can refresh it's screen.
As Flutter Docs States :
Flutter aims to provide 60 frames per second (fps) performance, or 120
fps performance on devices capable of 120Hz updates. For 60fps, frames
need to render approximately every 16ms. Jank occurs when the UI
doesn't render smoothly.
In your case you're running your app on iOS Simulator and possibly in debug mode. Please don't check your app's performance either in debug mode or on a simulator. Use a real device and run you app in release or profile mode to check the real performance of a flutter app.
Launch the app in profile mode as follows:
In Android Studio and IntelliJ, use the Run > Flutter Run main.dart in
Profile Mode menu item. In VS Code, open your launch.json file, and
set the flutterMode property to profile (when done profiling, change
it back to release or debug):
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
}
]
From the command line, use the --profile flag:
$ flutter run --profile
Please visit official flutter docs for more info on performance :
Example image with dot indicator
I have multiple images each with their own redirect link. Currently this works fine at displaying using a list view build to display the images inside a gesture detector.
However, I’d like to add a dot indicator to show which image is being viewed. How can I get the index of the image being displayed? Or increase / decrease a counter when swiping left or right.
You should post the code of what you have done so people can see what your exact problem is.
If you're using ListView.builder, you can get index from itemBuilder. Then create a variable to hold the value of that index when you interact with the list
int currentIndex;
itemCount: list.length,
itemBuilder: (context, index) {
currentIndex = index;
}
Then below the listView, add a custom dot indicator list.
Row(
children: [
for(int i = 0; i < list.length; i++)
Container(
height: 10, width: 10,
decoration: BoxDecoration(
color: i == currentIndex ? Colors.white : Colors.grey,
borderRadius: BorderRadius.circular(5)
)
)
]
)
You can get the index from itemBuilder. and change index by pass index in activeIndex parameter of AnimatedSmoothIndicator.
for smooth indicator smooth_page_indicator
AnimatedSmoothIndicator(
activeIndex: index,
count: images.length,
effect: ExpandingDotsEffect(
radius: 10,
dotWidth: 10,
dotHeight: 10,
activeDotColor: Colors.green,
expansionFactor: 4,
dotColor: Colors.green.withOpacity(0.17),
), // your preferred effect
onDotClicked: (index) {
pageViewController.animateToPage(
index,
duration: const Duration(milliseconds: 500),
curve: Curves.ease,
);
},
)
For handling pageview by clicking on dot
pageViewController.animateToPage(
index,
duration: const Duration(milliseconds: 500),
curve: Curves.ease,
);
I need to create a text label component that will act as an in-app tutorial explanatory label in Flutter. This text label should show up above a button explaining the clicking on that button would result in creating a new entry.
I tried using a Tooltip widget to wrap the button. But tooltip shows up only when long pressed the button. I need this widget to be visible if the user doesn't already have any entries created.
Currently, I thought of giving you this short but small help about this. I just want you to see whether it is helping you in any way or not.
My idea is to trigger the tooltip programatically. Let me know tell you how to do that, although there is no available documentation to do that. I did some research too on this.
Use ensureTooltipVisible from _TooltipState using a GlobalKey to fetch it.
Typically you'd do the following field inside the widget instantiating Tooltip : final key = new GlobalKey();
Then in your tooltip you will assign this key
final _key = new GlobalKey()
ToolTip(
key: _key
)
And in the initState() you just check whether there are some entries or not:
initState(){
super.initState();
if(_yourData == null){
//that's how you trigger it
final dynamic tooltip = _key.currentState;
tooltip.ensureTooltipVisible();
}
}
initState() is the method which gets initialized when you open up your page every time. So as soon as you will open up your page, the info will be checked and hence it will show accordingly. Let me know if that helps. :)
Can make using OverlayEntry.
Here is sample code
final overlayEntry = OverlayEntry(
builder: (BuildContext context) => Positioned(
top: _yourCustomTop,
left: _yourCustomLeft,
child: FadeTransition(
opacity: CurvedAnimation(
parent: _yourController,
curve: Curves.fastOutSlowIn,
),
child: Material(
color: const Color(0x00ffffff),
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: TooltipShapeBorder(radius: 6, arrowArc: 0.2),
shadows: [
const BoxShadow(
color: Colors.black26,
blurRadius: 4.0,
offset: const Offset(2, 2),
),
],
),
child: SizedBox(
width: 84,
height: 44,
child: Center(
child: Text('_yourString'),
),
),
),
);
),
),
);
Overlay.of(context).insert(_overlayEntry);
TooltipShapeBorder
from here.
import 'package:flutter/material.dart';
class TooltipShapeBorder extends ShapeBorder {
final double arrowWidth;
final double arrowHeight;
final double arrowArc;
final double radius;
TooltipShapeBorder({
this.radius = 16.0,
this.arrowWidth = 20.0,
this.arrowHeight = 10.0,
this.arrowArc = 0.0,
}) : assert(arrowArc <= 1.0 && arrowArc >= 0.0);
#override
EdgeInsetsGeometry get dimensions => EdgeInsets.only(bottom: arrowHeight);
#override
Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;
#override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
rect = Rect.fromPoints(rect.topLeft, rect.bottomRight - Offset(0, arrowHeight));
double x = arrowWidth, y = arrowHeight, r = 1 - arrowArc;
return Path()
..addRRect(RRect.fromRectAndRadius(rect, Radius.circular(radius)))
..moveTo(rect.bottomCenter.dx + x / 2, rect.bottomCenter.dy)
..relativeLineTo(-x / 2 * r, y * r)
..relativeQuadraticBezierTo(-x / 2 * (1 - r), y * (1 - r), -x * (1 - r), 0)
..relativeLineTo(-x / 2 * r, -y * r);
}
#override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
#override
ShapeBorder scale(double t) => this;
}
You can already show a label quite easily like this:
const Tooltip(
message: 'Hi there!',
triggerMode: TooltipTriggerMode.tap, // ensures the label appears when tapped
preferBelow: false, // use this if you want the label above the widget
child: Icon(Icons.interests),
)
You can use triggerMode: TooltipTriggerMode.manual for further customization ;)
Or you could use the showcaseview package
You can find more info for the Tooltip widget here
I have an animation where two containers collide with each other. I want the containers to reverse back with animation to their respective starting offset. The containers move along varied paths and duration.
As seen in the animation gif attached the green and red containers collide and jump back to their starting offsets rather than sliding back.
Here is the code I used to make the GIF, in my actual code I'm using rectangle intersection to check when the containers collide.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
List<AnimationController> _controller = List(2);
List<Animation<Offset>> _animation = List(2);
List<Tween<Offset>> tween = List(2);
#override
void initState() {
super.initState();
tween[0] = Tween(begin: Offset(0, 10), end: Offset(200, 10));
tween[1] = Tween(begin: Offset(200, 10), end: Offset(0, 10));
for (int i = 0; i < 2; i++) {
_controller[i] =
AnimationController(vsync: this, duration: Duration(seconds: 1));
_animation[i] = tween[i].animate(_controller[i])
..addListener(
() {
setState(
() {
print(
'${_animation[0].value.dx.toInt()}:${_animation[0].value.dy.toInt()} ${_animation[1].value.dx.toInt()}:${_animation[1].value.dy.toInt()}');
if (((_animation[0].value.dx) / 2).round() ==
((_animation[1].value.dx) / 2).round()) {
_controller[0].stop();
_controller[1].stop();
_controller[0].reset();
_controller[1].reset();
Future.delayed(Duration(milliseconds: 100)).then((_) {
tween[0] = Tween(
begin: Offset(
_animation[0].value.dx, _animation[0].value.dy),
end: Offset(0, 10));
tween[1] = Tween(
begin: Offset(
_animation[1].value.dx, _animation[1].value.dy),
end: Offset(200, 10));
_animation[0] = tween[0].animate(_controller[0]);
_animation[1] = tween[1].animate(_controller[1]);
_controller[0].forward();
_controller[1].forward();
});
}
},
);
},
);
}
WidgetsBinding.instance.addPostFrameCallback(
(_) => _afterLayout(),
);
}
void _afterLayout() {
for (int i = 0; i < 2; i++) {
_controller[i].forward();
}
}
#override
void dispose() {
super.dispose();
for (int i = 0; i < 1; i++) {
_controller[i].dispose();
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("collision"),
),
body: Column(
children: <Widget>[
Expanded(
child: Stack(
children: <Widget>[
Container(
width: double.infinity,
height: double.infinity,
),
Positioned(
left: _animation[0] != null ? _animation[0].value.dx : 0,
top: _animation[0] != null ? _animation[0].value.dy : 0,
child: Container(
width: 50,
height: 50,
color: Colors.red,
),
),
Positioned(
left: _animation[1] != null ? _animation[1].value.dx : 0,
top: _animation[1] != null ? _animation[1].value.dy : 0,
child: Container(
width: 50,
height: 50,
color: Colors.green,
),
),
],
),
),
RaisedButton(
onPressed: () {
_controller[0].reset();
_controller[1].reset();
tween[0] = Tween(begin: Offset(0, 10), end: Offset(200, 10));
tween[1] = Tween(begin: Offset(200, 10), end: Offset(0, 10));
_controller[0].duration = Duration(seconds: 1);
_controller[1].duration = Duration(seconds: 1);
_animation[0] = tween[0].animate(_controller[0]);
_animation[1] = tween[1].animate(_controller[1]);
_controller[0].forward();
_controller[1].forward();
},
child: Text("Animate"),
)
],
),
),
);
}
}
I want the containers to smoothly slide back to their respective starting offsets with animation.
I will add a bit of explanation here to help others maybe.
Animations in flutter are controlled by an AnimationController. You can control one ore more animations using a single AnimationController ( you can use same controller if you want to have animations triggered simultaneously).
Now, to start an animation (or all animations linked to the same controller) you can use forward() method. The forward() method accepts a parameter that specify from which point on should the animation continue, the parameter value is between 0...1.
If you want to do the animation in backwards, the controller has a method called reverse() which will perform the animation in the reverse order. Same, the method accepts a parameter with value from 0 to 1, if no parameter is specified the animation will "reverse" from the last state.
Note in order to have a reverse animation you should not call reset() on the controller. reset() method will set all the values of the controller to the initial ones.