I am new to Flutter. I was trying to implement this UI 👇👇
The way I was implementing is
class SearchFlightBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(
left: 25,
right: 25,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RoundButton(
icon: Arrow_Forward.icon_ionic_md_arrow_round_back,
title: "ONE WAY",
iconSize: 10,
),
RoundButton(
icon: Two_Arrows.group_73,
title: "ROUND TRIP",
iconSize: 16,
),
],
),
Container(
// base Conatiner
margin: EdgeInsets.only(
top: 35,
),
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
offset: Offset(-10, -10),
blurRadius: 15,
color: Color(0xffFFFFFF)),
],
borderRadius: BorderRadius.circular(4),
color: Color(0xffF6F9FF),
),
child: ClipPath(
clipper: MyCustomClipper(),
child: Container(
//Subtraction Container
decoration: BoxDecoration(
// color: Color(0xffF6F9FF),
color: Colors.red,
border: Border.all(
color: Color(0x00000000),
),
boxShadow: [
BoxShadow(
offset: Offset(0, 5),
blurRadius: 10,
color: Color(0x0f140B2B),
),
]),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(
vertical: 15,
horizontal: 15,
),
child: Icon(
TakeOff.take_off,
size: 30,
color: Theme.of(context).accentColor,
),
),
Padding(
padding: const EdgeInsets.only(
top: 15,
bottom: 5,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'FORM',
style: Theme.of(context).textTheme.caption,
),
SizedBox(
height: 10,
),
Text(
'Sydney, Austraila',
),
],
),
)
],
),
),
),
),
// Container(
// margin: EdgeInsets.only(
// right: 25,
// ),
// transform: Matrix4.translationValues(-3.0, -20, 0.0),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(50),
// color: Colors.red,
// ),
// height: 50,
// width: 50,
// ),
// Container(
// height: 100,
// width: double.infinity,
// color: Colors.red,
// )
],
),
);
}
}
My Custom Clipper is
import 'dart:math';
import 'package:flutter/cupertino.dart';
class MyCustomClipper extends CustomClipper<Path> {
#override
getClip(Size size) {
Size newSize = Size(size.width, size.height - 40);
double radius = 40;
Offset center = Offset(6 * size.width / 7, size.height + 20);
Offset firstEnd = Offset(6 * size.width / 7, size.height - radius);
Offset secondEnd = Offset(6 * size.width / 7 + radius, size.height);
print('size: ' + size.toString());
// TODO: implement getClip
Path path = Path()
// ..lineTo(0, size.height)
// ..lineTo(0, size.height)
// ..arcToPoint(
// Offset(7 * size.width / 8, size.height - radius),
// radius: Radius.circular(radius),
// )
// ..arcToPoint(
// Offset(7 * size.width / 8 + radius, size.height),
// radius: Radius.circular(radius),
// )
// // ..arcTo(
// // Rect.fromCircle(
// // center: Offset(3 * size.width / 4, size.height),
// // radius: radius,
// // ),
// // 1 * pi,
// // 2,
// // true)
// ..lineTo(size.width, size.height)
..arcTo(
Rect.fromCircle(
center: center,
radius: radius,
),
1.3 * pi,
1.8 * pi,
true);
// ..lineTo(size.width, 0)
// ..lineTo(0, 0)
return path;
}
#override
bool shouldReclip(covariant CustomClipper oldClipper) {
// TODO: implement shouldReclip
return false;
}
}
As You can see, I was first clipping with ..artoPoint(). But That was just joining the two point with arc radius. That function is cutting a semicircle.
And I tried with ..arTo() function. But In that Function, I have to specify the start angle and sweep angle. But There is no way I can tell the value of both angle(Is there any way to know?).
I just want a function that will just draw a circle at a given center. And Just cut the portion of the container(in my case) and be done with it.
Or is There anyway I can do?
Related
I am trying to recreate this UI:
Collapsed
Expanded
I first created the custom container shape using a ClipPath with a CustomClipper:
class FolderClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
Path path = Path();
Offset c1 = Offset(size.width * .0825, size.height * .048);
Offset c2 = Offset(size.width * .021, size.height * .0075);
Offset end = Offset(size.width * .12, size.height * 0);
path.moveTo(0, size.height * 0.0775);
path.cubicTo(c1.dx, c1.dy, c2.dx, c2.dy, end.dx, end.dy);
path.lineTo(size.width * .71, end.dy);
Offset c1_2 = Offset(size.width * .829, size.height * .0002);
Offset c2_2 = Offset(size.width * .7497, size.height * .06495);
Offset end_2 = Offset(size.width * .8602, size.height * .0652);
path.cubicTo(c1_2.dx, c1_2.dy, c2_2.dx, c2_2.dy, end_2.dx, end_2.dy);
path.lineTo(size.width, end_2.dy);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
return path;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
This draws the shape as expected when it takes up the full screen:
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.only(top: 16.0),
child: ClipPath(
clipper: FolderClipper(),
child: Container(
height: MediaQuery.of(context).size.height,
decoration: const BoxDecoration(
color: Colors.grey,
),
child: const Center(
child: Text('Text'),
),
),
),
),
);
shape drawn with a large height
Approach 1
To put this shape into an expandable list, I used the containers with different content (child) in a CustomScrollView.
I had to use a collapsed and expanded container, which will basically be the same custom clipped container that I created earlier in the full screen, except the expanded one will have the grid of cards. I set a height when the container is in the collapsed state because otherwise it won't show if it has no children (and if I add a child that takes a large space, the collapsed item will take up a large height, which is not what I want). Setting that height however distorts the shape and so it doesn't look right (it only looks right when it has a relatively large height). I put the custom shape in a stack and added a container with a color that matches the preceding container's color under it so that it looks as if it's stacked.
Issues:
Given different heights, the custom shape becomes distorted and does not have the same proportions (the curves don't look the same for different heights).
There are lines that appear between widgets (same colored widgets that touch) that I think might be a bug in flutter (I'm using flutter 2.10.4 and it will be difficult to upgrade). I think these lines are due to the height and pixel density not quite matching and so the background can be seen.
[kinda worked] I tried to fix this by using Transform.translate with a y-axis offset of -8 on the background container that has the color of the preceding list item. This seems to work for the most part (since I don't care about the bottom of the container being shifted up). This however doesn't fix the elevation issue (issue #3).
I also tried to fix this by adding a similar CustomScrollView below the current one so that the background color would always match the expandable panel, but how will I know the size of the panel when it expands so that the scrollview with the background colors would match (I had their scroll controllers linked), but that wasn't the best and added complexity.
The elevation is not working on the last item (which is supposed to be an inverse-ish shape that I haven't done yet, I'm just reusing the shape I have to test it out). I added a border to try and make it more prominent, but that background line (between same colored widgets) is messing things up.
In general the shadow/elevation for each item in the list was not showing. I tried wrapping the container in a Material widget and gave it an elevation and I also tried using BoxShadow.
Here's what the code looks like after my fix attempts:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'folder_clipper.dart';
import 'folder_clipper2.dart';
class MyAttempt extends StatelessWidget {
MyAttempt({Key? key}) : super(key: key);
RxInt _selectedIndex = (-1).obs;
final List<Color> colors = [
Color(0xFFBDBDBD),
Colors.grey,
Color(0xFF757575)
];
final int optionsCount = 5;
final int gridOptionCount = 2;
ScrollController sc = ScrollController();
//ScrollController scBack = ScrollController();
#override
Widget build(BuildContext context) {
// sc.addListener(() {
// scBack.jumpTo(sc.offset);
// });
return Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Stack(
children: [
// CustomScrollView(
// controller: sc,
// slivers: [
// SliverList(
// delegate: SliverChildBuilderDelegate(
// (BuildContext context, int index) {
// return Container(
// decoration: BoxDecoration(
// color: index == 0
// ? Colors.transparent
// : colors[(index - 1) % colors.length],
// ),
// width: double.infinity,
// height: _selectedIndex.value == index
// ? null
// : (Get.height * .1),
// child: Obx(() => _selectedIndex.value == index
// ? gridView(index)
// : const SizedBox.shrink()),
// );
// },
// childCount: optionsCount,
// ),
// ),
// SliverFillRemaining(
// child: GestureDetector(
// onTap: () {
// if (_selectedIndex.value == 2)
// _selectedIndex.value = -1;
// else
// _selectedIndex.value = 2;
// },
// child: Stack(
// children: [
// Container(
// color: colors[(optionsCount - 1) % colors.length],
// width: double.infinity,
// ),
// ClipPath(
// clipper: FolderClipper2(),
// child: Material(
// elevation: 10,
// child: Container(
// decoration: BoxDecoration(
// color: colors[(optionsCount - 1) % colors.length],
// border: const Border(
// top:
// BorderSide(width: 1.0, color: Colors.black),
// ),
// ),
// ),
// ),
// ),
// ],
// ),
// ),
// )
// ],
// ),
CustomScrollView(
controller: sc,
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Stack(
children: [
Transform.translate(
offset: Offset(0, -8),
child: Container(
decoration: BoxDecoration(
color: index == 0
? Colors.transparent
: colors[(index - 1) % colors.length],
),
width: double.infinity,
height: ((Get.height * .1)),
),
),
ClipPath(
clipper: FolderClipper(),
child: Material(
child: GestureDetector(
onTap: () {
if (_selectedIndex.value == index) {
_selectedIndex.value = -1;
} else {
_selectedIndex.value = index;
}
},
child: Obx(
() => Stack(
children: [
Container(
decoration: BoxDecoration(
color: colors[index % colors.length],
),
height: _selectedIndex.value != index
? (Get.height * .1)
: null,
width: double.infinity,
child: Obx(() =>
_selectedIndex.value == index
? gridView(index)
: const SizedBox.shrink()),
),
Container(
margin: EdgeInsets.only(
top: Get.height * .025,
left: Get.width * .125),
child: Text(
"Option $index",
style: const TextStyle(
fontWeight: FontWeight.bold),
),
),
],
),
),
),
),
),
],
);
},
childCount: optionsCount,
),
),
SliverFillRemaining(
child: Stack(
children: [
Transform.translate(
offset: Offset(0, -8),
child: Container(
color: colors[(optionsCount - 1) % colors.length],
width: double.infinity,
),
),
ClipPath(
clipper: FolderClipper2(),
child: Material(
elevation: 10,
child: Container(
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: Colors.black,
spreadRadius: 5,
blurRadius: 5,
offset:
Offset(0, -8), // changes position of shadow
),
],
color: colors[(optionsCount - 1) % colors.length],
border: const Border(
top: BorderSide(width: 1.0, color: Colors.black),
),
),
),
),
),
],
),
)
],
),
],
),
);
}
gridView(index) {
return GridView.count(
shrinkWrap: true,
primary: true,
padding: EdgeInsets.only(
left: Get.width * .15,
right: Get.width * .15,
top: Get.width * .15,
bottom: 16),
crossAxisSpacing: Get.width * .075, //24,
mainAxisSpacing: Get.width * .075, //16,
crossAxisCount: 2,
children: <Widget>[
for (int i = 0; i < (gridOptionCount * index); i++)
Card(
elevation: 10,
child: Container(
//width: Get.width * .05,
//height: Get.width * .05,
color: Colors.white,
child: Center(
child: Container(
child: Text(
"option $i",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
)),
),
],
);
}
}
and here is what it looks like, it's kind of working, but doesn't look right mainly due to the CustomClipper being distorted due to the height of the container and the shadows not working as expected:
collapsed state
expanded state
Approach 2
To put this shape into an expandable list, I used the expandable package on pub.dev in a CustomScrollView.
I set a header and expanded widget, which will basically be the same custom clipped container that I created, except the expanded one will have the grid of cards. I set a height when the container is in the header only state because otherwise it will be so short if it has no children (and if I add a child that takes a large space, the collapsed item will take up a large height, which is not what I want). Setting that height however distorts the shape and so it doesn't look right (it only looks right when it has a relatively large height). I put the custom shape in a stack and added a container with a color that matches the preceding container's color under it so that it looks as if it's stacked. I did not use the collapsed widget (set it to SizedBox.shrink).
Here's the code:
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'folder_clipper.dart';
import 'folder_clipper2.dart';
class MyAttempt2 extends StatelessWidget {
MyAttempt2({Key? key}) : super(key: key);
RxInt _selectedIndex = (-1).obs;
final List<Color> colors = [
Color(0xFFBDBDBD),
Colors.grey,
Color(0xFF757575)
];
final int optionsCount = 5;
final int gridOptionCount = 2;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 16.0),
child: CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Stack(
children: [
ExpandableNotifier(
child: Stack(
children: [
Transform.translate(
offset: Offset(0, -8),
child: Container(
decoration: BoxDecoration(
color: index == 0
? Colors.transparent
: colors[(index - 1) % colors.length],
),
width: double.infinity,
height: ((Get.height * .1)),
),
),
ClipPath(
clipper: FolderClipper(),
child: Container(
//height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
color: colors[index % colors.length],
),
child: ScrollOnExpand(
scrollOnExpand: true,
scrollOnCollapse: false,
child: ExpandablePanel(
theme: const ExpandableThemeData(
headerAlignment:
ExpandablePanelHeaderAlignment.center,
tapBodyToCollapse: true,
),
header: Container(
height: ((Get.height * .1)),
child: Padding(
padding: EdgeInsets.all(10),
child: Text(
"ExpandablePanel",
style: Theme.of(context)
.textTheme
.bodyText2,
)),
),
collapsed: SizedBox.shrink(),
// Text(
// loremIpsum,
// softWrap: true,
// maxLines: 2,
// overflow: TextOverflow.ellipsis,
// ),
expanded: SingleChildScrollView(
child: gridView(index)),
builder: (_, collapsed, expanded) {
return Padding(
padding: EdgeInsets.only(
left: 0, right: 0, bottom: 0),
child: Expandable(
collapsed: collapsed,
expanded: expanded,
theme: const ExpandableThemeData(
crossFadePoint: 0),
),
);
},
),
),
),
//margin: EdgeInsets.zero,
//clipBehavior: Clip.antiAlias,
),
],
),
),
],
);
},
childCount: optionsCount,
),
),
SliverFillRemaining(
child: Stack(
children: [
Container(
color: colors[(optionsCount - 1) % colors.length],
width: double.infinity,
),
ClipPath(
clipper: FolderClipper2(),
child: Material(
elevation: 10,
child: Container(
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: Colors.black,
spreadRadius: 5,
blurRadius: 5,
offset: Offset(0, -8), // changes position of shadow
),
],
color: colors[(optionsCount - 1) % colors.length],
border: const Border(
top: BorderSide(width: 1.0, color: Colors.black),
),
),
),
),
),
],
),
)
],
),
);
}
gridView(index) {
return GridView.count(
shrinkWrap: true,
primary: true,
padding: EdgeInsets.only(
left: Get.width * .15,
right: Get.width * .15,
top: Get.width * .15,
bottom: 16),
crossAxisSpacing: Get.width * .075, //24,
mainAxisSpacing: Get.width * .075, //16,
crossAxisCount: 2,
children: <Widget>[
for (int i = 0; i < (gridOptionCount * index); i++)
Card(
elevation: 10,
child: Container(
//width: Get.width * .05,
//height: Get.width * .05,
color: Colors.white,
child: Center(
child: Container(
child: Text(
"option $i",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
)),
),
],
);
}
}
and this is what it looks like:
collapsed (header only) state
expanded state
Similar issues as approach 1.
UPDATE
I managed to add a height factor to the CustomClipper so that all offsets are relative to the height I based it off of and that seemed to get the shape to be consistent! Will probably do the same for the width.
Here's the new code:
class FolderClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
print("${size.width} - ${size.height}");
double heightFactor = size.height / 667;
double widthFactor = size.width / 375;
Path path = Path();
Offset c1 = Offset(size.width * .0825, size.height * .048);
Offset c2 = Offset(size.width * .021, size.height * .0075);
Offset end = Offset(size.width * .12, size.height * 0);
// if (size.height < 100)
// path.moveTo(0, size.height * .5);
// else if (size.height < 300)
// path.moveTo(0, size.height * .15);
// else
path.moveTo(0, size.height * 0.0775 / heightFactor);
path.cubicTo(c1.dx, c1.dy / heightFactor, c2.dx, c2.dy / heightFactor,
end.dx, end.dy / heightFactor);
path.lineTo(size.width * .71, end.dy / heightFactor);
Offset c1_2 = Offset(size.width * .829, size.height * .0002);
Offset c2_2 = Offset(size.width * .7497, size.height * .06495);
Offset end_2;
// if (size.height < 100)
// end_2 = Offset(size.width * .8602, size.height * .45);
// else if (size.height < 300)
// end_2 = Offset(size.width * .8602, size.height * .15);
// else
end_2 = Offset(size.width * .8602, size.height * .0652);
path.cubicTo(c1_2.dx, c1_2.dy / heightFactor, c2_2.dx,
c2_2.dy / heightFactor, end_2.dx, end_2.dy / heightFactor);
path.lineTo(size.width, end_2.dy / heightFactor);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
return path;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
This is what it looks like now:
expanded state - issue with SliverFillRemaining line on top and shadows
So I believe the main issue remaining here is with the shadow and the space between the last item and the SliverFillRemainingSection (and its shadow as well)
I am currently developing my first flutter-application and I need some help with my onboarding screen.
The top part has a Lottiefile asset in a container. The bottom part has some Buttons.
Is it possible to set the size of the container with the Lottiefile asset in it depending of the size of the bottom part? I do not want to have a scroll view, so everything should fit it one screen. The Lottiefile asset should fill the space between the top and the beginning of the bottom part. Is there any possibility to set the size depending of the width and height of the free space?
I tried to set height & width to double.infinity but that crashed my app.
Here's the code:
class OnBoardingPage extends StatelessWidget {
const OnBoardingPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
toolbarHeight: 0,
elevation: 0,
backgroundColor: AppColor.statusbarColor,
),
backgroundColor: AppColor.statusbarColor,
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // <-- spaceBetween
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: size.width * 0.08),
child: Column(
children: [
SizedBox(
height: size.height * 0.03,
),
Container(
width: double.infinity,
child: Lottie.asset('assets/images/files.json',
fit: BoxFit.cover, repeat: false),
),
],
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: size.width * 0.08),
child: Column(
children: [
SizedBox(
height: size.height * 0.05,
),
Container(
height: size.height * 0.012,
width: size.width * 0.14,
decoration: BoxDecoration(
color: AppColor.textColor,
borderRadius: BorderRadius.circular(15)),
),
SizedBox(
height: size.height * 0.03,
),
AppText(
title: "...",
color: AppColor.textColor,
size: size.height * 0.024,
),
SizedBox(
height: size.height * 0.015,
),
AppText(
title:
"...",
textAlign: TextAlign.center,
color: AppColor.textColor,
size: size.height * 0.018,
),
SizedBox(
height: size.height * 0.05,
),
AppButton(
buttonName: "Register",
buttonColor: AppColor.buttonColor,
textColor: AppColor.buttonTextColor,
onTap: () {
Get.to(const SignUp());
},
buttonRadius: BorderRadius.circular(15),
buttonWidth: size.width,
fontWeight: FontWeight.w500,
buttonHeight: size.height * 0.065,
),
SizedBox(
height: size.height * 0.02,
),
AppButton(
buttonName: "Login",
buttonColor: AppColor.buttonColor,
textColor: AppColor.buttonTextColor,
onTap: () {
Get.to(const LoginScreen());
},
buttonRadius: BorderRadius.circular(15),
buttonWidth: size.width,
fontWeight: FontWeight.w500,
buttonHeight: size.height * 0.065,
),
SizedBox(
height: size.height * 0.02,
),
InkWell(
onTap: () {
Get.to(const Guest());
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AppText(
title: "Continue as a Guest (...)",
color: AppColor.primaryColor,
size: size.height * 0.02,
),
SizedBox(
height: size.height * 0.014,
),
],
),
),
SizedBox(
height: size.height * 0.05,
),
],
),
),
],
));
}
}
I hope somebody is can help me with that problem. Thank you very much.
Did you try wrapping your Column or Container in an Expanded widget?
I was trying to add a border shadow effect to a Container in a way that resembles exactly the same design as in the picture below. My initial idea was of using the CustomPaint class feature to do so. However, it hasn't worked out the way I wanted it to. I would honestly like to know how this can be achieved through the use of the CustomPaint class and I apologize for the code that I've written as I'm still trying to get used to CustomPaint. The code and the pictures are as follows:
This is what I intend to achieve:
This is what I have. You can see that the border goes well beyond where it should be and also, the border at the bottom gets clipped and despite adjusting the bottom padding:
This is the code:
class ProfilePageState extends State<ProfilePage> {
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
overlays: [SystemUiOverlay.bottom]);
final height = MediaQuery.of(context).size.height;
final width = MediaQuery.of(context).size.width;
final textScale = MediaQuery.of(context).textScaleFactor * 1.2;
// TODO: implement build
return Scaffold(
body: ListView(
children: [
Container(
height: height * 1,
width: width * 1,
// color: Colors.red,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/filter.png'),
fit: BoxFit.cover)),
child: Column(
......
Padding(
padding: EdgeInsets.only(left: width * 0.035),
child: Container(
width: double.infinity,
height: height * 0.25,
padding: EdgeInsets.only(
left: width * 0.03,
top: height * 0.01,
// bottom: height * 0.01
),
// color: Colors.yellow,
child: ProfilePageFavourite(), //This here is the widget
),
),
],
),
),
],
),
);
}
}
The ProfilePageFavourite widget:
class ProfilePageFavouriteState extends State<ProfilePageFavourite> {
// final List<dynamic> _favouritesList = [
// ];
var favourite = false;
void _onPressed() {
setState(() => favourite = !favourite);
print(favourite);
}
#override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
final height = MediaQuery.of(context).size.height;
final textScale = MediaQuery.of(context).textScaleFactor * 1.2;
// TODO: implement build
return ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) => Stack(
children: [
CustomPaint( //This is where I try using the CustomPaint class
painter: OrangePainter(),
child: Container(
width: width * 0.75,
margin: EdgeInsets.only(right: width * 0.09),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.yellow,
image: DecorationImage(
fit: BoxFit.fitWidth,
image: AssetImage('assets/images/pub screen 1 (1).png'))),
),
),
Positioned(
top: height * 0.16,
child: Container(
height: height * 0.07,
width: width * 0.75,
padding: EdgeInsets.only(top: height * 0.008),
decoration: BoxDecoration(
color: Colors.black38,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20))),
child: Column(
children: [
Row(
// mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.only(left: width * 0.05),
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: Colors.red,
blurRadius: 35,
spreadRadius: 8,
offset: Offset(0, 2))
]),
child: Image.asset(
'assets/icons/dine.png',
color: Colors.white,
),
),
SizedBox(
width: width * 0.02,
),
Text(
'Sollicitudin',
textScaleFactor: textScale,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 20,
shadows: [
Shadow(
color: Colors.red,
blurRadius: 10,
offset: Offset(0, 2))
]),
)
],
),
Container(
width: double.infinity,
// color: Colors.red,
padding: EdgeInsets.only(left: width * 0.118),
child: CustomRatingBar(4.5))
],
),
),
),
Positioned(
left: width * 0.58,
top: height * 0.01,
child: Container(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: Colors.pink,
blurRadius: 80,
spreadRadius: 6,
)
]),
child: !favourite
? IconButton(
onPressed: _onPressed,
icon: Icon(
Icons.favorite,
// color: Color.fromRGBO(247, 180, 230, 0.8),
color: Colors.pink,
size: 50,
))
: IconButton(
onPressed: _onPressed,
icon: Icon(
Icons.favorite_border_outlined,
color: Colors.pink,
size: 50,
)),
),
),
],
),
itemCount: 5,
);
}
}
The CustomPaint Class
class OrangePainter extends CustomPainter {
OrangePainter();
#override
void paint(Canvas canvas, Size size) {
final rrectBorder =
RRect.fromRectAndRadius(Offset.zero & size, Radius.circular(12));
final rrectShadow =
RRect.fromRectAndRadius(Offset(0, 2) & size, Radius.circular(12));
final shadowPaint = Paint()
..color = Colors.lightGreen
..style = PaintingStyle.stroke
..strokeWidth = 3
..maskFilter = MaskFilter.blur(BlurStyle.solid, 20);
canvas.drawRRect(rrectShadow, shadowPaint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
I would like to suggest an alternative approach, Custom Paint is very expensive to use in applications. You could instead wrap your container in a Card() then add your shadowColor: Colors.lightGreen set the **shape: ** to the same as your Container() and you should get the result you desire
I have retained the initial customPaint and used it here.This code combines your ProfilePageState and ProfilePageFavourite to give you the appearance below. I've just built it on my phone's dartpad so you can do the editing so it matches. I included comments:
#override
Widget build(BuildContext context) {
return Scaffold(
body: SizedBox(
height: 250,
width: double.infinity,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 5,
separatorBuilder: (ctx, index) => const SizedBox(width: 10),
itemBuilder: (ctx, index) => ClipRRect(
child: CustomPaint(
painter: OrangePainter(),
child: Container(
padding: const EdgeInsets.symmetric(horizontal:10,vertical:15),//increase padding to your liking
width: 350,
decoration: BoxDecoration(
// image: DecorationImage(image: AssetImage('')), add your image here and remove color below
color: Colors.black54,
borderRadius: BorderRadius.circular(10),
),
child: Column(
mainAxisAlignment:MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: const [
Icon(Icons.favorite, size: 40),
],
),
Row(
children: [
const Icon(Icons.restaurant),
const SizedBox(width:10),
Column(
children: const [
Text(
'Sollicitudin',
// textScaleFactor: textScale,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 20,
shadows: [
Shadow(
color: Colors.red,
blurRadius: 10,
offset: Offset(0, 2))
],
),
),
Icon(Icons.star),//Replace with your CustomRation()
],
),
],
),
],
),
),
),
),
),
),
);
}
}
class OrangePainter extends CustomPainter {
OrangePainter();
#override
void paint(Canvas canvas, Size size) {
final rrectBorder =
RRect.fromRectAndRadius(Offset.zero & size, const Radius.circular(12));
final rrectShadow =
RRect.fromRectAndRadius(const Offset(0, 2) & size, const Radius.circular(12));
final shadowPaint = Paint()
..color = Colors.lightGreen
..style = PaintingStyle.stroke
..strokeWidth = 3
..maskFilter = MaskFilter.blur(BlurStyle.solid, 20);
canvas.drawRRect(rrectShadow, shadowPaint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
I built a layout but I know this is not good way to create this layout.
so how to get the best approach to create this layout with dynamic and alignment of middle widget should in center .
CODE:
class StartGroupChatScreen extends StatelessWidget {
double startPoint = 30;
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Row(
children: [
IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: MyTheme.secondryColor,
),
onPressed: () {},
),
Text(
"GROUP CHAT",
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
color: MyTheme.secondryColor),
),
],
)),
body: Container(
color: MyTheme.grey800,
child: Stack(
children: [
InkWell(
child: Align(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: size.height * 0.14,
alignment: Alignment.center,
child: Stack(
children: [
Positioned(
left: startPoint,
child: UserAvatar(
imageUrl:
"https://wallpaperaccess.com/full/3957694.jpg",
avatarRadius: size.height * 0.12),
),
Positioned(
left: startPoint + 30,
child: UserAvatar(
imageUrl:
"https://m.media-amazon.com/images/M/MV5BZDA1ODgyODEtOWI3Yy00N2UzLTk5ZGMtZGI1MzU5YzFkZDQ1XkEyXkFqcGdeQXVyMTc4MzI2NQ##._V1_UY1200_CR285,0,630,1200_AL_.jpg",
avatarRadius: size.height * 0.12),
),
Positioned(
left: startPoint + 60,
child: UserAvatar(
imageUrl:
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQhS1n9AgNPFrsPjj0fHqwPdIJPJLl9hXUq5Q&usqp=CAU",
avatarRadius: size.height * 0.12),
),
Positioned(
left: startPoint + 90,
child: UserAvatar(
imageUrl:
"https://img.mensxp.com/media/content/2020/Aug/Michele-Morrone-From-365-Days-Floored-Us-With-His-Fashion-Game-1200x900_5f2a761253b66_1200x900.jpeg",
avatarRadius: size.height * 0.12),
),
Positioned(
left: startPoint + 120,
child: UserAvatar(
imageUrl:
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS44UpSZW-GZwDVxU3763H9HPOWqdB6ThRAoQ&usqp=CAU",
avatarRadius: size.height * 0.12),
),
Positioned(
left: startPoint + 150,
child: UserAvatar(
imageUrl:
"https://img1.nickiswift.com/img/gallery/the-untold-truth-of-michele-morrone-from-365-days/intro-1593017194.jpg",
avatarRadius: size.height * 0.12),
),
Positioned(
left: startPoint + 180,
child: UserAvatar(
imageUrl:
"https://images2.minutemediacdn.com/image/upload/c_fill,w_720,ar_16:9,f_auto,q_auto,g_auto/shape/cover/sport/Bar-Giuseppe-Red-Carpet---14th-Rome-Film-Fest-2019-7de8f118ccdbce430f2d706463c8e09c.jpg",
avatarRadius: size.height * 0.12),
),
Positioned(
left: startPoint + 210,
child: UserAvatar(
imageUrl:
"https://stat2.bollywoodhungama.in/wp-content/uploads/2021/04/Netflixs-365-Days-breakout-star-Michele-Morrone-looks-sharp-on-the-cover-of-Elle-India-..jpg",
avatarRadius: size.height * 0.12),
),
],
),
),
SizedBox(
height: size.height * 0.02,
),
Text(
"START CHAT",
style: TextStyle(
color: MyTheme.white,
fontWeight: FontWeight.bold,
fontSize: 28.0),
),
],
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: InkWell(
onTap: () {},
child: Container(
height: size.height * 0.08,
alignment: Alignment.center,
decoration: BoxDecoration(
color: MyTheme.primaryColor,
),
child: Text(
"CONTINUE",
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 17.0),
),
),
),
)
],
),
),
);
}
}
USER AVATER:
class UserAvatar extends StatelessWidget {
final String imageUrl;
final double avatarRadius;
const UserAvatar({Key key,#required this.imageUrl , #required this.avatarRadius}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: avatarRadius,
width: avatarRadius,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(imageUrl),
fit: BoxFit.cover
)
),
);
}
}
OUTPUT:
dartPad
Actually it was good 👍
But it seems repetitive, i think you should add another field properties like the position of image and also avatar radius because its have same repetitive const value
UserAvatar(
imageUrl: "https://wallpaperaccess.com/full/3957694.jpg",
imageNo : 1,
),
),
So the widget should be like this
class UserAvatar extends StatelessWidget {
final String imageUrl;
final double avatarRadius;
final int imageNo;
const UserAvatar({Key key,#required this.imageUrl , #required this.avatarRadius}) : super(key: key);
#override
Widget build(BuildContext context) {
double startPoint = 30;
final size = MediaQuery.of(context).size;
return Positioned(
left: startPoint + (30 * imageNo),
Container(
height: size.height * 0.12,
width: size.height * 0.12,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(imageUrl),
fit: BoxFit.cover
)
),
),
);
}
}
And moreover actually you can also move the body for the show avatar
from line 37 ~ 100 because it is constant value
I'm trying to making tile like below image.
This is the piece of code that I have tried:
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ListTile(
leading: Container(
alignment: Alignment.center,
height: MediaQuery.of(context).size.height/20,
width: MediaQuery.of(context).size.height/20,
decoration: BoxDecoration(
color: index.isEven?Colors.yellow:Colors.orange,
shape: BoxShape.circle,
image: snapshot.data.content[index].image != null? DecorationImage(
image: NetworkImage(snapshot.data.content[index].image.fileUrl),
fit: BoxFit.cover
):null
),
child: snapshot.data.content[index].image == null?
Icon(Icons.person):Container()
),
title:Text(
snapshot.data.content[index].name, style: TextStyle(
fontWeight: FontWeight.bold)
),
subtitle: Text(snapshot.data.content[index].phoneNumber),
trailing: Container(
alignment: Alignment.centerRight,
width: MediaQuery.of(context).size.width/5.5,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset('assets/icons/reminder.png',fit: BoxFit.cover,
),
Image.asset('assets/icons/calendar_soon.png',fit: BoxFit.cover,),
],
),
),
onTap: (){
//
},
),
Container(
alignment: Alignment.center,
height: MediaQuery.of(context).size.height/25,
width: MediaQuery.of(context).size.width/1.5,
decoration: BoxDecoration(
color: Colors.green[100],
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
topLeft: Radius.circular(10),
bottomRight: Radius.circular(30),
)
),
child: Text("12 Mar, Marriage Anniversary",style:TextStyle(color: Colors.green)),
),
SizedBox(height:5),
Divider(height: 0.5,)
],
)
and What I have got from this code:
This is my attempt using a CustomPainter:
class CurvePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final radius = 16.0;
final tipHeight = size.height * 0.2;
final paint = Paint()..color = const Color(0xFFDEF8EB);
final path = Path()
..moveTo(0, tipHeight)
..lineTo(0, size.height - radius)
..quadraticBezierTo(0, size.height, radius, size.height)
..lineTo(size.width - radius, size.height)
..quadraticBezierTo(
size.width, size.height, size.width, size.height - radius)
..lineTo(size.width, tipHeight + radius)
..quadraticBezierTo(size.width, 0, size.width - radius, 0)
..quadraticBezierTo(size.width - radius, tipHeight,
size.width - (radius + 8.0), tipHeight)
..lineTo(radius, tipHeight)
..quadraticBezierTo(0, tipHeight, 0, tipHeight + radius);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
To implement it, you need to use the CustomPaint widget:
Container(
height: 50.0,
width: 300,
child: CustomPaint(
painter: CurvePainter(),
),
),