Flutter: Making clipped area transparent for scrolling ListView - flutter

I have a ListView that I would like to "disappear" when it hits the clipping of another widget.
Here is my code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TopWidget(),
Expanded(
child: ListView(
itemExtent: 100,
children: <Widget>[
Card(color: Colors.green,),
],
),
),
],
),
);
}
}
class TopWidget extends StatelessWidget {
TopWidget();
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: ShadowPainter(),
child: ClipPath(
clipper: TopWidgetClipper(),
child: Container(
height: 370,
color: Colors.blue,
),
),
);
}
}
class TopWidgetClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
Offset controllPoint1 = Offset(0, size.height - 100);
Offset endPoint1 = Offset(100, size.height - 100);
Offset controllPoint2 = Offset(size.width, size.height - 100);
Offset endPoint2 = Offset(size.width, size.height - 200);
Path path = Path()
..lineTo(0, size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(size.width - 100, size.height - 100)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(size.width, 0);
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return true;
}
}
class ShadowPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Offset controllPoint1 = Offset(0, size.height - 100);
Offset endPoint1 = Offset(100, size.height - 100);
Offset controllPoint2 = Offset(size.width, size.height - 100);
Offset endPoint2 = Offset(size.width, size.height - 200);
Path path = Path()
..lineTo(0, size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(size.width - 100, size.height - 100)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(size.width, 0);
canvas.drawShadow(path, Colors.grey[50], 3.0, false);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
So far, when I am scrolling down, the list (green box) goes unseen when it reaches the bottom of the container (yellow border) of my clipped TopWidget. But I would like the list to smoothly disappear only when it reaches the edges of my clipping (i.e. the blue area - like in the second screenshot).
Any ideas how I could accomplish this? Thank you!

As I have learnt it from #pskink (thanks for that) in use cases like this where you need the widget to actually adjust its boundaries (spoiler: shape) you should make use of the shape property of different widgets and use the Path you used for this example in a custom class which extends ShapeBorder. Easiest approach would be:
Container(
height: 370,
decoration: ShapeDecoration(
color: Colors.blue,
shape: AppBarBorder(),
/// You can also specify some neat shadows to cast on widgets scrolling under this one
shadows: [
BoxShadow(
color: Colors.black.withOpacity(0.7),
blurRadius: 18.0,
spreadRadius: 2.0,
),
],
),
),
And the custom class:
class AppBarBorder extends ShapeBorder {
#override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
Offset controllPoint1 = Offset(0, rect.size.height - 100);
Offset endPoint1 = Offset(100, rect.size.height - 100);
Offset controllPoint2 = Offset(rect.size.width, rect.size.height - 100);
Offset endPoint2 = Offset(rect.size.width, rect.size.height - 200);
return Path()
..lineTo(0, rect.size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(rect.size.width - 100, rect.size.height - 100)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(rect.size.width, 0);
}
#override
EdgeInsetsGeometry get dimensions => EdgeInsets.only(bottom: 0);
#override
Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;
#override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
#override
ShapeBorder scale(double t) => this;
}
Pretty much the same approach how you would declare a CustomClipper or CustomPainter since you don't need to implement most of those methods and essentially only need to care about getOuterPath.
At the end we need to restructure the layout itself, since currently you have a Column with this custom Container shape and the ListView beneath that. Since the Container is not part of the ListView it can't be scrolled under or something. Easiest approach would be using a Stack:
Stack(
children: [
Expanded(
child: ListView(
padding: EdgeInsets.only(top: 370.0),
itemExtent: 100,
children: <Widget>[
Card(
color: Colors.green,
),
],
),
),
Container(
height: 370,
decoration: ShapeDecoration(
color: Colors.blue,
shape: AppBarBorder(),
shadows: [
BoxShadow(
color: Colors.black.withOpacity(0.7),
blurRadius: 18.0,
spreadRadius: 2.0,
),
],
),
),
],
),

Related

How to make a cutout in a container using ClipPath?

I have a page on which the container is located, I need to make borders and a cutout for the buttons in this container. I want to do this using ClipPath when I added this widget, nothing has changed, there are no borders and there is no vyzer that I registered. Tell me why nothing is displayed, what is my mistake?
#override
Widget build(BuildContext context) {
return ClipPath(
clipper: CustomClipperArc(position: 150, holeRadius: 32),
child: Container(
decoration: const BoxDecoration(
color: constants.Colors.greyXDark,
),
padding: const EdgeInsets.only(top: 50),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
constants.Assets.likeBoarding,
height: 50,
),
],
),
],
),
),
);
}
CustomClipperArc
class CustomClipperArc extends CustomClipper<Path> {
CustomClipperArc({this.position, this.holeRadius = 16});
double? position;
final double holeRadius;
#override
Path getClip(Size size) {
final path = Path()
..moveTo(0, 0)
..lineTo(position! - holeRadius, 0.0)
..lineTo(size.width, 0.0)
..lineTo(size.width, size.height)
..lineTo(position!, size.height)
..arcToPoint(
Offset(position! - holeRadius, size.height),
clockwise: false,
radius: const Radius.circular(1),
);
path.lineTo(0.0, size.height);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper oldClipper) => oldClipper != this;
}
Here is your Clip Code... and also use Shape Maker to design such layout and you will get clip code
Your clip container with border
Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(
children: <Widget>[
ClipPath(
clipper: CustomClipPathTopContainer(),
child: Container(
height: 400,
color: Colors.green,
),
),
CustomPaint(
painter: BorderPainter(),
child: Container(
height: 400,
),
),
],
),
),
Clipping Code of Container
import 'package:flutter/cupertino.dart';
class CustomClipPathTopContainer extends CustomClipper<Path> {
#override
Path getClip(Size size) {
double w = size.width;
double h = size.height;
Path path0 = Path();
path0.moveTo(0,size.height);
path0.lineTo(0,0);
path0.lineTo(size.width,0);
path0.lineTo(size.width,size.height);
path0.lineTo(size.width*0.3333333,size.height);
path0.lineTo(size.width*0.3014833,size.height*0.9182714);
path0.quadraticBezierTo(size.width*0.2795000,size.height*0.8617714,size.width*0.2488333,size.height*0.8552714);
path0.quadraticBezierTo(size.width*0.2145667,size.height*0.8643571,size.width*0.1948583,size.height*0.9179714);
path0.lineTo(size.width*0.1653417,size.height);
path0.lineTo(0,size.height);
path0.lineTo(0,size.height);
path0.lineTo(0,size.height);
path0.close();
return path0;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
Making Border Painting
class BorderPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2
..color = Colors.pink;
Path path0 = Path();
path0.moveTo(0,size.height);
path0.lineTo(0,size.height*0.2114714);
path0.quadraticBezierTo(size.width*0.1255833,size.height*0.4277857,size.width*0.2698833,size.height*0.4275143);
path0.cubicTo(size.width*0.4168083,size.height*0.4270286,size.width*0.5467250,size.height*0.0190429,size.width*0.6867167,size.height*0.0189000);
path0.quadraticBezierTo(size.width*0.8293000,size.height*0.0211000,size.width,size.height*0.2121429);
path0.lineTo(size.width,size.height);
path0.lineTo(0,size.height);
path0.lineTo(0,size.height);
path0.lineTo(0,size.height*0.9994143);
path0.close();
canvas.drawPath(path0, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}

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

Flutter: Remove space between widgets

By using CustomPainter i have created a half circle.Now i have this widget tree:
return Scaffold(
body: Center(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
// card view
height: 100,
alignment: Alignment.center,
margin: EdgeInsets.only(
top: 20.0, bottom: 0.0, left: 50.0, right: 50.0),
decoration: BoxDecoration(
boxShadow: ([
BoxShadow(
color: color_transparent_black,
spreadRadius: 5,
blurRadius: 3.0,
offset: Offset(2, 3),
),
]),
borderRadius: BorderRadius.circular(14.0),
),),
MyArc(diameter: 200),
This is MyArc:
class MyArc extends StatelessWidget {
final double diameter;
const MyArc({Key key, this.diameter = 200}) : super(key: key);
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: MyPainter(),
size: Size(diameter, diameter),
);
}
}
// This is the Painter class
class MyPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()..color = Colors.blue;
canvas.drawArc(
Rect.fromCenter(
center: Offset(size.height / 2, 0),
height: size.height,
width: size.width,
),
6.4,
2.9,
false,
paint,
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
But i got this problem:
I don't want any space between gray and blue shape!!!
I finally figured it out...
import 'dart:math' as math;
//...
class MyPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Paint blue = Paint()..color = Colors.blue;
canvas.translate(0, -size.height / 2);
canvas.drawArc(
Offset.zero & size,
-math.pi,
-math.pi,
false,
blue,
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
If you want to make your canvas fit nicely also you can make it rectangular like so...
CustomPaint(
painter: MyPainter(),
size: Size(diamieter, diameter / 2),
)
Then have you custom painter as...
class MyPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Paint blue = Paint()..color = Colors.blue;
Paint red = Paint()..color = Colors.red;
canvas.drawRect(Offset.zero & size, red);
canvas.translate(0, -size.height);
canvas.drawArc(
Offset.zero & Size(size.width, size.height * 2),
-math.pi,
-math.pi,
false,
blue,
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
It'll fit snug this way...
try changing the spreadRadius to 3.0
Its possible that it's creating a boundary greater than the container's Blur Radius boundary.
BoxShadow(
color: color_transparent_black,
spreadRadius: 5,
blurRadius: 3.0,
offset: Offset(2, 3),
),

How to make a container with inverted rounded corners in Flutter?

This is the way to make a container with rounded corners:
Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(10),),
But is there a way to make inverted rounded corners like in the picture below? I couldn't find anything online
as an option (based on #pskink tip)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: Scaffold(appBar: AppBar(), body: Demo()));
}
}
class Demo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
Container(
width: 300,
height: 200,
margin: const EdgeInsets.only(top: 24, bottom: 16),
decoration: ShapeDecoration(shape: WeirdBorder(radius: 32), color: Colors.red),
),
Container(
width: 200,
height: 100,
margin: const EdgeInsets.only(bottom: 16),
decoration: ShapeDecoration(shape: WeirdBorder(radius: 16, pathWidth: 8), color: Colors.green),
),
Container(
width: 300,
height: 200,
decoration: ShapeDecoration(shape: WeirdBorder(radius: 12, pathWidth: 2), color: Colors.blue),
),
],
),
);
}
}
class WeirdBorder extends ShapeBorder {
final double radius;
final double pathWidth;
WeirdBorder({#required this.radius, this.pathWidth = 1});
#override
EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
#override
Path getInnerPath(Rect rect, {TextDirection textDirection}) {
return Path()
..fillType = PathFillType.evenOdd
..addPath(getOuterPath(rect, textDirection: textDirection), Offset.zero);
}
#override
Path getOuterPath(Rect rect, {TextDirection textDirection}) => _createPath(rect);
#override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
#override
ShapeBorder scale(double t) => WeirdBorder(radius: radius);
Path _createPath(Rect rect) {
final innerRadius = radius + pathWidth;
final innerRect = Rect.fromLTRB(rect.left + pathWidth, rect.top + pathWidth, rect.right - pathWidth, rect.bottom - pathWidth);
final outer = Path.combine(PathOperation.difference, Path()..addRect(rect), _createBevels(rect, radius));
final inner = Path.combine(PathOperation.difference, Path()..addRect(innerRect), _createBevels(rect, innerRadius));
return Path.combine(PathOperation.difference, outer, inner);
}
Path _createBevels(Rect rect, double radius) {
return Path()
..addOval(Rect.fromCircle(center: Offset(rect.left, rect.top), radius: radius))
..addOval(Rect.fromCircle(center: Offset(rect.left + rect.width, rect.top), radius: radius))
..addOval(Rect.fromCircle(center: Offset(rect.left, rect.top + rect.height), radius: radius))
..addOval(Rect.fromCircle(center: Offset(rect.left + rect.width, rect.top + rect.height), radius: radius));
}
}
Also you can use fw_ticket package.

Flutter : Achieved Sliverappbar Shape

I'm found some Appbar design in Dribble https://dribbble.com/shots/9175650-Beauty-Salon-App/attachments/1218583?mode=media , I try to create same like that but i confused get border radius each other side.
Trial 1 Without Background :
Trial 2 With Scaffold Background Color Blue
Trial 3 With background Color Deep Orange
How can i achieved appbar design like the link ?
Source Code
import 'package:flutter/material.dart';
import 'package:flutter_iconpicker/flutter_iconpicker.dart';
class TestingScreen extends StatelessWidget {
static const routeName = "/testing-screen";
#override
Widget build(BuildContext context) {
var mediaQuery = MediaQuery.of(context);
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: mediaQuery.size.height / 4,
backgroundColor: Colors.deepOrange,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.only(bottomRight: Radius.circular(80))),
),
SliverList(
delegate: SliverChildListDelegate(
[
Container(
height: mediaQuery.size.height,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius:
BorderRadius.only(topLeft: Radius.circular(80))),
child: Center(child: Text('data')),
)
],
),
)
],
),
);
}
}
You can actually use CustomClipper to do this, with a method of Path quadraticBezierTo:
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Stack(
children: <Widget>[
Container(color: Colors.orange),
ClipPath(
clipper: TheCustomClipper(),
child: Container(
color: Colors.greenAccent,
),
),
],
),
),
);
}
}
class TheCustomClipper extends CustomClipper<Path> {
#override
getClip(Size size) {
var path = Path();
path.lineTo(0, size.height / 3);
var firstControlPoint = Offset(0, size.height / 3.5); // adjust the height to move start of the first curve
var firstEndPoint = Offset(size.width / 4.2, size.height / 3.5 + 10); // adjust the width to add the end controll point and height to move end of the first curve
var secControlPoint = Offset(size.width, size.height / 2.8); // adjust the height to move end of the second curve
var secEndPoint = Offset(size.width, size.height / 3 - 40); // adjust the width to add the right first controll point and height to move start of the second curve
path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy,
firstEndPoint.dx, firstEndPoint.dy);
path.quadraticBezierTo(
secControlPoint.dx, secControlPoint.dy, secEndPoint.dx, secEndPoint.dy);
path.lineTo(size.width, size.height / 3);
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper oldClipper) {
return null;
}
}
Do note that, I only added curve to first widget. If you want to add another CustomClipper to the first Container of the Stack, do it by doing the reverse of the logic.
Output:
Source Code:
import 'package:flutter/material.dart';
import '../../../shared/app_colors.dart';
class WelcomenPage extends StatelessWidget {
static const id = 'welcomen_page';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: <Widget>[
ClipPath(
child: Container(
height: MediaQuery.of(context).size.height,
color: AppColors.primary,
),
clipper: BottomWaveClipper(),
),
],
),
);
}
}
class BottomWaveClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path = Path();
path.lineTo(0, size.height * .65);
var firstControlPoint = Offset(0, size.height * .75);
var firstEndPoint = Offset(size.width / 6, size.height * .75);
path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy,
firstEndPoint.dx, firstEndPoint.dy);
path.lineTo(size.width / 1.2, size.height * .75);
var secControlPoint = Offset(size.width, size.height * .75);
var secEndPoint = Offset(size.width, size.height * 0.85);
path.quadraticBezierTo(
secControlPoint.dx, secControlPoint.dy, secEndPoint.dx, secEndPoint.dy);
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
Output:
image