How to make a button with ripple effect? - flutter

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.

Related

How to make a custom bubble shape in flutter?

I am trying to create a custom tooltip with the triangle shape on either side. I have created a bubble but how to add the triangle in there without using any library?
class SdToolTip extends StatelessWidget {
final Widget child;
final String message;
const SdToolTip({
required this.message,
required this.child,
});
#override
Widget build(BuildContext context) {
return Center(
child: Tooltip(
child: child,
message: message,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blueAccent.withOpacity(0.6),
borderRadius: BorderRadius.circular(22)),
textStyle: const TextStyle(
fontSize: 15, fontStyle: FontStyle.italic, color: Colors.white),
),
);
}
}
You can do it by CustomPainter without any library.
Example 1:
Create Custom Painter Class,
class customStyleArrow extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..color = Colors.white
..strokeWidth = 1
..style = PaintingStyle.fill;
final double triangleH = 10;
final double triangleW = 25.0;
final double width = size.width;
final double height = size.height;
final Path trianglePath = Path()
..moveTo(width / 2 - triangleW / 2, height)
..lineTo(width / 2, triangleH + height)
..lineTo(width / 2 + triangleW / 2, height)
..lineTo(width / 2 - triangleW / 2, height);
canvas.drawPath(trianglePath, paint);
final BorderRadius borderRadius = BorderRadius.circular(15);
final Rect rect = Rect.fromLTRB(0, 0, width, height);
final RRect outer = borderRadius.toRRect(rect);
canvas.drawRRect(outer, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Wrap your text widget with CustomPaint,
return CustomPaint(
painter: customStyleArrow(),
child: Container(
padding: EdgeInsets.only(left: 15, right: 15, bottom: 20, top: 20),
child: Text("This is the custom painter for arrow down curve",
style: TextStyle(
color: Colors.black,
)),
),
);
Example 2:
Check below example code for tooltip shapedecoration
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Customize Tooltip'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Tooltip(
child: const IconButton(
icon: Icon(Icons.info, size: 30.0),
onPressed: null,
),
message: 'Hover Icon for Tooltip...',
padding: const EdgeInsets.all(20),
showDuration: const Duration(seconds: 10),
decoration: ShapeDecoration(
color: Colors.blue,
shape: ToolTipCustomShape(),
),
textStyle: const TextStyle(color: Colors.white),
preferBelow: false,
verticalOffset: 20,
),
),
);
}
}
class ToolTipCustomShape extends ShapeBorder {
final bool usePadding;
ToolTipCustomShape({this.usePadding = true});
#override
EdgeInsetsGeometry get dimensions =>
EdgeInsets.only(bottom: usePadding ? 20 : 0);
#override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) => Path();
#override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
rect =
Rect.fromPoints(rect.topLeft, rect.bottomRight - const Offset(0, 20));
return Path()
..addRRect(
RRect.fromRectAndRadius(rect, Radius.circular(rect.height / 3)))
..moveTo(rect.bottomCenter.dx - 10, rect.bottomCenter.dy)
..relativeLineTo(10, 20)
..relativeLineTo(10, -20)
..close();
}
#override
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
#override
ShapeBorder scale(double t) => this;
}
Wrap your widget with CustomPaint refer to this article https://medium.com/flutter-community/a-deep-dive-into-custompaint-in-flutter-47ab44e3f216 and documentation for more info, should do the trick.
Try this package https://pub.dev/packages/shape_of_view_null_safe
ShapeOfView(
shape: BubbleShape(
position: BubblePosition.Bottom,
arrowPositionPercent: 0.5,
borderRadius: 20,
arrowHeight: 10,
arrowWidth: 10
),
//Your Data goes here
child: ...,
)

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()

How to make this arc in flutter

I want to work exactly like this image with the circles inside it, how can I do it?
Here is your widget
Center(
child: Container(
width: 200,
height: 100,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(20.0)),
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF9B6EEF), Color(0xFF715ED7)]),
),
child: ClipRect(child: CustomPaint(painter: CirclePainter())),
),
),
CustomPainter class for rings inside
class CirclePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = Colors.white10
..style = PaintingStyle.stroke
..strokeWidth = 25;
canvas.drawCircle(Offset.zero, 60, paint);
canvas.drawCircle(Offset(size.width, size.height), 60, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Result
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SO(),
debugShowCheckedModeBanner: false,
);
}
}
class SO extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ClipRRect(
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24)),
child: CustomPaint(
size: Size(250, 200),
painter: SOP(
fillColor: Colors.indigo,
spreadColor: Colors.indigoAccent,
spread: 25,
radius: 100,
),
),
),
),
);
}
}
class SOP extends CustomPainter {
final double spread; //the thickness of inner circles
final Color spreadColor; //the color of inner circles
final Color fillColor; //the background color
final double radius; //the radius of inner circles
final Paint p;
SOP({
#required this.spread,
#required this.spreadColor,
#required this.fillColor,
#required this.radius,
}) : p = Paint()
..strokeWidth = spread
..style = PaintingStyle.stroke
..color = spreadColor;
#override
void paint(Canvas canvas, Size size) {
canvas.drawColor(fillColor, BlendMode.src);
canvas.drawCircle(Offset(0, 0), radius, p);
canvas.drawCircle(Offset(size.width, size.height), radius, p);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}

How to create a background with stripes in Flutter

I'm trying to build a background consisting of alternating red and orange stripes like this:
I don't want to use a static image to ensure consistency over different devices.
I tried to use gradients but I'm having trouble making it work.
Container(
decoration: BoxDecoration(
// Box decoration takes a gradient
gradient: LinearGradient(
// Where the linear gradient begins and ends
begin: Alignment.topRight,
end: Alignment(0.3, 0),
tileMode: TileMode.repeated, // repeats the gradient over the canvas
colors: [
// Colors are easy thanks to Flutter's Colors class.
Colors.red,
Colors.orange,
],
),
),
),
Is there a better way to solve this other than gradients in Dart / Flutter?
I've just modified gradient value in the question and ended up with this.
You can adjust the end value to change the angle and width of the strips.
BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment(-0.4, -0.8),
stops: [0.0, 0.5, 0.5, 1],
colors: [
Colors.red,
Colors.red,
Colors.orange,
Colors.orange,
],
tileMode: TileMode.repeated,
),
)
What about using Clipper and using stack of RectangularWidgets and clipping off the left corner triangle each time with increasing heights.
class MyCustomClipper extends CustomClipper<Path> {
final double extent;
MyCustomClipper({this.extent});
#override
Path getClip(Size size) {
var path = Path();
path.moveTo(0, extent);
path.lineTo(extent, 0);
path.lineTo(size.width, 0);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return true;
}
}
class StripsWidget extends StatelessWidget {
final Color color1;
final Color color2;
final double gap;
final noOfStrips;
const StripsWidget(
{Key key, this.color1, this.color2, this.gap, this.noOfStrips})
: super(key: key);
List<Widget> getListOfStripes() {
List<Widget> stripes = [];
for (var i = 0; i < noOfStrips; i++) {
stripes.add(
ClipPath(
child: Container(color: (i%2==0)?color1:color2),
clipper: MyCustomClipper(extent: i*gap),
),
);
}
return stripes;
}
#override
Widget build(BuildContext context) {
return Stack(children: getListOfStripes());
}
}
Usage:
StripsWidget(
color1:Color.fromRGBO(231, 79, 36, 1),
color2:Color.fromRGBO(218, 59, 32, 1),
gap: 100,
noOfStrips: 10,
),
At each time I clipped top left triangle and increased the size of triangle with gap of specified gap in constructor and ran loop noOfStrips times in the constructor defined.
And the output that I got was exactly the same
Example Usage
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body:StripsWidget(color1:Color.fromRGBO(231, 79, 36, 1),color2:Color.fromRGBO(218, 59, 32, 1),gap: 100,noOfStrips: 10,),
);
}
}
create CustomPaint
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: CustomPaint(
size: Size(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height),
painter: BackGround(),
child: Container(
child: Center(
child: Icon(Icons.android , size: 100,),
),
),
),
),
backgroundColor: Colors.black,
);
}
}
class BackGround extends CustomPainter{
#override
void paint(Canvas canvas, Size size) {
Paint paint = new Paint();
paint.color = Colors.red;
paint.strokeWidth = 100;
paint.isAntiAlias = true;
Paint paint2 = new Paint();
paint2.color = Colors.orange;
paint2.strokeWidth = 100;
paint2.isAntiAlias = true;
canvas.drawLine(Offset(300, -120), Offset(size.width+60, size.width-280), paint2);
canvas.drawLine(Offset(200, -80), Offset(size.width+60, size.width-160), paint);
canvas.drawLine(Offset(100, -40), Offset(size.width+60, size.width-40), paint2);
canvas.drawLine(Offset(0, 0), Offset(size.width+60, size.width+80), paint);
canvas.drawLine(Offset(-100, 40), Offset(size.width+60, size.width+200), paint2);
canvas.drawLine(Offset(-200, 90), Offset(size.width+60, size.width+320), paint);
canvas.drawLine(Offset(-300, 140), Offset(size.width+60, size.width+440), paint2);
canvas.drawLine(Offset(-400, 190), Offset(size.width+60, size.width+560), paint);
canvas.drawLine(Offset(-500, 240), Offset(size.width+60, size.width+680), paint2);
}
Use canvas (CustomPainter and CustomPaint Widgets).
Or SVG by package: https://pub.dev/packages/flutter_svg

Ripple animation 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);
}
}