How to add shadow to ClipOval in flutter? - flutter

I have been trying to make a new app being a beginner. So, adding shadows to things is completely new to me.
So, Following is my code:
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
ClipOval(
child: Material(
color: Colors.white, // button color
child: InkWell(
// splashColor: Colors.red, // inkwell color
child: SizedBox(
width: 46, height: 46, child: Icon(Icons.menu,color: Colors.red,),),
onTap: () {},
),
),
),
],
),
),
Following is the mock:

Adding shadow to ClipOval:
Center(
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.green,
blurRadius: 50.0,
spreadRadius: 10.0,
)
],
),
child: ClipOval(
child: Image.network(
'https://i.picsum.photos/id/384/536/354.jpg?hmac=MCKw0mm4RrI3IrF4QicN8divENQ0QthnQp9PVjCGblo',
width: 100,
height: 100,
fit: BoxFit.cover,
),
),
),
),
Output:

You can create your own CustomClipper
class CustomClipperOval extends CustomClipper<Rect> {
#override
Rect getClip(Size size) {
return Rect.fromCircle(
center: new Offset(size.width / 2, size.width / 2),
radius: size.width / 2 + 3);
}
#override
bool shouldReclip(CustomClipper<Rect> oldClipper) {
return false;
}
}
class ClipOvalShadow extends StatelessWidget {
final Shadow shadow;
final CustomClipper<Rect> clipper;
final Widget child;
ClipOvalShadow({
#required this.shadow,
#required this.clipper,
#required this.child,
});
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: _ClipOvalShadowPainter(
clipper: this.clipper,
shadow: this.shadow,
),
child: ClipRect(child: child, clipper: this.clipper),
);
}
}
class _ClipOvalShadowPainter extends CustomPainter {
final Shadow shadow;
final CustomClipper<Rect> clipper;
_ClipOvalShadowPainter({#required this.shadow, #required this.clipper});
#override
void paint(Canvas canvas, Size size) {
var paint = shadow.toPaint();
var clipRect = clipper.getClip(size).shift(Offset(0, 0));
canvas.drawOval(clipRect, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
And then to use it
ClipOvalShadow(
shadow: Shadow(
color: Colors.amber,
offset: Offset(1.0, 1.0),
blurRadius: 2,
),
clipper: CustomClipperOval(),
child: ClipOval(
child: Material(
color: Colors.white, // button color
child: InkWell(
// splashColor: Colors.red, // inkwell color
child: Container(
width: 46,
height: 46,
child: Icon(
Icons.menu,
color: Colors.black,
),
),
onTap: () {},
),
),
),
),
The Result will be

Related

Position widgets in orbit around another widget in Flutter

anyone has an idea on how's the best way to position widgets in orbit AROUND another (circular) widget in flutter like this?
Thank you very much for any help you can give :D
You can Use Stack Widget . For your Idea here is the Example:
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(home: new ExampleWidget()));
}
class ExampleWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
Widget bigCircle = new Container(
width: 300.0,
height: 300.0,
decoration: new BoxDecoration(
color: Colors.orange,
shape: BoxShape.circle,
),
);
return new Material(
color: Colors.black,
child: new Center(
child: new Stack(
children: <Widget>[
bigCircle,
new Positioned(
child: new CircleButton(onTap: () => print("Cool"), iconData: Icons.favorite_border),
top: 10.0,
left: 130.0,
),
new Positioned(
child: new CircleButton(onTap: () => print("Cool"), iconData: Icons.timer),
top: 120.0,
left: 10.0,
),
new Positioned(
child: new CircleButton(onTap: () => print("Cool"), iconData: Icons.place),
top: 120.0,
right: 10.0,
),
new Positioned(
child: new CircleButton(onTap: () => print("Cool"), iconData: Icons.local_pizza),
top: 240.0,
left: 130.0,
),
new Positioned(
child: new CircleButton(onTap: () => print("Cool"), iconData: Icons.satellite),
top: 120.0,
left: 130.0,
),
],
),
),
);
}
}
class CircleButton extends StatelessWidget {
final GestureTapCallback onTap;
final IconData iconData;
const CircleButton({Key key, this.onTap, this.iconData}) : super(key: key);
#override
Widget build(BuildContext context) {
double size = 50.0;
return new InkResponse(
onTap: onTap,
child: new Container(
width: size,
height: size,
decoration: new BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: new Icon(
iconData,
color: Colors.black,
),
),
);
}
}
/// from https://gist.github.com/pskink/50554a116698f03a862a356c38b75eb3#file-rotary_dial-dart-L225
Iterable<Rect> getBounds(Rect rect, int length) sync* {
final s = Size.square(rect.shortestSide / 6.5);
final radius = (rect.shortestSide - s.shortestSide) * 0.40;
for (var i = 0; i < length; i++) {
/// distance +
final angle = i * pi / 6 + pi * .01;
final center = rect.center + Offset(cos(angle), sin(angle)) * radius;
yield Rect.fromCenter(center: center, width: s.width, height: s.height);
}
}
class Avatar3xBtnMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
final double iconSize;
Avatar3xBtnMultiChildLayoutDelegate(this.iconSize);
#override
void performLayout(Size size) {
int id = 1;
getBounds(Offset.zero & size, 3).forEach(
(rect) {
layoutChild(id, BoxConstraints.tight(rect.size));
positionChild(id, rect.centerRight);
id++;
},
);
}
#override
bool shouldRelayout(
covariant Avatar3xBtnMultiChildLayoutDelegate oldDelegate) {
return true;
}
}
And using it
class CAPG extends StatefulWidget {
const CAPG({Key? key}) : super(key: key);
#override
State<CAPG> createState() => _CAPGState();
}
class _CAPGState extends State<CAPG> {
double value = 300;
final iconSize = 24.0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
Slider(
value: value,
max: 500,
onChanged: (v) {
setState(() {
value = v;
});
}),
Container(
width: value,
height: value,
clipBehavior: Clip.none,
decoration: BoxDecoration(
color: Colors.cyanAccent.withOpacity(.2),
shape: BoxShape.circle,
border: Border.all(),
),
child: CustomMultiChildLayout(
delegate: Avatar3xBtnMultiChildLayoutDelegate(iconSize),
children: [
LayoutId(
id: 1,
child: Material(
color: Colors.purple,
shape: CircleBorder(),
child: Icon(Icons.one_k, size: iconSize),
)),
LayoutId(
id: 2,
child: Material(
color: Colors.purple,
shape: CircleBorder(),
child: Icon(Icons.two_k, size: iconSize),
)),
LayoutId(
id: 3,
child: Material(
color: Colors.purple,
shape: CircleBorder(),
child: Icon(Icons.three_k_outlined, size: iconSize),
),
),
],
),
)
],
),
),
);
}
}

Flutter draw container with a curve in the center

I'd like to draw the black container shape with flutter.
There are many ways to do this, the first time I thought it was a cut, I was thinking in a ClipPath widget, but now I see you have a Circle widget at the top of your black Container.
This is an example:
class FunnyContainer extends StatelessWidget {
const FunnyContainer({Key? key}) : super(key: key);
Widget _childContainer() {
return Padding(
padding: const EdgeInsets.all(20.0),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.yellow,
width: 4,
),
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 40),
child: Container(
height: 400,
decoration: const BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 40),
Expanded(child: _childContainer()),
Expanded(child: _childContainer()),
Expanded(child: _childContainer()),
],
),
const Positioned(
left: 0,
right: 0,
top: -50,
child: CircleAvatar(
backgroundColor: Colors.white,
radius: 40,
),
)
],
),
),
),
);
}
}
Result:
UPDATE (Using your updated design)
Now the you updated your post, this could be done using Clippers, the problem is that clip needs Shadow, so I took this code : https://gist.github.com/coman3/e631fd55cd9cdf9bd4efe8ecfdbb85a7
And I used on this example:
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Padding(
padding: const EdgeInsets.all(40.0),
child: ClipShadowPath(
clipper: _MyClipper(100),
shadow: const Shadow(
blurRadius: 15,
color: Colors.grey,
offset: Offset(0, 10),
),
child: SizedBox(
height: 400,
child: Container(
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
Spacer(),
],
),
),
),
),
),
),
);
}
}
class _MyClipper extends CustomClipper<Path> {
final double space;
_MyClipper(this.space);
#override
Path getClip(Size size) {
final path = Path();
final halfWidth = size.width / 2;
final halfSpace = space / 2;
final curve = space / 6;
final height = halfSpace / 1.4;
path.lineTo(halfWidth - halfSpace, 0);
path.cubicTo(halfWidth - halfSpace, 0, halfWidth - halfSpace + curve,
height, halfWidth, height);
path.cubicTo(halfWidth, height, halfWidth + halfSpace - curve, height,
halfWidth + halfSpace, 0);
path.lineTo(size.width, 0);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
return path;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) => true;
}
RESULT
You just need to update the Path in order to get the rounded corners of the Container.
You may use Stack which may write draw circle on top of the container like this:
class BlackContainer extends StatelessWidget {
const BlackContainer({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
#override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Container(
child: child,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.black,
boxShadow: const [
BoxShadow(
color: Colors.grey,
spreadRadius: 5,
blurRadius: 10,
),
],
),
),
Positioned(
top: -25,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
width: 40,
height: 40,
),
),
],
);
}
}
And use it like that:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlackContainer(
child: Text(
'Content here',
style: Theme.of(context)
.textTheme
.bodyText2!
.copyWith(color: Colors.white),
),
),
),
);
}
}
Position a transparent png on top of your container.
child: Container(
width: 200,
height: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.black,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: Offset(0, 3),
),
],
),
child: Container(
child: Image.asset('assets/images/half-circle.png', fit: BoxFit.contain, alignment: new Alignment(-1.0, -1.0)),
margin: EdgeInsets.only(left: 50.0, right: 50.0),
),
),
Image:
Live example

how to make a container draggable in horizontal direction in flutter

How do I get a draggable container like this, which when reaches a certain point gets vanished and a function could be called at that time. Also, the visibility of text is also decreasing and arrow icon is rotating according to the length. How can I achieve this functionality?
I have written a code, and I can add visibility widget for same. But, how to constraint it and what is the best way of doing it? Please help me out -
import 'package:flutter/material.dart';
class SwipeButton extends StatefulWidget {
const SwipeButton({Key? key}) : super(key: key);
#override
_SwipeButtonState createState() => _SwipeButtonState();
}
class _SwipeButtonState extends State<SwipeButton> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
color: Colors.orange,
width: MediaQuery.of(context).size.width * 0.9,
height: 50,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Draggable<int>(
data: 10,
feedback: Container(
height: 20.0,
width: 20.0,
color: Colors.white,
child: const Center(
child: Icon(
Icons.arrow_forward,
size: 12,
),
),
),
axis: Axis.horizontal,
childWhenDragging: Container(
height: 20.0,
width: 20.0,
color: Colors.orange,
child: const Center(
child: Text(''),
),
),
child: Container(
height: 20.0,
width: 20.0,
color: Colors.white,
child: const Center(
child: Icon(
Icons.arrow_forward,
size: 12,
),
),
),
),
DragTarget<int>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Container(
height: 20.0,
width: 20.0,
color: Colors.orange,
child: const Center(
child: Text(''),
),
);
},
onAccept: (int data) {
setState(() {
print(data);
});
},
),
],
),
),
),
),
);
}
}
For the arrow, we can use Transform.rotate.
Transform.rotate(
angle: deg2rad(-(180 + 20) * (posX / maxWidth)), // 20 comes from icon size
child: const Icon(
Icons.arrow_forward,
size: 12,
),
),
I'm not sure why onDragUpdate is not updating the UI, you can get current position of drag from that, I store it on posX.
I am using GestureDetector for test case, apply the same logic you will get the result.
Run on dartPad
class SwipeButton extends StatefulWidget {
const SwipeButton({Key? key}) : super(key: key);
#override
_SwipeButtonState createState() => _SwipeButtonState();
}
class _SwipeButtonState extends State<SwipeButton> {
double deg2rad(double deg) => deg * pi / 180;
double posX = 0.0;
#override
Widget build(BuildContext context) {
final maxWidth = MediaQuery.of(context).size.width * 0.9;
return Scaffold(
body: Center(
child: Container(
color: Colors.orange,
width: maxWidth * 0.9,
height: 50,
child: Stack(
alignment: Alignment.center,
children: [
AnimatedPositioned(
duration: const Duration(milliseconds: 100),
left: posX,
key: const ValueKey("item 1"),
child: Container(
height: 20.0,
width: 20.0,
color: Colors.white,
alignment: Alignment.center,
child: Transform.rotate(
angle: deg2rad(-(180 + 20) * (posX / maxWidth)),
child: const Icon(
Icons.arrow_forward,
size: 12,
),
),
),
),
GestureDetector(
onPanEnd: (details) {
setState(() {
posX = 0;
});
},
onPanUpdate: (details) {
setState(() {
posX = details.localPosition.dx;
});
if (posX / maxWidth > .95) {
debugPrint("on success drag");
}
},
)
],
),
),
),
);
}
}

Why calling setState in a callback inside child widget, which have another setState, breaks program?

Here is a custom Switch widget I implemented using Animation.
enum SwitchType {
LockToggle, EnableToggle
}
class DiamondSwitch extends StatefulWidget {
final double width, height;
final SwitchType switchType;
final double switchThumbSize;
final VoidCallback onTapCallback;
DiamondSwitch({
key, this.width, this.height,
this.switchType, this.switchThumbSize,
#required this.onTapCallback
}) : super(key: key);
#override
_DiamondSwitchState createState() => _DiamondSwitchState(
width: width, height: height,
switchType: switchType, switchThumbSize: switchThumbSize,
onTapCallback: onTapCallback
);
}
class _DiamondSwitchState extends State<DiamondSwitch> {
final double width, height;
final int _toggleAnimationDuration = 1000;
bool _isOn = false;
final List<Color>
_darkGradientShades = <Color>[
Colors.black, Color.fromRGBO(10, 10, 10, 1.0)
],
_lightGradientShades = <Color>[
Colors.white, Color.fromRGBO(150, 150, 150, 1.0)
];
final SwitchType switchType;
final double switchThumbSize;
List<Icon> _switchIcons = new List<Icon>();
final double _switchIconSize = 35.0;
final VoidCallback onTapCallback;
_DiamondSwitchState({
this.width = 100.0, this.height = 40.0,
this.switchThumbSize = 40.0, #required this.switchType,
#required this.onTapCallback
});
#override
void initState() {
_switchIcons.addAll(
(switchType == SwitchType.LockToggle)?
<Icon>[
Icon(
Icons.lock,
color: Colors.black,
size: _switchIconSize,
),
Icon(
Icons.lock_open,
color: Colors.white,
size: _switchIconSize,
)
]
:
<Icon>[
Icon(
Icons.done,
color: Colors.black,
size: _switchIconSize,
),
Icon(
Icons.close,
color: Colors.white,
size: _switchIconSize,
)
]
);
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: Duration(milliseconds: _toggleAnimationDuration),
width: width, height: height,
decoration: ShapeDecoration(
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.circular(28.0),
side: (_isOn)?
BorderSide(
color: Color.fromRGBO(45, 45, 45, 1.0),
width: 0.5,
)
:
BorderSide.none,
),
gradient: LinearGradient(
colors: <Color>[
...((_isOn)? _darkGradientShades : _lightGradientShades)
],
begin: Alignment(1.0, -0.8), end: Alignment(-0.7, 1.0),
stops: <double>[0.4, 1.0]
),
),
alignment: Alignment(0.0, 0.0),
child: Stack(
alignment: Alignment(0.0, 0.0),
children: <Widget>[
AnimatedPositioned(
duration: Duration(milliseconds: _toggleAnimationDuration),
curve: Curves.easeIn,
left: (_isOn)? 0.0 : ((width * 70) / 100),
right: (_isOn)? ((width * 70) / 100) : 0.0,
child: GestureDetector(
onTap: () {
onTapCallback();
setState(() {
_isOn = !_isOn;
});
},
child: AnimatedSwitcher(
duration: Duration(milliseconds: _toggleAnimationDuration),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: child,
);
},
child: Transform.rotate(
alignment: Alignment.center,
angle: (math.pi / 4),
child: Container(
width: switchThumbSize, height: switchThumbSize,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3.0),
color: (_isOn)? Colors.white : Colors.black,
border: (_isOn)?
null
:
Border.all(
color: Color.fromRGBO(87, 87, 87, 1.0),
width: 1.0,
),
),
child: Transform.rotate(
alignment: Alignment.center,
angle: -(math.pi / 4),
child: (_isOn)?
_switchIcons[0]
:
_switchIcons[1],
),
),
),
),
),
)
],
),
);
}
}
I added a onTapCallback because I need to set another flag in the parent widget to trigger a Image change. Here is related code that belongs to parent widget;
DiamondSwitch(
switchType: SwitchType.LockToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
onTapCallback: () {
this.setState(() {
this._isLockOn = !this._isLockOn;
});
},
key: UniqueKey(),
),
When I run this code, animation doesn't work. It detects tap and executes onTap callback, and all code in onTap Works, (I tested with print methods), but as I said, animation isn't happening.
I want to learn why does this happen, is this about how Flutter work? If yes, can you explain?
TY for taking time ^.^!
EDIT
I want to know why does getting a method with setState breaks the animation I'm sharing the current parent widget with the approach of the answerer #pulyaevskiy implemented.
class _SettingsState extends State<Settings> {
bool
_isLockOn = false,
_isPassiconOn = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Column(
children: <Widget>[
/*Lock Toggle Graphic*/
Align(
alignment: Alignment(-0.1, 0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
/*Lock Toggle Text*/
RotatedBox(
quarterTurns: 3,
child: Column(
children: <Widget>[
Text(
(_isLockOn)? "locked" : "unlocked",
style: TextStyle(
color: Colors.white,
fontFamily: "Philosopher",
fontSize: 25.0,
shadows: <Shadow>[
Shadow(
color: Color.fromRGBO(184, 184, 184, 0.68),
offset: Offset(2.0, 2.0),
blurRadius: 4.0,
),
],
),
),
Padding(
padding: const EdgeInsets.only(top: 5.5),
child: Container(
color: Color.fromRGBO(204, 204, 204, 1.0),
width: 30.0, height: 1.0,
),
),
],
),
),
/*Lock Toggle Image*/
Image.asset(
"assets/images/settings_screen/crystal_"
"${(_isLockOn)? "white_light_up" : "black_outline"}.png",
scale: 5.0,
alignment: Alignment.center,
),
],
),
),
/*Lock Toggle Switch*/
Padding(
padding: const EdgeInsets.only(top: 12.5),
child: DiamondSwitch(
switchType: SwitchType.LockToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
flagToControl: this._isLockOn,
onTapCallback: () {
this.setState(() {
this._isLockOn = !this._isLockOn;
});
},
key: UniqueKey(),
),
),
/*Separator*/
WhiteDiamondSeparator(paddingAmount: 36.5),
/*Passicon Toggle Graphic*/
Align(
alignment: Alignment(-0.32, 0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
/*Lock Toggle Text*/
RotatedBox(
quarterTurns: 3,
child: Column(
children: <Widget>[
Text(
"passicon",
style: TextStyle(
color: Colors.white,
fontFamily: "Philosopher",
fontSize: 25.0,
shadows: <Shadow>[
Shadow(
color: Color.fromRGBO(184, 184, 184, 0.68),
offset: Offset(2.0, 2.0),
blurRadius: 4.0,
),
],
),
),
Padding(
padding: const EdgeInsets.only(top: 5.5),
child: Container(
color: Color.fromRGBO(204, 204, 204, 1.0),
width: 30.0, height: 1.0,
),
),
],
),
),
/*Passicon Toggle Image*/
Padding(
padding: const EdgeInsets.only(left: 40),
child: Image.asset(
"assets/images/settings_screen/emote_"
"${(_isPassiconOn)? "winking" : "nervous"}.png",
scale: 3.25,
alignment: Alignment.center,
),
),
],
),
),
/*Passicon Toggle Switch*/
Padding(
padding: const EdgeInsets.only(top: 42.5),
child: DiamondSwitch(
switchType: SwitchType.PassiconToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
flagToControl: this._isPassiconOn,
onTapCallback: () {
this.setState(() {
this._isPassiconOn = !this._isPassiconOn;
});
},
key: UniqueKey(),
),
),
/*Separator*/
WhiteDiamondSeparator(paddingAmount: 36.5),
],
)
),
);
}
}
And I added flagToControl in DiamondSwitch and using it in _DiamondSwitchState as bool get _isOn => widget.flagToControl;.
In the old approach, if I don't execute and other thing than
setState() { _isOn = !_isOn; }
animation happens as it should. What am I missing?
In your onTapCallback you are changing state of your parent widget, which works indeed. However it does not affect state of the DiamondSwitch widget itself
(I see that in GestureDetector you also setState of the switch widget, but there are a few issues with this approach).
To fix this you can pass the value of this._isLockOn from your parent widget's state to the DiamondSwitch child. This means you need another property on your switch widget. E.g.
class DiamondSwitch extends StatefulWidget {
final bool isOn;
// ... remaining fields go here
DiamondSwitch({this.isOn, ...});
}
Then change _DiamondSwitchState as well. Could simply proxy _isOn to the widget's value:
class _DiamondSwitchState extends State<DiamondSwitch> {
bool get _isOn => widget.isOn;
}
This is much better than keeping state of isOn in two places as you have now (in parent widget AND in the switch itself). With this change your isLockOn state is only kept on the parent widget and you just pass it down to the switch child to use.
This means that for GestureDetector's onTap property you'll simply pass the onTapCallback of the parent widget as well, no need to wrap it with another function.
The last part: in your parent widget's build method:
DiamondSwitch(
isOn: this._isLockOn, // <-- new line here to pass the value down
switchType: SwitchType.LockToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
onTapCallback: () {
this.setState(() {
this._isLockOn = !this._isLockOn;
});
},
key: UniqueKey(),
),
Another benefit of doing it this way is that now you can initialize your switch with a different default value if needed (right now it's hardcoded to always start off as false). So if you load value of isLockOn from a database and it's set to true you can immediately pass this value to the switch child and represent your state correctly.

Outlined transparent button with gradient border in flutter

Is it possible to create an outlined(transparent) button with gradient border in flutter? I tried to use LinearGradient in BorderSide style but it's not allowed.
I spent about two hours on it :)
how to use:
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
UnicornOutlineButton(
strokeWidth: 2,
radius: 24,
gradient: LinearGradient(colors: [Colors.black, Colors.redAccent]),
child: Text('OMG', style: TextStyle(fontSize: 16)),
onPressed: () {},
),
SizedBox(width: 0, height: 24),
UnicornOutlineButton(
strokeWidth: 4,
radius: 16,
gradient: LinearGradient(
colors: [Colors.blue, Colors.yellow],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
child: Text('Wow', style: TextStyle(fontSize: 16)),
onPressed: () {},
),
],
),
),
),
),
);
}
}
and the class itself:
class UnicornOutlineButton extends StatelessWidget {
final _GradientPainter _painter;
final Widget _child;
final VoidCallback _callback;
final double _radius;
UnicornOutlineButton({
#required double strokeWidth,
#required double radius,
#required Gradient gradient,
#required Widget child,
#required VoidCallback onPressed,
}) : this._painter = _GradientPainter(strokeWidth: strokeWidth, radius: radius, gradient: gradient),
this._child = child,
this._callback = onPressed,
this._radius = radius;
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: _painter,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: _callback,
child: InkWell(
borderRadius: BorderRadius.circular(_radius),
onTap: _callback,
child: Container(
constraints: BoxConstraints(minWidth: 88, minHeight: 48),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_child,
],
),
),
),
),
);
}
}
class _GradientPainter extends CustomPainter {
final Paint _paint = Paint();
final double radius;
final double strokeWidth;
final Gradient gradient;
_GradientPainter({#required double strokeWidth, #required double radius, #required Gradient gradient})
: this.strokeWidth = strokeWidth,
this.radius = radius,
this.gradient = gradient;
#override
void paint(Canvas canvas, Size size) {
// create outer rectangle equals size
Rect outerRect = Offset.zero & size;
var outerRRect = RRect.fromRectAndRadius(outerRect, Radius.circular(radius));
// create inner rectangle smaller by strokeWidth
Rect innerRect = Rect.fromLTWH(strokeWidth, strokeWidth, size.width - strokeWidth * 2, size.height - strokeWidth * 2);
var innerRRect = RRect.fromRectAndRadius(innerRect, Radius.circular(radius - strokeWidth));
// apply gradient shader
_paint.shader = gradient.createShader(outerRect);
// create difference between outer and inner paths and draw it
Path path1 = Path()..addRRect(outerRRect);
Path path2 = Path()..addRRect(innerRRect);
var path = Path.combine(PathOperation.difference, path1, path2);
canvas.drawPath(path, _paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => oldDelegate != this;
}
You can achieve this by doing just a simple trick
You have to define two Containers.
First outer container with a gradient background and the second inner container with white background. and as a child of the inner container, you can place anything e.g. TextField, Text, another button, etc.
final kInnerDecoration = BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(32),
);
final kGradientBoxDecoration = BoxDecoration(
gradient: LinearGradient(colors: [Colors.black, Colors.redAccent]),
border: Border.all(
color: kHintColor,
),
borderRadius: BorderRadius.circular(32),
);
Now this is your View
Container(
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Container(
child:Text("Button Title with your style"),
decoration: kInnerDecoration,
),
),
height: 66.0,
decoration: kGradientBoxDecoration,
),
Done
Use OutlinedButton (Recommended)
Create this class (null-safe code)
class MyOutlinedButton extends StatelessWidget {
final VoidCallback onPressed;
final Widget child;
final ButtonStyle? style;
final Gradient? gradient;
final double thickness;
const MyOutlinedButton({
Key? key,
required this.onPressed,
required this.child,
this.style,
this.gradient,
this.thickness = 2,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(gradient: gradient),
child: Container(
color: Colors.white,
margin: EdgeInsets.all(thickness),
child: OutlinedButton(
onPressed: onPressed,
style: style,
child: child,
),
),
);
}
}
Usage:
MyOutlinedButton(
onPressed: () {},
gradient: LinearGradient(colors: [Colors.indigo, Colors.pink]),
child: Text('OutlinedButton'),
)
To change the size, you can insert a Container:
OutlineGradientButton(
child: Container(
constraints: BoxConstraints(maxWidth: 300, maxHeight: 50),
height: 50,
alignment: Alignment.center,
child: Text(
'Text',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white, fontSize: 20, fontWeight: FontWeight.w500),
),
),
gradient: LinearGradient(
colors: [Color(0xfff3628b), Color(0xffec3470)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
strokeWidth: 3,
radius: Radius.circular(25),
),
Now there is a easier way provided. Flutter now has a package which does the job perfectly. I am leaving a link to the documentation for further use
https://pub.dev/packages/gradient_borders.
You can use a structure like below. You can also use it as a flat button by removing BorderRadius.
InkWell(
onTap: () {
print("TAP");
},
child: Container(
height: 85,
width: 85,
padding: EdgeInsets.all(6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
gradient: LinearGradient(
colors: [Colors.blue, Colors.black],
begin: Alignment(-1, -1),
end: Alignment(2, 2),
),
),
child: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(100),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
image: DecorationImage(
image: Image.network("https://images.unsplash.com/photo-1612151855475-877969f4a6cc?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8aGQlMjBpbWFnZXxlbnwwfHwwfHw%3D&w=400&q=80").image,
fit: BoxFit.fitHeight,
), //By deleting the image here, you can only use it text.
color: Colors.white,
border: Border.all(
color: Colors.white,
width: 4,
),
),
child: Center(child: Text("sssss")), //By deleting the text here, you can only use it visually.
width: 75,
height: 75,
),
),
),
),
)
Wrap your widget width CustomPaint widget and use this _CustomGradientBorder that extends CustomPainter.
CustomPaint(painter: const _CustomGradientBorder(thickness: 1,
colors: [Colors.red, Colors.green, Colors.blue, Colors.tealAccent],
radius: 8), child: //widget here)
class _CustomGradientBorder extends CustomPainter{
final double thickness;
final List<Color> colors;
final double radius;
const _CustomGradientBorder({required this.thickness, required this.colors, required this.radius});
#override
void paint(Canvas canvas, Size size) {
final Path path = Path();
path.moveTo(0, size.height/2);
path.lineTo(0, radius);
path.quadraticBezierTo(0, 0, radius, 0);
path.lineTo(size.width-radius, 0);
path.quadraticBezierTo(size.width, 0, size.width, radius);
path.lineTo(size.width, size.height-radius);
path.quadraticBezierTo(size.width, size.height, size.width-radius, size.height);
path.lineTo(radius, size.height);
path.quadraticBezierTo(0, size.height, 0, size.height-radius);
path.close();
final Paint paint = Paint()
..style = PaintingStyle.stroke
..shader = LinearGradient(colors: colors).createShader(Rect.fromCenter(center: Offset(size.width/2, size.height/2), width: size.width, height: size.height))
..strokeWidth = thickness;
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
class GradientBorderWidget extends StatelessWidget {
final Widget child;
const GradientBorderWidget({super.key, required this.child});
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 1.5, vertical: 1.5),
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: const LinearGradient(
colors: [color1, color2, color3],
begin: Alignment.centerLeft,
end: Alignment.centerRight)),
alignment: Alignment.center,
child: ClipRRect(borderRadius: BorderRadius.circular(15), child:
child),
);
}
}
I tried many ways to do that, but all of them had their limitations, then I found a package that worked how I expected: https://pub.dev/packages/outline_gradient_button