Ripple animation flutter - flutter

I want to create ripple animation using flutter. I already know ripple effect but this is not what I want , I want something which is here in the link

Output
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
lowerBound: 0.5,
duration: Duration(seconds: 3),
)..repeat();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Title")),
body: _buildBody(),
);
}
Widget _buildBody() {
return AnimatedBuilder(
animation: CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn),
builder: (context, child) {
return Stack(
alignment: Alignment.center,
children: <Widget>[
_buildContainer(150 * _controller.value),
_buildContainer(200 * _controller.value),
_buildContainer(250 * _controller.value),
_buildContainer(300 * _controller.value),
_buildContainer(350 * _controller.value),
Align(child: Icon(Icons.phone_android, size: 44,)),
],
);
},
);
}
Widget _buildContainer(double radius) {
return Container(
width: radius,
height: radius,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.blue.withOpacity(1 - _controller.value),
),
);
}

Here is another version using CustomPaint
import 'dart:math' as math show sin, pi, sqrt;
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
class Ripples extends StatefulWidget {
const Ripples({
Key key,
this.size = 80.0,
this.color = Colors.pink,
this.onPressed,
#required this.child,
}) : super(key: key);
final double size;
final Color color;
final Widget child;
final VoidCallback onPressed;
#override
_RipplesState createState() => _RipplesState();
}
class _CirclePainter extends CustomPainter {
_CirclePainter(
this._animation, {
#required this.color,
}) : super(repaint: _animation);
final Color color;
final Animation<double> _animation;
void circle(Canvas canvas, Rect rect, double value) {
final double opacity = (1.0 - (value / 4.0)).clamp(0.0, 1.0);
final Color _color = color.withOpacity(opacity);
final double size = rect.width / 2;
final double area = size * size;
final double radius = math.sqrt(area * value / 4);
final Paint paint = Paint()..color = _color;
canvas.drawCircle(rect.center, radius, paint);
}
#override
void paint(Canvas canvas, Size size) {
final Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
for (int wave = 3; wave >= 0; wave--) {
circle(canvas, rect, wave + _animation.value);
}
}
#override
bool shouldRepaint(_CirclePainter oldDelegate) => true;
}
class _RipplesState extends State<Ripples> with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
)..repeat();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
Widget _button() {
return Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(widget.size),
child: DecoratedBox(
decoration: BoxDecoration(
gradient: RadialGradient(
colors: <Color>[
widget.color,
Color.lerp(widget.color, Colors.black, .05)
],
),
),
child: ScaleTransition(
scale: Tween(begin: 0.95, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: const _PulsateCurve(),
),
),
child: widget.child,
),
),
),
);
}
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: _CirclePainter(
_controller,
color: widget.color,
),
child: SizedBox(
width: widget.size * 2.125,
height: widget.size * 2.125,
child: _button(),
),
);
}
}
class _PulsateCurve extends Curve {
const _PulsateCurve();
#override
double transform(double t) {
if (t == 0 || t == 1) {
return 0.01;
}
return math.sin(t * math.pi);
}
}

Related

Animated Curve Line - Flutter

Is it possible to somehow do in flutter what is shown in the GIF?
And so that the starting point always displays a number (in what position this point is at the moment), in the range from 0-100.
EDIT: The animation is now similar to the one in GIF
import 'dart:ui';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 3)
);
late final Size size = const Size(400,400);
late Path path = Path()
..moveTo(3, size.height-4.5)
..quadraticBezierTo(
size.width, size.height,
size.width-4.5, 3,
);
late PathAnimation pathAnimation = PathAnimation(path: path);
#override
void initState() {
super.initState();
_controller.forward();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: Scaffold(
appBar: AppBar(
title: const Text('Material App Bar'),
),
body: Center(
child: ClipRect(
child: AnimatedBuilder(
animation: _controller,
child: Container(
alignment: Alignment.center,
height: size.height,
width: size.width,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.black,
width: 3,
)
),
),
builder: (context, child) {
return CustomPaint(
foregroundPainter: PathPainter(
animation: _controller.value,
pathAnimation: pathAnimation,
),
child: child
);
}
),
),
),
),
);
}
}
class PathAnimation {
final Path path;
Path currentPath = Path();
late final List<PathMetric> pathSegments;
late final double totalLength;
double currentLength = 0;
int currentPathIndex = 0;
PathAnimation({
required this.path
}) {
pathSegments = path.computeMetrics().toList();
totalLength = pathSegments.fold(0, (value, element) => value + element.length);
}
Path getCurrentPath(double animation) {
while (animation > (currentLength + pathSegments[currentPathIndex].length) / totalLength) {
double segmentLength = pathSegments[currentPathIndex].length;
currentPath.addPath(
pathSegments[currentPathIndex].extractPath(0, segmentLength),
Offset.zero
);
currentPathIndex++;
currentLength += segmentLength;
}
if (currentPathIndex >= pathSegments.length) return currentPath;
double missingPartLength = animation - currentLength / totalLength;
double newSegmentLength = pathSegments[currentPathIndex].length;
return
currentPath..addPath(
pathSegments[currentPathIndex].extractPath(0, missingPartLength * newSegmentLength),
Offset.zero
);
}
}
class PathPainter extends CustomPainter{
double animation;
PathAnimation pathAnimation;
PathPainter({
required this.animation,
required this.pathAnimation,
});
#override
void paint(Canvas canvas, Size size) {
canvas.drawPath(
pathAnimation.getCurrentPath(animation),
Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3
..color = Colors.red
);
}
#override
bool shouldRepaint(PathPainter oldDelegate) {
return animation != oldDelegate.animation;
}
}

How to make a button with ripple effect?

Here's what I've got
Positioned(
bottom: 15,
child: InkWell(
onTap: () {},
child: Material(
type: MaterialType.circle,
color: Color(0xFF246DE9),
child: Padding(
padding: const EdgeInsets.all(24),
child: Text(
'GO',
style: TextStyle(
fontSize: 25,
color: Colors.white,
),
),
),
),
),
),
Positioned(
bottom: 30,
child: CustomPaint(
size: Size(50, 50),
painter: CirclePainter(),
),
),
Circle Painter
class CirclePainter extends CustomPainter {
final _paint = Paint()
..color = Colors.white
..strokeWidth = 2
..style = PaintingStyle.stroke;
#override
void paint(Canvas canvas, Size size) {
canvas.drawOval(
Rect.fromLTWH(0, 0, size.width, size.height),
_paint,
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
I created a stack, and tried to stack the white circle and the 'GO' button together, but I've no idea how to create that animation. The size of the white circle need to gradually increase, and it has to become invisible.
Can anyone help?
I have prepared one ripple class. Inspired by https://pub.dev/packages/ripple_animation. Please check it as below. Here, please update minRadius as per your child widget.
import 'dart:async';
import 'package:flutter/material.dart';
/// You can use whatever widget as a [child], when you don't need to provide any
/// [child], just provide an empty Container().
/// [delay] is using a [Timer] for delaying the animation, it's zero by default.
/// You can set [repeat] to true for making a paulsing effect.
class RippleAnimation extends StatefulWidget {
final Widget child;
final Duration delay;
final double minRadius;
final Color color;
final int ripplesCount;
final Duration duration;
final bool repeat;
const RippleAnimation({
required this.child,
required this.color,
Key? key,
this.delay = const Duration(milliseconds: 0),
this.repeat = false,
this.minRadius = 25,
this.ripplesCount = 5,
this.duration = const Duration(milliseconds: 2300),
}) : super(key: key);
#override
_RippleAnimationState createState() => _RippleAnimationState();
}
class _RippleAnimationState extends State<RippleAnimation>
with TickerProviderStateMixin {
late AnimationController _controller;
#override
void initState() {
_controller = AnimationController(
duration: widget.duration,
vsync: this,
lowerBound: 0.7,
upperBound: 1.0
);
// repeating or just forwarding the animation once.
Timer(widget.delay, () {
widget.repeat ? _controller?.repeat() : _controller?.forward();
});
super.initState();
}
#override
Widget build(BuildContext context) {
return CustomPaint(
foregroundPainter: CirclePainter(
_controller,
color: widget.color ?? Colors.black,
minRadius: 25,
wavesCount: widget.ripplesCount,
),
child: widget.child,
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}
// Creating a Circular painter for clipping the rects and creating circle shapes
class CirclePainter extends CustomPainter {
CirclePainter(
this._animation, {
required this.minRadius,
this.wavesCount,
required this.color,
}) : super(repaint: _animation);
final Color color;
final double minRadius;
final wavesCount;
final Animation<double> _animation;
final _paint = Paint()
..color = Colors.white
..strokeWidth = 2
..style = PaintingStyle.stroke;
#override
void paint(Canvas canvas, Size size) {
final Rect rect = Rect.fromLTRB(0.0, 0.0, 100, 100);
for (int wave = 0; wave <= wavesCount; wave++) {
circle(canvas, rect, minRadius, wave, _animation.value, wavesCount);
}
}
// animating the opacity according to min radius and waves count.
void circle(Canvas canvas, Rect rect, double minRadius, int wave,
double value, int length) {
Color _color;
double r;
if (wave != 0) {
double opacity = (1 - ((wave - 1) / length) - value).clamp(0.0, 1.0);
_color = color.withOpacity(opacity);
r = minRadius * (1 + ((wave * value))) * value;
print("value >> r >> $r min radius >> $minRadius value>> $value");
final Paint paint = Paint()..color = _color;
paint..strokeWidth = 2
..style = PaintingStyle.stroke;
canvas.drawCircle(rect.center, r, paint);
}
}
#override
bool shouldRepaint(CirclePainter oldDelegate) => true;
}
Example:
RippleAnimation(
ripplesCount: 1,
repeat: true,
child: Container(
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle
),
width: 100,
height: 100,
child: Center(
child: Text(
'GO',
style: TextStyle(
fontSize: 25,
color: Colors.white,
),
),
),
),
color: Colors.white)
Please let me know if it doesn't work for you.

Allow overflow container more than screen

I have AnimatedContainer inside a Stack widget. I want to change the scale of the MyAnimatedContainer and make it bigger than screen, like image below :
How can I do that ?
Code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
AnimatedContainer(
height: _width,
width: _height,
duration: const Duration(milliseconds: 500),
child: Image.asset('assets/Asset 2.png'),
),
],
),
);
}
I try to change width/height but it doesn't work.
The constraints passed into theStack from its parent are tightened using stackfit.expand,
So I want you to use stackfit.loose and than change the width and height .
Just try if it works for you.
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.loose,
children: [
AnimatedContainer(
height: _width,
width: _height,
duration: const Duration(milliseconds: 500),
child: Image.asset('assets/Asset 2.png'),
),
],
),
);
}
#manishyadav solution didn't work for me. My animation was always constrained by the device size. Nonetheless, I achieved what #mohammadsadra-kafiri depicted in the question:
Allow overflow container more than screen
I used CustomPainter with AnimationController. Container is actually RippleBackground and here's full example:
import 'package:flutter/material.dart';
class RippleBackground extends StatefulWidget {
const RippleBackground({required this.rippleColor, super.key});
final Color rippleColor;
#override
RippleBackgroundState createState() => RippleBackgroundState();
}
class RippleBackgroundState extends State<RippleBackground> with SingleTickerProviderStateMixin {
late AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 400),
vsync: this,
)..forward();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return CustomPaint(
size: const Size(double.infinity, double.infinity),
painter: _RipplePainter(
animation: _controller,
rippleColor: widget.rippleColor,
),
);
}
}
class _RipplePainter extends CustomPainter {
final Animation<double> animation;
_RipplePainter({required this.animation, required this.rippleColor})
: _path = Path(),
_paint = Paint(),
super(repaint: animation);
final Color rippleColor;
final Path _path;
final Paint _paint;
#override
void paint(Canvas canvas, Size size) {
_paint
..color = rippleColor
..style = PaintingStyle.fill;
var centralPoint = Offset(size.width / 2, size.height / 2);
var radiusOfCircumscribedCircle = centralPoint.distance;
var value = animation.value;
if (value >= 0.0 && value <= 1.0) {
canvas.drawPath(
_path
..addOval(
Rect.fromCircle(
center: centralPoint,
radius: radiusOfCircumscribedCircle * value,
),
),
_paint,
);
}
}
#override
bool shouldRepaint(_RipplePainter oldDelegate) => false;
}

How to set wave effect for a button when taps on it in Flutter

Hi, I was trying to build this ui, but i couldnt implement the wave effect as shown in the image.
i got some code for the wave effect but it does not fit well. I made the ui code very complex. so i made a similar ui for sharing .
///////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
class Test extends StatefulWidget {
const Test({Key key}) : super(key: key);
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.mainBg3,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
buttonView(0),
buttonView(1),
buttonView(2),
buttonView(4),
],
),
),
);
}
var selectedIndex = 0;
Widget buttonView(int i) {
return Container(
margin: EdgeInsets.only(bottom: 30),
child: InkWell(
onTap: () {
selectedIndex = i;
setState(() {
});
},
child: selectedIndex == i ? WaveAnimation(child: button()) : button(),
),
);
}
Widget button() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
radius: 12,
backgroundColor: Colors.white,
child: Container(
height: 13,
width: 13,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [Color(0xffD053A3), Color(0xff842990)])),
),
),
],
);
}
}
And heres the code of wave animation
class WaveAnimation extends StatefulWidget {
const WaveAnimation({
this.size = 80.0,
#required this.child,
});
final double size;
final Widget child;
#override
_WaveAnimationState createState() => _WaveAnimationState();
}
class _WaveAnimationState extends State<WaveAnimation>
with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
Color _color = Color(0xffB05CA1);
#override
Widget build(BuildContext context) {
return Center(
child: CustomPaint(
painter: CirclePainter(
_controller,
color: _color.withOpacity(0.1),
),
child: SizedBox(
width: widget.size * 2,
height: widget.size * 2,
child: _button(),
),
),
);
}
Widget _button() {
return Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(widget.size),
child: DecoratedBox(
decoration: BoxDecoration(
gradient: RadialGradient(
colors: <Color>[_color, Color.lerp(_color, Colors.black, 0.05)],
),
),
child: ScaleTransition(
scale: Tween(begin: 0.95, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: CurveWave(),
),
),
),
),
),
);
}
}
class CurveWave extends Curve {
const CurveWave();
#override
double transform(double t) {
if (t == 0 || t == 1) {
return 0.01;
}
return math.sin(t * math.pi);
}
}
class CirclePainter extends CustomPainter {
CirclePainter(
this._animation, {
#required this.color,
}) : super(repaint: _animation);
final Color color;
final Animation<double> _animation;
void circle(Canvas canvas, Rect rect, double value) {
final double opacity = (1.0 - (value / 4.0)).clamp(0.0, 0.2);
final Color _color = color.withOpacity(opacity);
final double size = rect.width / 2;
final double area = size * size;
final double radius = math.sqrt(area * value / 4);
final Paint paint = Paint()..color = _color;
canvas.drawCircle(rect.center, radius, paint);
}
#override
void paint(Canvas canvas, Size size) {
final Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
for (int wave = 3; wave >= 0; wave--) {
circle(canvas, rect, wave + _animation.value);
}
}
#override
bool shouldRepaint(CirclePainter oldDelegate) => true;
}
To fit this effect you will use customBorder inside the inkwell.
customBorder:StadiumBorder()

Flutter manage dx of Offset by swiping to right or left

in Flutter application i want to divide width of screen to 10 part and when user swipe to right or left i could detect each part of this swipe, for example
after divide screen to 10 part i have a variable named screenParts as double which that has 0.0 by default, when user swipe to right the variable value should be plus 1 part and swipe to left should be minus the variable value minus
this variable value should be between 0.0 and 1, you could consider Tween<double>
double screenParts = 0.0;
final double screenWidth = MediaQuery.of(context).size.width / 10;
i want to use this value inside into this part of code:
return GestureDetector(
onPanUpdate: (details) {
if (details.delta.dx > 0) {
if (screenParts / screenWidth == 0) screenParts = screenParts += 0.1;
} else if (details.delta.dx < 0) {
if (screenParts / screenWidth == 0) screenParts = screenParts -= 0.1;
}
setState(() {});
},
child: SafeArea(
child: FadeTransition(
opacity: CurvedAnimation(parent: animation, curve: Curves.fastLinearToSlowEaseIn),
child: SlideTransition(
position: Tween<Offset>(
begin: animateDirection,
end: Offset(screenParts, 0), //<---- this part
).animate(CurvedAnimation(
parent: animation,
curve: Curves.fastLinearToSlowEaseIn,
)),
// ignore: void_checks
child: Material(elevation: 8.0, child: child)),
),
),
);
it means i try to manage dx of Offset by screenParts value with swiping to right or left
is this what you are looking for?
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: Scaffold(
body: SafeArea(
child: MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
double position = 0.0;
#override
void initState() {
super.initState();
}
void handleXChange(double deltaX) {
setState(() {
position = deltaX / MediaQuery.of(context).size.width;
});
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Text(position.toString()),
DragArea(
handleXChange: handleXChange,
),
],
);
}
}
class DragArea extends StatefulWidget {
const DragArea({Key key, #required this.handleXChange}) : super(key: key);
final void Function(double newX) handleXChange;
#override
_DragAreaState createState() => _DragAreaState();
}
class _DragAreaState extends State<DragArea> {
double initX;
void onPanStart(DragStartDetails details) {
initX = details.globalPosition.dx;
}
void onPanUpdate(DragUpdateDetails details) {
var x = details.globalPosition.dx;
var deltaX = x - initX;
widget.handleXChange(deltaX);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onPanStart: onPanStart,
onPanUpdate: onPanUpdate,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
),
),
),
);
}
}