admin管理员组文章数量:1302317
I am currently struggling to create a ripple effect behind a given widget. I am only able to create circular ripples.
The requirements are as such
- The ripple should appear as if coming from behind the center of the widget, in my case the rounded square button
- The ripple should take the shape of the child widget, in my case the rounded square button.
I am trying to make the ripple_effect reusable such that any shape can become the child widget and the ripples will take the form and shape and render accordingly.
Any help will be highly appreciated.
ripple_effect.dart
import 'dart:async';
import 'package:flutter/material.dart';
class RippleAnimation extends StatefulWidget {
const RippleAnimation({
required this.child,
this.color = Colors.black,
this.delay = Duration.zero,
this.repeat = false,
this.minRadius = 10,
this.maxRadius = 30,
this.ripplesCount = 3,
this.duration = const Duration(milliseconds: 2400),
super.key,
});
final Widget child;
final Duration delay;
final double minRadius;
final double maxRadius;
final Color color;
final int ripplesCount;
final Duration duration;
final bool repeat;
@override
RippleAnimationState createState() => RippleAnimationState();
}
class RippleAnimationState extends State<RippleAnimation>
with TickerProviderStateMixin<RippleAnimation> {
late AnimationController _controller;
late GlobalKey _childKey;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
_childKey = GlobalKey();
Timer(widget.delay, () {
if (mounted) {
widget.repeat ? _controller.repeat() : _controller.forward();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return Stack(
alignment: Alignment.center,
children: [
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
size: Size(constraints.maxWidth, constraints.maxHeight),
painter: RipplePainter(
progress: _controller.value,
color: widget.color,
minRadius: widget.minRadius,
maxRadius: widget.maxRadius,
ripplesCount: widget.ripplesCount,
childKey: _childKey,
),
);
},
),
Container(key: _childKey, child: widget.child),
],
);
},
);
}
}
class RipplePainter extends CustomPainter {
final double progress;
final Color color;
final double minRadius;
final double maxRadius;
final int ripplesCount;
final GlobalKey childKey;
RipplePainter({
required this.progress,
required this.color,
required this.minRadius,
required this.maxRadius,
required this.ripplesCount,
required this.childKey,
});
@override
void paint(Canvas canvas, Size size) {
final RenderBox? childBox =
childKey.currentContext?.findRenderObject() as RenderBox?;
if (childBox == null) return;
final Offset childPosition =
childBox.localToGlobal(Offset.zero) - childBox.size.center(Offset.zero);
final Path path = Path();
path.addRect(Rect.fromLTWH(childPosition.dx, childPosition.dy,
childBox.size.width, childBox.size.height));
for (int i = 0; i < ripplesCount; i++) {
final rippleProgress = (progress - (i / ripplesCount)).clamp(0.0, 1.0);
final radius = minRadius + (maxRadius - minRadius) * rippleProgress;
final opacity = 1.0 - rippleProgress;
final paint = Paint()
..color = color.withValues(alpha: opacity)
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
canvas.save();
canvas.clipPath(path);
canvas.drawCircle(childBox.size.center(Offset.zero), radius, paint);
canvas.restore();
}
}
@override
bool shouldRepaint(RipplePainter oldDelegate) {
return oldDelegate.progress != progress;
}
}
Usage within testpage.dart
import 'package:flutter/material.dart';
import 'package:freshcart/components/colors.dart';
import 'package:freshcart/components/ripple_effect.dart';
import 'package:freshcart/components/static_decoration.dart';
import 'package:freshcart/pages/home/widgets/drawer_widget.dart';
import 'package:freshcart/pages/home/widgets/sqbuttons_widgets.dart';
class PlaygroundPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
child: Stack(
children: [_buildBackButton(), _buildPlaygroundContent()],
),
),
);
}
Widget _buildBackButton() {
return AppBarButton(
onTap: () async {
await drawerController.open!();
},
svgPath: "assets/images/svg/menu.svg",
);
}
Widget _buildPlaygroundContent() {
return Align(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Playground content"),
height20,
RippleAnimation(
child: AppBarButton(
svgPath: "assets/images/svg/basket.svg",
onTap: () {},
),
color: primaryAppColor,
delay: const Duration(milliseconds: 500),
repeat: true,
minRadius: 10,
maxRadius: 30,
ripplesCount: 3,
duration: const Duration(milliseconds: 6 * 400),
),
height20,
],
),
);
}
}
My output so far
I have tried different varieties of animations / painters in flutter, but to no avail. LLMs are not helpful as well.
I am currently struggling to create a ripple effect behind a given widget. I am only able to create circular ripples.
The requirements are as such
- The ripple should appear as if coming from behind the center of the widget, in my case the rounded square button
- The ripple should take the shape of the child widget, in my case the rounded square button.
I am trying to make the ripple_effect reusable such that any shape can become the child widget and the ripples will take the form and shape and render accordingly.
Any help will be highly appreciated.
ripple_effect.dart
import 'dart:async';
import 'package:flutter/material.dart';
class RippleAnimation extends StatefulWidget {
const RippleAnimation({
required this.child,
this.color = Colors.black,
this.delay = Duration.zero,
this.repeat = false,
this.minRadius = 10,
this.maxRadius = 30,
this.ripplesCount = 3,
this.duration = const Duration(milliseconds: 2400),
super.key,
});
final Widget child;
final Duration delay;
final double minRadius;
final double maxRadius;
final Color color;
final int ripplesCount;
final Duration duration;
final bool repeat;
@override
RippleAnimationState createState() => RippleAnimationState();
}
class RippleAnimationState extends State<RippleAnimation>
with TickerProviderStateMixin<RippleAnimation> {
late AnimationController _controller;
late GlobalKey _childKey;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
_childKey = GlobalKey();
Timer(widget.delay, () {
if (mounted) {
widget.repeat ? _controller.repeat() : _controller.forward();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return Stack(
alignment: Alignment.center,
children: [
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
size: Size(constraints.maxWidth, constraints.maxHeight),
painter: RipplePainter(
progress: _controller.value,
color: widget.color,
minRadius: widget.minRadius,
maxRadius: widget.maxRadius,
ripplesCount: widget.ripplesCount,
childKey: _childKey,
),
);
},
),
Container(key: _childKey, child: widget.child),
],
);
},
);
}
}
class RipplePainter extends CustomPainter {
final double progress;
final Color color;
final double minRadius;
final double maxRadius;
final int ripplesCount;
final GlobalKey childKey;
RipplePainter({
required this.progress,
required this.color,
required this.minRadius,
required this.maxRadius,
required this.ripplesCount,
required this.childKey,
});
@override
void paint(Canvas canvas, Size size) {
final RenderBox? childBox =
childKey.currentContext?.findRenderObject() as RenderBox?;
if (childBox == null) return;
final Offset childPosition =
childBox.localToGlobal(Offset.zero) - childBox.size.center(Offset.zero);
final Path path = Path();
path.addRect(Rect.fromLTWH(childPosition.dx, childPosition.dy,
childBox.size.width, childBox.size.height));
for (int i = 0; i < ripplesCount; i++) {
final rippleProgress = (progress - (i / ripplesCount)).clamp(0.0, 1.0);
final radius = minRadius + (maxRadius - minRadius) * rippleProgress;
final opacity = 1.0 - rippleProgress;
final paint = Paint()
..color = color.withValues(alpha: opacity)
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
canvas.save();
canvas.clipPath(path);
canvas.drawCircle(childBox.size.center(Offset.zero), radius, paint);
canvas.restore();
}
}
@override
bool shouldRepaint(RipplePainter oldDelegate) {
return oldDelegate.progress != progress;
}
}
Usage within testpage.dart
import 'package:flutter/material.dart';
import 'package:freshcart/components/colors.dart';
import 'package:freshcart/components/ripple_effect.dart';
import 'package:freshcart/components/static_decoration.dart';
import 'package:freshcart/pages/home/widgets/drawer_widget.dart';
import 'package:freshcart/pages/home/widgets/sqbuttons_widgets.dart';
class PlaygroundPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
child: Stack(
children: [_buildBackButton(), _buildPlaygroundContent()],
),
),
);
}
Widget _buildBackButton() {
return AppBarButton(
onTap: () async {
await drawerController.open!();
},
svgPath: "assets/images/svg/menu.svg",
);
}
Widget _buildPlaygroundContent() {
return Align(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Playground content"),
height20,
RippleAnimation(
child: AppBarButton(
svgPath: "assets/images/svg/basket.svg",
onTap: () {},
),
color: primaryAppColor,
delay: const Duration(milliseconds: 500),
repeat: true,
minRadius: 10,
maxRadius: 30,
ripplesCount: 3,
duration: const Duration(milliseconds: 6 * 400),
),
height20,
],
),
);
}
}
My output so far
I have tried different varieties of animations / painters in flutter, but to no avail. LLMs are not helpful as well.
Share asked Feb 11 at 6:32 Freshcart EngineeringFreshcart Engineering 111 silver badge1 bronze badge1 Answer
Reset to default 1solved your error check it.
in simple_ripple_animation.dart
import 'dart:async';
import 'package:flutter/material.dart';
class RippleAnimation extends StatefulWidget {
const RippleAnimation({
required this.child,
this.color = Colors.black,
this.delay = Duration.zero,
this.repeat = false,
this.minRadius = 60,
this.maxRadius = 120,
this.ripplesCount = 5,
this.borderRadius = 25.0, // Added border radius
this.duration = const Duration(milliseconds: 2300),
super.key,
});
final Widget child;
final Duration delay;
final double minRadius;
final double maxRadius;
final Color color;
final int ripplesCount;
final Duration duration;
final bool repeat;
final double borderRadius; // Border radius for rounded square
@override
RippleAnimationState createState() => RippleAnimationState();
}
class RippleAnimationState extends State<RippleAnimation>
with TickerProviderStateMixin<RippleAnimation> {
AnimationController? _controller;
@override
void initState() {
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
Timer? animationTimer;
animationTimer = Timer(widget.delay, () {
if (_controller != null && mounted) {
widget.repeat ? _controller!.repeat() : _controller!.forward();
}
animationTimer?.cancel();
});
super.initState();
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: SquareRipplePainter(
_controller,
color: widget.color,
minRadius: widget.minRadius,
maxRadius: widget.maxRadius,
wavesCount: widget.ripplesCount + 2,
borderRadius: widget.borderRadius, // Pass border radius
),
child: widget.child,
);
}
}
/// Creating a Rounded Square Painter
class SquareRipplePainter extends CustomPainter {
SquareRipplePainter(
this.animation, {
required this.wavesCount,
required this.color,
this.minRadius,
this.maxRadius,
this.borderRadius = 20.0, // Default rounded corners
}) : super(repaint: animation);
final Color color;
final double? minRadius;
final double? maxRadius;
final int wavesCount;
final double borderRadius; // Border radius for square ripples
final Animation<double>? animation;
@override
void paint(Canvas canvas, Size size) {
final Rect rect = Rect.fromLTRB(0, 0, size.width, size.height);
for (int wave = 0; wave <= wavesCount; wave++) {
drawSquareRipple(
canvas,
rect,
minRadius,
maxRadius,
wave,
animation!.value,
wavesCount,
color,
);
}
}
/// Creating animated rounded square ripples
void drawSquareRipple(
Canvas canvas,
Rect rect,
double? minRadius,
double? maxRadius,
int wave,
double value,
int? length,
Color rippleColor,
) {
if (wave != 0) {
final double opacity =
(1 - ((wave - 1) / length!) - value).clamp(0.0, 1.0);
final Color fadedColor = rippleColor.withOpacity(opacity);
final double sizeFactor = minRadius! + ((maxRadius! - minRadius) * value);
final double rippleSize = sizeFactor * (1 + (wave * value)) * value;
final Paint paint = Paint()..color = fadedColor;
final RRect roundedSquare = RRect.fromRectAndRadius(
Rect.fromCenter(
center: rect.center,
width: rippleSize,
height: rippleSize,
),
Radius.circular(borderRadius), // Rounded corners
);
canvas.drawRRect(roundedSquare, paint);
}
}
@override
bool shouldRepaint(SquareRipplePainter oldDelegate) => true;
}
in testpage.dart
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:simple_ripple_animation/simple_ripple_animation.dart';
class PlaygroundPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
child: Stack(
children: [_buildPlaygroundContent()],
),
),
);
}
Widget _buildPlaygroundContent() {
return Align(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RippleAnimation(
color: Colors.blueGrey,
delay: const Duration(milliseconds: 500),
repeat: true,
minRadius: 30,
maxRadius: 70,
ripplesCount: 4,
duration: const Duration(milliseconds: 6 * 400),
child: Container(
width: 60.r,
height: 60.r,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.r),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 5,
spreadRadius: 2,
),
],
),
child: IconButton(
onPressed: () {},
icon: Image.asset(
'assets/pnb.png',
height: 40.r,
width: 40.r,
),
),
),
),
],
),
);
}
}
本文标签: flutterRipple effect behind a widget (no press)Stack Overflow
版权声明:本文标题:flutter - Ripple effect behind a widget (no press) - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741675595a2391862.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论