Reputation: 2206
I'm trying to create a button with these effects:
Currently I have a rounded flat button like this:
RaisedButton(
color: Color(0xFFA93EF0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
How can I apply such effects to make it look 3D? I don't know the name of this effect but it looks like glass
Upvotes: 0
Views: 4346
Reputation: 4509
It is easy to use image as button resource.
If you want the 3D shadow effect by code, it can be achieved by shadows
inside Container
widget.
Here is the shape path
Path myCustomShapePath(Rect rect) {
var r1 = rect.height * 0.5;
var r2 = rect.height * 0.35;
var r3 = rect.height * 0.1;
var L = rect.left;
var R = rect.right;
var T = rect.top;
var B = rect.bottom;
return Path()
..moveTo(L, T + r2)
..arcTo(Rect.fromLTWH(L, T, r2, r2), pi, 0.5 * pi, false)
..lineTo(R - r2, T)
..arcTo(Rect.fromLTWH(R - r2, T, r2, r2), 1.5 * pi, 0.5 * pi, false)
..lineTo(R, B - r1 - 2 * r3)
..arcTo(Rect.fromLTWH(R - r3, B - r1 - 2 * r3, r3, r3), 0, 0.5 * pi, false)
..arcTo(Rect.fromLTWH(R - r1 - r3, B - r1 - r3, r1 * 2.5, r1 * 2.5),
1.5 * pi, -0.5 * pi, false)
..arcTo(Rect.fromLTWH(R - r1 - 2 * r3, B - r3, r3, r3), 0, 0.5 * pi, false)
..lineTo(R - r2, B)
..arcTo(Rect.fromLTWH(L, B - r2, r2, r2), 0.5 * pi, 0.5 * pi, false)
..close();
}
Because I want the custom shape for the button and avoid the shadow blur outside of the button, so I extend both ShapeBorder
and CustomClipper<Path>
with the same path:
CustomClipPath
class CustomClipPath extends CustomClipper<Path> {
@override
Path getClip(Size size) =>
myCustomShapePath(Rect.fromLTRB(0, 0, size.width, size.height));
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
}
CustomShape
class CustomShape extends ShapeBorder {
@override
EdgeInsetsGeometry get dimensions => const EdgeInsets.only();
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) =>
getOuterPath(rect, textDirection: textDirection);
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) =>
myCustomShapePath(rect);
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
@override
ShapeBorder scale(double t) => null;
}
Use ClipPath
to clip the shadow blur and use shadows
inside Container's ShapeDecoration
to draw the 3D effect:
class MySolidButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ClipPath(
clipper: CustomClipPath(),
child: Container(
width: 200,
height: 100,
decoration: ShapeDecoration(
shape: CustomShape(),
shadows: [
BoxShadow(color: Colors.white),
BoxShadow(
color: Color(0xFF550091),
offset: Offset(0, 20),
blurRadius: 5,
spreadRadius: 10,
),
BoxShadow(
color: Color(0xFFA93EF0),
blurRadius: 10,
spreadRadius: -2,
),
],
),
child: FlatButton(
onPressed: () {},
),
),
);
}
}
Update:
LayoutBuilder
child: Container(
width: 300,
height: 200,
child: LayoutBuilder(
builder: (context, constraint) {
// get constraint here 300 x 200
return Container(
decoration: ShapeDecoration(
shape: CustomShape(),
Upvotes: 6
Reputation: 10675
Final Output:
We can achieve this look using ClipPath
and CustomClipper
,
It will be a little hard to comprehend the code below at first if you are not accustomed to using CustomClipper
so you might have to spend some time understanding how path.lineTo
and path.quadraticBezierTo
are implemented.
Once you get a hang of it, you will be able to replicate this button as well as even more complex shapes with ease.
Full Source Code:
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,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: RoundedButton(title: 'Flutter Demo Home Page'),
);
}
}
class RoundedButton extends StatefulWidget {
RoundedButton({Key key, this.title}) : super(key: key);
final String title;
@override
_RoundedButtonState createState() => _RoundedButtonState();
}
class _RoundedButtonState extends State<RoundedButton> {
int counter = 0;
void _counter() {
setState(() {
counter = counter + 1;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: ClipPath(
clipper: CustomClipperButton(),
child: Stack(
children: [
InkWell(
onTap: _counter,
child: Container(
width: 300,
height: 150,
color: Colors.purple[900],
child: Container(
width: 200,
height: 100,
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.purple[300],
spreadRadius: 0,
blurRadius: 20.0,
),
],
),
),
),
),
Positioned(
bottom: -70,
right: -90,
child: Container(
width: 180,
height: 180,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.purple[800],
spreadRadius: 5,
blurRadius: 20.0,
),
],
),
),
),
],
),
),
),
SizedBox(height: 30),
Text(
"You Pressed $counter times",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
)
],
),
);
}
}
class CustomClipperButton extends CustomClipper<Path> {
@override
getClip(Size size) {
var path = Path();
path.moveTo(0, size.height * 0.2);
path.lineTo(0, size.height * 0.8);
path.quadraticBezierTo(0, size.height, size.width * 0.1, size.height);
path.lineTo(size.width * 0.7 - 10, size.height);
path.quadraticBezierTo(
size.width * 0.7, size.height, size.width * 0.7, size.height * 0.95);
path.quadraticBezierTo(size.width * 0.7, size.height * 0.30,
size.width - 10, size.height * 0.3);
path.quadraticBezierTo(
size.width, size.height * 0.3, size.width, size.height * 0.3 - 10);
path.lineTo(size.width, size.height * 0.2);
path.quadraticBezierTo(size.width, 0, size.width * 0.9, 0);
path.lineTo(size.width * 0.1, 0);
path.quadraticBezierTo(0, 0, 0, size.height * 0.2);
return path;
}
@override
bool shouldReclip(CustomClipper oldClipper) {
return true;
}
}
List To Documentations:
PS: I have used a dimension of 300*150 for the button container, you might have to take into account the dimension of the button that you will be creating, so calculating x,y coordinates will be a bit cumbersome at times, but as I said in the beginning after you understand the lineTo()
and quadraticBezierTo()
then implementation button of any size and shape will be very easy.
Update:
New Example without using ClipPath
and probably the easiest:
Final Output:
The only thing here you have to sacrifice is the tiny rounded edges where that central quarter-circle starts-ends.
Width and heights can be dynamically set globally if required.
If You ask me, I will suggest you follow this method instead of ClipPath
, which looks uniform, less complex, and easy to configure.
import 'package:flutter/material.dart';
class DpadButtons extends StatefulWidget {
@override
_DpadButtonsState createState() => _DpadButtonsState();
}
class _DpadButtonsState extends State<DpadButtons> {
String button = "";
void _selectedButton(String selectedButton) {
setState(() {
button = selectedButton;
});
}
@override
Widget build(BuildContext context) {
double heightMain = 100;
double widthMain = 180;
return Scaffold(
appBar: AppBar(title: Text("Dpad Button")),
body: Container(
width: double.infinity,
child: Stack(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
buildButtonRow(
b1: "Button 1",
b2: "Button 2",
onClick: _selectedButton,
width: widthMain,
height: heightMain,
),
SizedBox(
height: 10,
),
buildButtonRow(
b1: "Button 3",
b2: "Button 4",
onClick: _selectedButton,
width: widthMain,
height: heightMain,
),
SizedBox(
height: 10,
),
],
),
Center(
child: Container(
width: heightMain * 1.5,
height: heightMain * 1.5,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
),
),
Positioned(
left: 100,
top: 200,
child: Text(
"You clicked : $button",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
}
Row buildButtonRow(
{String b1, String b2, Function onClick, double width, double height}) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
onClick(b1);
},
child: Container(
width: width,
height: height,
decoration: BoxDecoration(
color: Colors.purple[900],
borderRadius: BorderRadius.circular(height * 0.2),
),
child: Container(
width: width * 0.80,
height: height * 0.80,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(height * 0.15),
boxShadow: [
BoxShadow(
color: Colors.purple[200],
spreadRadius: -8,
blurRadius: 10.0,
),
],
),
child: Center(child: Text(b1)),
),
),
),
SizedBox(
width: 10,
),
InkWell(
onTap: () {
onClick(b2);
},
child: Container(
width: width,
height: height,
decoration: BoxDecoration(
color: Colors.purple[900],
borderRadius: BorderRadius.circular(height * 0.2),
),
child: Container(
width: width * 0.80,
height: height * 0.80,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(height * 0.15),
boxShadow: [
BoxShadow(
color: Colors.purple[200],
spreadRadius: -8,
blurRadius: 10.0,
),
],
),
child: Center(child: Text(b2)),
),
),
),
],
);
}
}
Upvotes: 7