Flutter - How to make the custom rounded shape of card in flutter? - flutter

I want to make a card with a rounded shape like the image below
I tried a lot for it but I can't make it a shape like a design.

I have made a sample with ShapeBorder that you can copy(paste to dartPad and play with.
Replace the Icon with an Image.
PS. I added a LayoutBuilder to make it more robust to responsive layouts.
This is the result, hope it can help you:
import "package:flutter/material.dart";
import "dart:math";
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false, home: Scaffold(body: HomeScreen())));
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
width: 300,
height: 800,
child: Column(children: <Widget>[
MyCustomCard(),
MyCustomCard(),
]));
}
}
class MyCustomCard extends StatelessWidget {
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (_, BoxConstraints bc) {
return Container(
padding: EdgeInsets.all(10),
color: Colors.black,
child: Stack(children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 80,
),
Container(
width: double.infinity,
//height: 400,
decoration: ShapeDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xff3344ff), Color(0x883344ff)],
stops: [0, 1],
),
shape: CustomCardShape(
//kW:MediaQuery.of(context).size.width-40,
),
),
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(height: 20),
Text(
"Steve Jobs",
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
Text(
"Seattle",
style:
TextStyle(color: Colors.white, fontSize: 18),
),
SizedBox(height: 20),
Text(
"hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello ",
style:
TextStyle(color: Colors.white, fontSize: 14),
),
]),
)),
]),
Positioned(
top: 20,
//left: (MediaQuery.of(context).size.width/2)-60, //100,
left: bc.constrainWidth() / 2 - 50,
child: Container(
alignment: Alignment.center,
width: 80,
decoration: BoxDecoration(
color: Colors.black,
shape: BoxShape.circle,
),
child: Icon(Icons.home, color: Colors.white, size: 80),
),
),
]), // stack
);
});
}
}
class CustomCardShape extends ShapeBorder {
//final double kH = 350; // card height
//final double kW; // = 260; // card width
final double circleW = 100;
CustomCardShape(); //{this.kW});
#override
EdgeInsetsGeometry get dimensions => EdgeInsets.all(0);
#override
Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;
#override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
rect = Rect.fromPoints(rect.topLeft, rect.bottomRight);
double kW = rect.width - 20;
double kH = rect.height;
double www = (kW - circleW - 20) / 2;
double wwwR = www;
return Path()
..moveTo(rect.topLeft.dx, rect.topLeft.dy + 20)
..lineTo(rect.topLeft.dx, rect.topLeft.dy + kH)
// rect bottom left
..arcTo(Rect.fromLTWH(rect.topLeft.dx, rect.topLeft.dy + kH, 20, 20), -pi,
-pi / 2, false)
..lineTo(rect.topLeft.dx + kW, rect.topLeft.dy + kH + 20)
// rect bottom right
..arcTo(Rect.fromLTWH(rect.topLeft.dx + kW, rect.topLeft.dy + kH, 20, 20),
-3 * pi / 2, -pi / 2, false)
..lineTo(rect.topLeft.dx + kW + 20, rect.topLeft.dy + 20)
// rect top right
..arcTo(Rect.fromLTWH(rect.topLeft.dx + kW, rect.topLeft.dy, 20, 20), 0,
-pi / 2, false)
..lineTo(rect.topLeft.dx + kW - www, rect.topLeft.dy)
// circle bottom right
..arcTo(
Rect.fromLTWH(
rect.topLeft.dx + kW - wwwR, rect.topLeft.dy - 20, 20, 20),
pi / 2,
pi / 2,
false)
..lineTo(rect.topLeft.dx + kW - wwwR, rect.topLeft.dy - 20)
// circle
..arcTo(
Rect.fromLTWH(rect.topLeft.dx + kW - wwwR - circleW,
rect.topLeft.dy - 20 - 50, circleW, circleW),
0,
-pi,
false)
..lineTo(rect.topLeft.dx + kW - wwwR - circleW, rect.topLeft.dy - 20)
// circle bottom left
..arcTo(
Rect.fromLTWH(
rect.topLeft.dx + 20 + www - 20, rect.topLeft.dy - 20, 20, 20),
0,
pi / 2,
false)
..lineTo(rect.topLeft.dx, rect.topLeft.dy)
// rect top left
..arcTo(Rect.fromLTWH(rect.topLeft.dx, rect.topLeft.dy, 20, 20), -pi / 2,
-pi / 2, false)
..close();
}
#override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
#override
ShapeBorder scale(double t) => this;
}

Related

Can't solve this "The method 'WaveBasePainter' isn't defined for the type '_PlayerAppState'" error

I'm following this speed code tutorial (https://www.youtube.com/watch?v=KO_PYJKHglo) and currently facing some problems. Where it all started is somewhere at 2:42 in the youtube video.
This is my main.dart file.
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
home: PlayerApp(),
),
);
}
class PlayerApp extends StatefulWidget {
const PlayerApp({super.key});
#override
State<PlayerApp> createState() => _PlayerAppState();
}
class _PlayerAppState extends State<PlayerApp> {
#override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
final width = MediaQuery.of(context).size.width;
return Scaffold(
body: Stack(
children: <Widget>[
Positioned(
height: height,
width: width,
child: Material(
elevation: 16,
color: const Color(0xFFd6dde5), //Background Color
borderRadius: BorderRadius.circular(20),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 30.0,
),
child: Column(
children: <Widget>[
const SizedBox(
height: 90,
),
const Text(
'Music title',
),
const SizedBox(
height: 15,
),
const Text(
'Music artist',
),
const SizedBox(
height: 75,
),
buildRecordPlayer(),
const SizedBox(
height: 60,
),
Row(
children: <Widget>[
const Text('time'),
const SizedBox(
width: 8,
),
buildWave(),
const SizedBox(
width: 8,
),
const Text('end'),
],
)
],
),
),
),
)
],
),
);
}
Widget buildRecordPlayer() {
return Container(
height: 190,
width: 190,
alignment: Alignment.center,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/vinyl.png'),
fit: BoxFit.fitHeight,
colorFilter: ColorFilter.mode(
Colors.blue,
BlendMode.color,
),
),
shape: BoxShape.circle,
),
child: ClipOval(
child: Image.asset(
'assets/images/SL.png',
height: 150,
width: 150,
fit: BoxFit.fill,
),
),
);
}
Widget buildWave() {
return SizedBox(
width: 260,
height: 40,
child: CustomPaint(
painter: WaveBasePainter(),
),
);
}
}
And this is my wave_base_painter.dart file
import 'package:flutter/material.dart';
import 'dart:math';
class WaveBasePainter extends CustomPainter {
Paint? _paint; //4:03
#override
void paint(Canvas canvas, Size size) {
_paint = Paint()
..color = Colors.grey.withOpacity(0.3)
..style = PaintingStyle.stroke
..strokeWidth = 1.5;
canvas.translate(0, size.height / 2);
canvas.scale(1, -1);
for (int i = 0; i < size.width.toInt(); i++) {
double x = i.toDouble();
double r = 2 * sin(i) - 2 * cos(4 * i) + sin(2 * i - pi * 24);
r = r * 5;
canvas.drawLine(Offset(x, r), Offset(x, -r), _paint!);
}
#override
bool shouldRepaint(WaveBasePainter oldDelegate) => false;
}
}
This is what it resulted and I have no idea why the speed code video that I'm following doesn't show any errors and mine does.
You have defined shouldRepaint function incorrectly.
You can see this file, I have updated it:
class WaveBasePainter extends CustomPainter {
Paint? _paint; //4:03
#override
void paint(Canvas canvas, Size size) {
_paint = Paint()
..color = Colors.grey.withOpacity(0.3)
..style = PaintingStyle.stroke
..strokeWidth = 1.5;
canvas.translate(0, size.height / 2);
canvas.scale(1, -1);
for (int i = 0; i < size.width.toInt(); i++) {
double x = i.toDouble();
double r = 2 * sin(i) - 2 * cos(4 * i) + sin(2 * i - pi * 24);
r = r * 5;
canvas.drawLine(Offset(x, r), Offset(x, -r), _paint!);
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}

Flutter border around container with text in the middle (divider?)

I try to create a border around a container, that is not that difficult ofcourse, but i need also a text IN the border with space around it. Like horizontal divder but i need a complete border.
Attached what i like to achieve.
Any one who can help me how to approach this?
Thanks!
Tried to use the horizontal and vertical divider packages, but then the border is not in full.
You can use CustomPainter like this:
class CustomDraw extends CustomPainter {
late Paint painter;
late double radius;
late double textWidth;
CustomDraw(Color color, this.textWidth, {this.radius = 0}) {
painter = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2
..color = color;
}
#override
void paint(Canvas canvas, Size size) {
var path = Path();
path.moveTo(size.width - ((size.width - textWidth) / 2), 0);
path.lineTo(size.width - radius, 0);
path.cubicTo(size.width - radius, 0, size.width, 0, size.width, radius);
path.lineTo(size.width, size.height - radius);
path.cubicTo(size.width, size.height - radius, size.width, size.height,
size.width - radius, size.height);
path.lineTo(radius, size.height);
path.cubicTo(radius, size.height, 0, size.height, 0, size.height - radius);
path.lineTo(0, radius);
path.cubicTo(0, radius, 0, 0, radius, 0);
path.lineTo(((size.width - textWidth) / 2), 0);
canvas.drawPath(path, painter);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
and this widget:
class CustomTitleWidget extends StatefulWidget {
final double height;
final double width;
final double? radius;
final String title;
const CustomTitleWidget(
{Key? key,
required this.height,
required this.width,
required this.title,
this.radius})
: super(key: key);
#override
State<CustomTitleWidget> createState() => _CustomTitleWidgetState();
}
class _CustomTitleWidgetState extends State<CustomTitleWidget> {
GlobalKey textKey = GlobalKey();
double textHeight = 0.0;
double textWidth = 0.0;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
final textKeyContext = textKey.currentContext;
if (textKeyContext != null) {
final box = textKeyContext.findRenderObject() as RenderBox;
textHeight = box.size.height;
textWidth = box.size.width;
}
});
});
}
#override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
alignment: Alignment.topCenter,
children: [
CustomPaint(
child: Container(
height: widget.height,
width: widget.width,
),
painter: CustomDraw(
Colors.red,
textWidth,
radius: widget.radius ?? 0,
),
),
Positioned(
top: -textHeight / 2,
child: Padding(
key: textKey,
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
widget.title,
),
),
)
],
);
}
}
use like this:
CustomTitleWidget(
height: 200,
width: double.infinity,
title: 'asdasdasdasdasd',
radius: 16),
#Maenda, We can implement such kind of structure using Stack, take one container with a border & put the other container over the first one.
Here is an example:
Stack(
children: [
Positioned(
top: 12,
left: 0,
right: 0,
child: Container(
height: 120,
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.green,
border: Border.all(width: 1.2),
borderRadius: BorderRadius.circular(3)),
)),
Container(
margin: EdgeInsets.symmetric(
horizontal: 21,
),
child: Column(
children: [
Container(
width: 100,
margin: EdgeInsets.only(top: 12, bottom: 5),
alignment: Alignment.center,
color: Colors.blue,
child: Text(
"Any Text",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
Text(
"Nature, in the broadest sense, is the physical world or universe. Nature can refer to the phenomena of the physical world, and also to life in general.",
textAlign: TextAlign.center,
),
SizedBox(
height: 8,
)
],
),
),
],
);
Hope this works! You can customize as per the design.

How to have a dotted line on one side of container?

How can I add a dotted border for a container but for only one side but also with border radius?
There are packages like dotted_border and fdottedline , but they both do around the whole container.
I'm trying to achieve this
As mentioned in another thread there isn't a default way of implementing this yet, however the method detailed in this answer seems to get you part way there. There are also a few other approaches to this problem in there too which may be of help.
It is not possible with an actual border. especially not since the curved part of the border is still solid in your picture. However, I managed to get the desired result by overlaying a dotted line at the center between two containers with a solid border, using a Stack
class MyWidget extends StatelessWidget {
static const double _height = 500;
static const double _width = 500;
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
width: _width + 30,
height: _height + 30,
child: Center(
child: Stack(
alignment: Alignment.center,
children: [
Row(
children: [
_container(),
_container(),
],
mainAxisAlignment: MainAxisAlignment.center,
),
CustomPaint(
foregroundPainter: DashedLinePainter(),
child: Container(
width: 5, color: Colors.white, height: _height - 30)),
],
),
),
);
}
Widget _container() {
return Container(
height: _height,
width: _width / 2,
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
width: 2,
),
borderRadius: BorderRadius.all(Radius.circular(15.0)),
),
);
}
}
class DashedLinePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
double dashWidth = 9, dashSpace = 5, startX = size.width / 2, startY = 0;
final paint = Paint()
..color = Colors.blueAccent
..strokeWidth = 2;
while (startY < size.height) {
canvas.drawLine(
Offset(startX, startY), Offset(startX, startY + dashWidth), paint);
startY += dashWidth + dashSpace;
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
This code renders this:
You can use the plugin dotted_decoration to achieve the required design.
Code:-
Container(
decoration: DottedDecoration(
color: Colors.white,
strokeWidth: 0.5,
linePosition: LinePosition.right,
),
height:120,
width: 50,
),

Draw Bezier curve with rectangle in Flutter

I'm starting with Flutter and have to design a UI that looks like
But with icon button at the center of the Bezier curve.
What I tried is
class HeaderPainter extends CustomPainter {
HeaderPainter({
#required this.color,
#required this.avatarRadius
});
final Color color;
final double avatarRadius;
#override
void paint(Canvas canvas, Size size) {
final shapeBounds = Rect.fromLTRB(0, 0, size.width, size.height - avatarRadius);
final centerAvatar = Offset(shapeBounds.center.dx, shapeBounds.bottom);
final avatarBounds = Rect.fromCircle(center: centerAvatar, radius: avatarRadius).inflate(3);
_drawBackground(canvas, shapeBounds, avatarBounds);
}
#override
bool shouldRepaint(HeaderPainter oldDelegate) {
return color != oldDelegate.color;
}
void _drawBackground(Canvas canvas, Rect shapeBounds, Rect avatarBounds) {
final paint = Paint()..color = color;
final backgroundPath = Path()
..moveTo(shapeBounds.left, shapeBounds.top)
..lineTo(shapeBounds.bottomLeft.dx, shapeBounds.bottomLeft.dy)
..arcTo(avatarBounds, -pi, pi, false)
..lineTo(shapeBounds.bottomRight.dx, shapeBounds.bottomRight.dy)
..lineTo(shapeBounds.topRight.dx, shapeBounds.topRight.dy)
..lineTo(0.0, shapeBounds.height - 100)
..quadraticBezierTo(
shapeBounds.width / 4, shapeBounds.height,
shapeBounds.width / 2, shapeBounds.height
)
..quadraticBezierTo(
shapeBounds.width - shapeBounds.width / 4, shapeBounds.height,
shapeBounds.width, shapeBounds.height - 100
)
..lineTo(shapeBounds.width, 0.0)
..close();
canvas.drawPath(backgroundPath, paint);
}
}
And the outcome is
How can I get the bezier curve with the rectangle?
Edit 2: The HeaderPainter is used like
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
child: CustomPaint(
size: Size.fromHeight(400.0),
painter: HeaderPainter(
color: Colors.red,
avatarRadius: avatarRadius
),
),
),
Positioned(
left: 0,
right: 0,
bottom: titleBottomMargin,
child: Column(
children: <Widget>[
Text('Hello World', style: TextStyle(fontWeight: FontWeight.bold),),
],
),
),
Align(
alignment: Alignment.bottomCenter,
child: CircleAvatar(
radius: avatarRadius,
backgroundColor: Colors.green,
child: IconButton(icon: Icon(Icons.message), onPressed: _onAddMessageButtonClick,),
),
)
],
);
}
Got it solved using ClipPath and CustomClipper.
The updated build() method is
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
child: ClipPath(
clipper: HeaderClipper(avatarRadius: avatarRadius),
child: CustomPaint(
size: Size.fromHeight(400.0),
painter: HeaderPainter(
color: Colors.green,
avatarRadius: avatarRadius
),
),
),
),
...
and HeaderClipper
class HeaderClipper extends CustomClipper<Path> {
HeaderClipper({
#required this.avatarRadius
});
final avatarRadius;
#override
getClip(Size size) {
final path = Path()
..lineTo(0.0, size.height - 100)
..quadraticBezierTo(
size.width / 4, (size.height - avatarRadius),
size.width / 2, (size.height - avatarRadius)
)
..quadraticBezierTo(
size.width - (size.width / 4), (size.height - avatarRadius),
size.width, size.height - 100
)
..lineTo(size.width, 0.0)
..close();
return path;
}
#override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
The final output is

How to make the Bottom Navigation Bar overlay the main page in Flutter?

I'm building an app that has a Bottom Navigation Bar and I want to provide to this bar the ability to overlay the main page when I change its height in order to prevent the contents in the main page to be reduced to fit the available area. How can I do that?
I think that explaining it by images could be better, so there we go:
This is how the main page looks like together with the Bottom Navigation Bar:
Normal App:
When I click in the Yellow Circle Button (middle button), the BottomAppBar has to increase its height, but when I do this, the main content reduces its size to fit the available space.
Weird App:
I want to prevent it. How can I do it?
This is my code:
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double _appBarHeight = 60.0;
void _openBottomAppBar() {
setState(() {
if (_appBarHeight > 60) {
_appBarHeight = 60.0;
} else {
_appBarHeight = 300.0;
}
});
}
#override
Widget build(BuildContext context) {
final Shader textGradient = LinearGradient(
colors: <Color>[Theme.of(context).primaryColorDark, Theme.of(context).primaryColorLight],
).createShader(Rect.fromLTWH(0.0, 0.0, 200.0, 70.0));
return Scaffold(
body: Container(
child: Column(
children: <Widget>[
Expanded(
flex: 3,
child: ClipShadowPath(
clipper: ApplicationBar(),
shadow: Shadow(blurRadius: 10),
child: LayoutBuilder(builder: (context, constraints) {
return Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage(
'images/tiny-squares.png',
),
repeat: ImageRepeat.repeat,
),
),
),
Container(
decoration: BoxDecoration(
backgroundBlendMode: BlendMode.multiply,
gradient: LinearGradient(
begin: const Alignment(-1.0, 0.0),
end: const Alignment(0.6, 0.0),
colors: <Color>[Theme.of(context).primaryColorDark, Theme.of(context).primaryColorLight],
),
),
),
Positioned(
right: constraints.biggest.width * 0.1,
top: constraints.biggest.height * 0.35,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
),
child: Material(
elevation: 4.0,
clipBehavior: Clip.antiAliasWithSaveLayer,
type: MaterialType.circle,
color: Colors.transparent,
)),
Padding(
padding: EdgeInsets.only(top: 8),
),
],
))
],
);
})),
),
Expanded(
flex: 4,
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
padding: EdgeInsets.only(top: 16, left: 64),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 8),
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
),
Padding(
padding: EdgeInsets.only(top: 8),
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
),
),
Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
),
)
],
),
),
],
),
))
],
)),
],
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
onPressed: _openBottomAppBar,
tooltip: 'Increment',
child: Text('A'),
elevation: 10.0,
),
bottomNavigationBar: NotchedBottomBar(
centerItemText: '',
height: _appBarHeight,
color: Colors.grey,
selectedColor: Theme.of(context).accentColor,
notchedShape: FollowerNotchedShape(inverted: true),
items: [
NotchedBottomBarItem(iconData: Icons.home, text: 'This'),
NotchedBottomBarItem(iconData: Icons.payment, text: 'is'),
NotchedBottomBarItem(iconData: Icons.input, text: 'Bottom'),
NotchedBottomBarItem(iconData: Icons.settings_applications, text: 'Bar'),
],
),
);
}
}
class ApplicationBar extends CustomClipper<Path> {
#override
Path getClip(Size size) {
final path = Path();
path.lineTo(size.width, 0);
path.lineTo(size.width, 0.84 * size.height);
path.lineTo(0.72 * size.width, 0.983 * size.height);
final firstEndPoint = new Offset(0.7 * size.width, (0.986 + 0.0065) * size.height);
final controlPoint = new Offset(0.675 * size.width, 0.989 * size.height);
path.quadraticBezierTo(firstEndPoint.dx, firstEndPoint.dy, controlPoint.dx, controlPoint.dy);
path.lineTo(0, 0.87 * size.height);
path.close();
return path;
}
#override
bool shouldReclip(YEXApplicationBar oldClipper) {
return oldClipper != this;
}
}
Code for the Bottom Bar
import 'package:flutter/material.dart';
import 'dart:math' as math;
class FollowerNotchedShape extends CircularNotchedRectangle {
int _inverterMultiplier;
#override
Path getOuterPath(Rect host, Rect guest) {
if (!host.overlaps(guest)) return Path()..addRect(host);
final double notchRadius = guest.width / 2.0;
const double s1 = 15.0;
const double s2 = 1.0;
final double r = notchRadius;
final double a = -1.0 * r - s2;
final double b = host.top - guest.center.dy;
final double n2 = math.sqrt(b * b * r * r * (a * a + b * b - r * r));
final double p2xA = ((a * r * r) - n2) / (a * a + b * b);
final double p2xB = ((a * r * r) + n2) / (a * a + b * b);
final double p2yA = math.sqrt(r * r - p2xA * p2xA);
final double p2yB = math.sqrt(r * r - p2xB * p2xB);
final List<Offset> p = List<Offset>(6);
// p0, p1, and p2 are the control points for segment A.
p[0] = Offset(a - s1, b);
p[1] = Offset(a, b);
final double cmp = b < 0 ? -1.0 : 1.0;
p[2] =
cmp * p2yA > cmp * p2yB ? Offset(p2xA, _inverterMultiplier * p2yA) : Offset(p2xB, _inverterMultiplier * p2yB);
// p3, p4, and p5 are the control points for segment B, which is a mirror
// of segment A around the y axis.
p[3] = Offset(-1.0 * p[2].dx, p[2].dy);
p[4] = Offset(-1.0 * p[1].dx, p[1].dy);
p[5] = Offset(-1.0 * p[0].dx, p[0].dy);
// translate all points back to the absolute coordinate system.
for (int i = 0; i < p.length; i += 1) p[i] += guest.center;
final Path path = Path()
..moveTo(host.left, -host.top)
..lineTo(p[0].dx, -p[0].dy)
..quadraticBezierTo(p[1].dx, p[1].dy, p[2].dx, p[2].dy);
if (guest.height == guest.width) {
// circle
path.arcToPoint(
p[3],
radius: Radius.circular(notchRadius),
clockwise: _inverterMultiplier == 1 ? false : true,
);
} else {
// stadium
path
..arcToPoint(
(_inverterMultiplier == 1 ? guest.bottomLeft : guest.topLeft) + Offset(guest.height / 2, 0), // here
radius: Radius.circular(guest.height / 2),
clockwise: _inverterMultiplier == 1 ? false : true,
)
..lineTo(guest.right - guest.height / 2, (_inverterMultiplier == 1 ? guest.bottom : guest.top)) // here
..arcToPoint(
p[3],
radius: Radius.circular(guest.height / 2),
clockwise: _inverterMultiplier == 1 ? false : true,
);
}
path
..quadraticBezierTo(p[4].dx, p[4].dy, p[5].dx, p[5].dy)
..lineTo(host.right, host.top)
..lineTo(host.right, host.bottom)
..lineTo(host.left, host.bottom)
..close();
return path;
}
FollowerNotchedShape({inverted: false}) {
if (inverted) {
_inverterMultiplier = -1;
} else
_inverterMultiplier = 1;
}
}
class NotchedBottomBarItem {
NotchedBottomBarItem({this.iconData, this.text});
IconData iconData;
String text;
}
class NotchedBottomBar extends StatefulWidget {
NotchedBottomBar({
this.items,
this.centerItemText,
this.height: 60.0,
this.iconSize: 24.0,
this.backgroundColor,
this.color,
this.selectedColor,
this.notchedShape,
this.onTabSelected,
}) {
assert(this.items.length == 2 || this.items.length == 4);
}
final List<NotchedBottomBarItem> items;
final String centerItemText;
double height;
final double iconSize;
final Color backgroundColor;
final Color color;
final Color selectedColor;
final NotchedShape notchedShape;
final ValueChanged<int> onTabSelected;
#override
State<StatefulWidget> createState() => NotchedBottomBarState();
}
class NotchedBottomBarState extends State<NotchedBottomBar> {
int _selectedIndex = 0;
_updateIndex(int index) {
widget.onTabSelected(index);
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
List<Widget> items = List.generate(widget.items.length, (int index) {
return _buildTabItem(
item: widget.items[index],
index: index,
onPressed: _updateIndex,
);
});
items.insert(items.length >> 1, _buildMiddleTabItem());
return BottomAppBar(
shape: widget.notchedShape,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: items,
),
color: widget.backgroundColor,
);
}
Widget _buildMiddleTabItem() {
return Expanded(
child: SizedBox(
height: widget.height,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(height: widget.iconSize),
Text(
widget.centerItemText ?? '',
style: TextStyle(color: widget.color),
),
],
),
),
);
}
Widget _buildTabItem({
NotchedBottomBarItem item,
int index,
ValueChanged<int> onPressed,
}) {
Color color = _selectedIndex == index ? widget.selectedColor : widget.color;
return Expanded(
child: SizedBox(
height: widget.height,
child: Material(
type: MaterialType.transparency,
color: Colors.transparent,
child: InkWell(
onTap: () {
onPressed(index);
},
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 8),
),
Icon(item.iconData, color: color, size: widget.iconSize),
Padding(
padding: EdgeInsets.only(bottom: 4),
),
Text(
item.text,
style: TextStyle(color: color),
)
],
),
),
),
),
);
}
}
Any help?
use "extendBody: true" inside "Scaffold" class
Scaffold(
extendBody: true,
body: Container()