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:
- ClipPath
- CustomClipper
- lineTo()
- quadraticBezierTo()
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)),
),
),
),
],
);
}
}