Rotating image based on drag handle in flutter - flutter

My end goal is to achieve somethinng like this:
As you can see there's the drag handle is required to rotate this image.
I have a following code:
import 'package:flutter/material.dart';
double ballRadius = 7.5;
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double _angle = 0.0;
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SafeArea(
child: Stack(
children: [
Positioned(
top: 100,
left: 100,
child: Transform.rotate(
angle: _angle,
child: Column(
children: [
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(30),
),
child: LayoutBuilder(
builder: (context, constraints) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanUpdate: (DragUpdateDetails details) {
Offset centerOfGestureDetector = Offset(
constraints.maxWidth / 2,
constraints.maxHeight / 2,
);
final touchPositionFromCenter =
details.localPosition -
centerOfGestureDetector;
print(touchPositionFromCenter.direction);
setState(() {
_angle = touchPositionFromCenter.direction;
});
},
);
},
),
),
Container(
height: 30,
width: 5,
color: Colors.black,
),
Container(
height: 200,
width: 200,
color: Colors.red,
),
],
),
),
)
],
),
),
),
);
}
}
It is working. But sometimes it's too fast or too slow. Please help me fix this issue.

I made a few modifications to the code, notably
Treating the "real" centerOfGestureDetector as the center of all the items you would like to rotate
Determining and tracking the change in angle with the onPanStart,onPanEnd and onPanUpdate methods
import 'package:flutter/material.dart';
double ballRadius = 7.5;
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double _angle = 0.0;
double _oldAngle = 0.0;
double _angleDelta = 0.0;
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SafeArea(
child: Stack(
children: [
Positioned(
top: 100,
left: 100,
child: Transform.rotate(
angle: _angle,
child: Column(
children: [
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(30),
),
child: LayoutBuilder(
builder: (context, constraints) {
// Offset centerOfGestureDetector = Offset(
// constraints.maxWidth / 2, constraints.maxHeight / 2);
/**
* using center of positioned element instead to better fit the
* mental map of the user rotating object.
* (height = container height (30) + container height (30) + container height (200)) / 2
*/
Offset centerOfGestureDetector =
Offset(constraints.maxWidth / 2, 130);
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (details) {
final touchPositionFromCenter =
details.localPosition -
centerOfGestureDetector;
_angleDelta = _oldAngle -
touchPositionFromCenter.direction;
},
onPanEnd: (details) {
setState(
() {
_oldAngle = _angle;
},
);
},
onPanUpdate: (details) {
final touchPositionFromCenter =
details.localPosition -
centerOfGestureDetector;
setState(
() {
_angle = touchPositionFromCenter.direction +
_angleDelta;
},
);
},
);
},
),
),
Container(
height: 30,
width: 5,
color: Colors.black,
),
Container(
height: 200,
width: 200,
color: Colors.red,
),
],
),
),
)
],
),
),
),
);
}
}

Related

How can I make responsive UI using stack and positioned with x y coordinates?

I am using stack and positioned with x y coordinates from gesturedetector, ontapdowndetils(details),in with details provide localposition.
But by positioning it with x y , it is not responsive as I tried to do it with stack overflow answered questions and tips. Its changing position from device to device. please refer images and code provided , need to position it on same spots on all devices.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int selectedImageIndex = 0;
String ourImage = '';
void _changeImageIndex(index) {
setState(() {
selectedImageIndex = index;
});
}
addImageToSpots() {
setState(() {
ourImage =
'https://img.ecelebrityfacts.com/wp-content/uploads/2016/08
/elon-musk-3067-30988-1471258987-2048x2048.jpg';
});
}
List<ImageList> list = [
ImageList(
image:
'https://www.101planners.com/borders/wp-content/uploads/2018/03
/wanted-poster-13.jpg',
x: 1650.0,
y: 1229.0502793296089,
),
ImageList(
image: 'https://img1.etsystatic.com/000/0/6713244/il_570xN.306337089.jpg',
x: 99.35715553977273,
y: 291.88920454545456,
),
];
var newX;
var newY;
void setPoints() async {
if (mounted) {
setState(() {
var newWidth = MediaQuery.of(context).size.width;
var newHeight = MediaQuery.of(context).size.height * .6;
double baseWidth = 395;
double baseHeight = 460;
newX = (newWidth * 102) / baseWidth;
newY = (newHeight * 326) / baseHeight;
});
}
}
#override
Widget build(BuildContext context) {
setPoints();
return Scaffold(
appBar: AppBar(
title: const Text('pip image'),
),
body: Center(
child: Column(
children: [
imageSelector(context),
buildImageList(),
//buildImageList(),
],
)),
);
}
imageSelector(BuildContext context) {
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return Stack(
children: [
SizedBox(
height: height * .6,
width: width,
child: GestureDetector(
onTapDown: ((details) {
print(height);
print(width);
print(details
.localPosition.dx); // for getting the image local position
print(details
.localPosition.dy); // for getting the image local position
}),
child: Image.network(
list[selectedImageIndex].image,
fit: BoxFit.contain,
),
),
),
if (selectedImageIndex == 0)
Positioned(
left: newX,
top: newY,
child: GestureDetector(
onTap: () {
print('addImageToSpots');
addImageToSpots();
},
child: Container(
color: Colors.grey,
height: (height * .6) * .31,
width: width * .36,
child: ourImage.isEmpty
? null
: Image.network(
ourImage,
fit: BoxFit.cover,
),
),
),
)
else
Positioned(
left: newX,
top: newY,
child: GestureDetector(
onTap: () {
print('addImageToSpots');
addImageToSpots();
},
child: CircleAvatar(
radius: (width * .6) * .19,
backgroundImage:
ourImage.isEmpty ? null : NetworkImage(ourImage),
),
),
)
],
);
}
buildImageList() {
return Expanded(
child: Container(
height: 180,
color: Colors.blueGrey,
child: ListView.builder(
itemCount: list.length,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
return imageList(index);
},
),
),
);
}
imageList(int index) {
return GestureDetector(
onTap: () {
print(index);
_changeImageIndex(index);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image.network(
list[index].image,
width: 150,
fit: BoxFit.cover,
),
),
),
);
}
}

MouseRegion doesn't cover positioned element in stack

I struggle to get a positioned object that I have in a stack to keep the mouse region that I have defined on an element that is bigger than the initial stacksize. The content gets shown, but it doesn't keep the mouse region. Meaning, even I'm still on top of the positioned object, it disappears when the mouse leaves the defined area.
I also can't switch the size of the element because that would cause my content to get shifted.
Do you have any suggestions on how to solve that?
In HTML, this behaviour is standard. So it puzzles me that Flutter struggles so much with it.
Here a code example
MouseRegion(
onEnter: (_) {
isButtonHovered = true;
setState(() {});
},
onExit: (_) async {
isButtonHovered = false;
setState(() {});
},
child: SizedBox(
width: 200,
height: 100,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.bottomLeft,
children: [
Container(
color: Colors.blue,
width: 200,
height: 100,
),
isButtonHovered
? Positioned(
bottom: 0,
left: 0,
child: Container(
color: Colors.red,
width: 200,
height: 500,
))
: Container(),
],
)));
The mouse region is constrained within the 200 of the sized box.. You can also change the height of the sized box based on the mouse hover like this
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
bool isButtonHovered = false;
#override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) {
isButtonHovered = true;
setState(() {});
},
onExit: (_) async {
isButtonHovered = false;
setState(() {});
},
child: SizedBox(
width: 200,
height: isButtonHovered?500:100,//<---Here
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.bottomLeft,
children: [
Container(
color: Colors.blue,
width: 200,
height: 100,
),
isButtonHovered
? Positioned(
bottom: 0,
left: 0,
child: Container(
color: Colors.red,
width: 200,
height: 500,
))
: Container(),
],
)));
}
}
Edit
I think the layout you created is wrong. I created a working demo for you. Please check this
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
bool isButtonHovered = false;
#override
Widget build(BuildContext context) {
return Stack(children: [
Column(children: [
Text("Content"),
MouseRegion(
onEnter: (_) {
isButtonHovered = true;
setState(() {});
},
child: Container(height: 200, width: 200, color: Colors.blue)),
Text("Content"),
]),
if (isButtonHovered)
MouseRegion(
onExit: (_) {
isButtonHovered = false;
setState(() {});
},
child: Container(height: 500, width: 200, color: Colors.red.withOpacity(0.5)))
]);
}
}
The mouse region will not work outside a SizedBox with specific size even if we set the clip to none in a stack that's inside the sized box..
I think the structure will get initially 500 height. And rather than using onEnter it is better to use onHover. Also, I think enabling the max container will be based on hover position.
Text('Content'),
SizedBox(
width: 200,
height: 500,
child: MouseRegion(
onEnter: (details) {},
onHover: (event) {
final dx = event.localPosition.dy;
// enter position > max height - min blue Box
if (dx > 500 - 100) {
isButtonHovered = true;
setState(() {});
}
},
onExit: (details) async {
isButtonHovered = false;
setState(() {});
},
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.bottomLeft,
children: [
Positioned(
bottom: 0,
left: 0,
child: Container(
color: Colors.blue,
width: 200,
height: 100,
),
),
isButtonHovered
? Positioned(
bottom: 0,
left: 0,
child: Container(
color: Colors.red.withOpacity(0.8),
width: 200,
height: 500,
))
: Container(),
],
),
),
),
Text('Content'),

Placing images partially off screen and rotate it

I'm currently playing around with Flutter and came across an issue where I need your help. I already googled a lot, but missing terms or there is none...
I have a wheel that can be rotated around the center of the wheel (actually of the Container) using a GestureDetector. This is fine so far and works great.
However, I'd like to place the wheel on the left, so that half of it is off screen.
What I have ---------------+--- What I want to achieve
The wheel should still be spinnable around its center, so that the covered colors will be revealed again.
Your help is highly appreciated!
The code I use so far is basically taken from here: How to use GestureDetector to rotate images in Flutter smoothly?
import 'package:flutter/material.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wheel',
theme: ThemeData(
primarySwatch: Colors.yellow,
),
home: const Wheel(),
);
}
}
class Wheel extends StatefulWidget {
const Wheel({Key? key}) : super(key: key);
#override
_WheelState createState() => _WheelState();
}
class _WheelState extends State<Wheel> {
double finalAngle = 0.0;
double oldAngle = 0.0;
double upsetAngle = 0.0;
#override
Widget build(BuildContext context) {
return _defaultApp(context);
}
_defaultApp(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 340,
height: 500,
color: Colors.grey,
margin: const EdgeInsets.all(10.0),
child: LayoutBuilder(
builder: (context, constraints) {
Offset centerOfGestureDetector = Offset(
constraints.maxWidth / 2, constraints.maxHeight / 2);
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (details) {
final touchPositionFromCenter =
details.localPosition - centerOfGestureDetector;
upsetAngle =
oldAngle - touchPositionFromCenter.direction;
},
onPanEnd: (details) {
setState(
() {
oldAngle = finalAngle;
},
);
},
onPanUpdate: (details) {
final touchPositionFromCenter =
details.localPosition - centerOfGestureDetector;
setState(
() {
finalAngle = touchPositionFromCenter.direction +
upsetAngle;
},
);
},
child: Transform.rotate(
angle: finalAngle,
child: Image.asset('assets/images/wheel.png',
scale: 1.0),
),
);
},
),
),
],
)
],
),
),
);
}
}
I have a solution. although it is not the best solution but it is workable:
If you like it or use it kindly upvote :)
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.white,
appBar: new AppBar(
title: new Text('Hello'),
),
body: ListView(
scrollDirection: Axis.horizontal,
reverse: true,
physics: NeverScrollableScrollPhysics(),
children: [
Container(
width: MediaQuery.of(context).size.width * 2,
height: 500,
color: Colors.grey,
margin: const EdgeInsets.all(10.0),
child: LayoutBuilder(
builder: (context, constraints) {
Offset centerOfGestureDetector =
Offset(constraints.maxWidth / 2, constraints.maxHeight / 2);
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (details) {
final touchPositionFromCenter =
details.localPosition - centerOfGestureDetector;
upsetAngle = oldAngle - touchPositionFromCenter.direction;
},
onPanEnd: (details) {
setState(
() {
oldAngle = finalAngle;
},
);
},
onPanUpdate: (details) {
final touchPositionFromCenter =
details.localPosition - centerOfGestureDetector;
setState(
() {
finalAngle =
touchPositionFromCenter.direction + upsetAngle;
},
);
},
child: Transform.rotate(
angle: finalAngle,
child: Image.network(
'https://static.toiimg.com/thumb/msid-31346158,width-748,height-499,resizemode=4,imgsize-114461/.jpg',
scale: 1.0),
),
);
},
),
),
],
),
);
}

Custom shape of PopupMenuButton in flutter

I wan to change the shape of my PopupMenuButton in flutter, want to add a triangle on top as shown in below picture, I have spent a lot of time on google but no achievement please help me out I am new to flutter so I do not know how to change this default container, now it simply has white rounded container, white arrow/triangle is not being added on top of it. please help out, thanks in advance
popUpMenu= PopupMenuButton<String>(
key: _menuKey,
offset: Offset(50,100),
padding: EdgeInsets.all(0.0),
onSelected: (value) {
if (value == "Tax & Additional Charges") {
endDrawerController.drawerKey.value =
EndDrawerKeys.TaxAndAdditionalChargesEndDrawer;
endDrawerController.scaffoldKey.currentState.openEndDrawer();
print("Entering in tax");
} else if (value == "Hold this Invoice") {
endDrawerController.drawerKey.value =
EndDrawerKeys.HoldInvoiceEndDrawer;
endDrawerController.scaffoldKey.currentState.openEndDrawer();
}
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.h))),
itemBuilder: (context) => [
PopupMenuItem(
value: "Tax & Additional Charges",
child: popUpMenuSingleItem(
icon: AppAssets.DeliveryIcon,
text: "Tax & Additional Charges",
topMargin: 15.h),
),
PopupMenuItem(
value: "Hold this Invoice",
child: popUpMenuSingleItem(
icon: AppAssets.DeliveryIcon,
text: "Hold this Invoice",
topMargin: 25.h),
),
],
);
This is how I want my PopupMenuButton to be appear
You can create a shape for your custom PopupMenuButton.
Sample...
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
PopupMenuButton(
offset: Offset(0, 50),
shape: const TooltipShape(),
itemBuilder: (_) => <PopupMenuEntry>[
PopupMenuItem(
child: ListTile(
leading: const Icon(Icons.calculate_outlined),
title: const Text('Tax & Additional Charges'),
)),
PopupMenuItem(
child: ListTile(
leading: const Icon(Icons.av_timer_outlined),
title: const Text('Hold This Invoice'),
)),
],
),
],
),
);
}
}
/// I'm using [RoundedRectangleBorder] as my reference...
class TooltipShape extends ShapeBorder {
const TooltipShape();
final BorderSide _side = BorderSide.none;
final BorderRadiusGeometry _borderRadius = BorderRadius.zero;
#override
EdgeInsetsGeometry get dimensions => EdgeInsets.all(_side.width);
#override
Path getInnerPath(
Rect rect, {
TextDirection? textDirection,
}) {
final Path path = Path();
path.addRRect(
_borderRadius.resolve(textDirection).toRRect(rect).deflate(_side.width),
);
return path;
}
#override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
final Path path = Path();
final RRect rrect = _borderRadius.resolve(textDirection).toRRect(rect);
path.moveTo(0, 10);
path.quadraticBezierTo(0, 0, 10, 0);
path.lineTo(rrect.width - 30, 0);
path.lineTo(rrect.width - 20, -10);
path.lineTo(rrect.width - 10, 0);
path.quadraticBezierTo(rrect.width, 0, rrect.width, 10);
path.lineTo(rrect.width, rrect.height - 10);
path.quadraticBezierTo(
rrect.width, rrect.height, rrect.width - 10, rrect.height);
path.lineTo(10, rrect.height);
path.quadraticBezierTo(0, rrect.height, 0, rrect.height - 10);
return path;
}
#override
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
#override
ShapeBorder scale(double t) => RoundedRectangleBorder(
side: _side.scale(t),
borderRadius: _borderRadius * t,
);
}
try this:
class PopMenu extends StatefulWidget {
#override
_PopMenuState createState() => _PopMenuState();
}
class _PopMenuState extends State<PopMenu> {
List<Icon> icons = [
Icon(Icons.person),
Icon(Icons.settings),
Icon(Icons.credit_card),
];
GlobalKey _key = LabeledGlobalKey("button_icon");
OverlayEntry _overlayEntry;
Offset _buttonPosition;
bool _isMenuOpen = false;
void _findButton() {
RenderBox renderBox = _key.currentContext.findRenderObject();
_buttonPosition = renderBox.localToGlobal(Offset.zero);
}
void _openMenu() {
_findButton();
_overlayEntry = _overlayEntryBuilder();
Overlay.of(context).insert(_overlayEntry);
_isMenuOpen = !_isMenuOpen;
}
void _closeMenu() {
_overlayEntry.remove();
_isMenuOpen = !_isMenuOpen;
}
OverlayEntry _overlayEntryBuilder() {
return OverlayEntry(
builder: (context) {
return Positioned(
top: _buttonPosition.dy + 50,
left: _buttonPosition.dx - 250,
width: 300,
child: _popMenu(),
);
},
);
}
Widget _popMenu() {
return Column(
children: [
Align(
alignment: Alignment.centerRight,
child: Padding(
padding: EdgeInsets.only(right: 20),
child: ClipPath(
clipper: ArrowClipper(),
child: Container(
width: 17,
height: 17,
color: Color(0xFFF67C0B9),
),
),
),
),
Container(
width: 300,
height: 300,
decoration: BoxDecoration(
color: Color(0xFFF67C0B9),
borderRadius: BorderRadius.circular(4),
),
child: Theme(
data: ThemeData(
iconTheme: IconThemeData(
color: Colors.white,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: List.generate(
icons.length,
(index) {
return GestureDetector(
onTap: () {},
child: Container(
width: 300,
height: 100,
child: icons[index],
),
);
},
),
),
),
),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
key: _key,
decoration: BoxDecoration(
color: Color(0xFFF5C6373),
borderRadius: BorderRadius.circular(4),
),
child: IconButton(
icon: Icon(Icons.menu),
color: Colors.white,
onPressed: () {
_isMenuOpen ? _closeMenu() : _openMenu();
},
),
),
),
);
}
}
class ArrowClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
Path path = Path();
path.moveTo(0, size.height);
path.lineTo(size.width / 2, size.height / 2);
path.lineTo(size.width, size.height);
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return true;
}
}

Flutter how to display a Custom Clipper when (or inside) using a Custom Scroll View

so here's the deal. I created (sort of) a custom clipper shaped like a wave inside a class called WaveClipper
the wave clipper class:
class WaveClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path = Path();
path.lineTo(0, 220);
path.quadraticBezierTo(size.width / 4, 160 , size.width / 2, 175);
path.quadraticBezierTo(3 / 4 * size.width, 190, size.width, 130);
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return false;
}
}
And whenever I display it using a scaffold it shows fine however when I try to push it inside a SliverListView which is inside a CustomScrollView then nothing appears and there are no errors either. Is the clipper under the content? And how can I display it.
the clipper I am trying to show:
Stack(
children: [
ClipPath(
clipper: WaveClipper(),
child: Container(
color: Colors.cyanAccent,
))
],
),
where I am trying to show it:
Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0.0,
iconTheme: IconThemeData(
color: Colors.cyanAccent,
),
),
backgroundColor: Colors.white,
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
SliverList(
delegate: SliverChildListDelegate([
//here
//there rest of the content (mostly buttons)
]),
),
],
),
)
Any help is appreciated and thank you for taking the time.
Try to give some dimension to your Container:
Stack(
children: [
ClipPath(
clipper: WaveClipper(),
child: Container(
height: 300,
color: Colors.amber.shade200,
),
),
],
),
Full source code
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0.0,
iconTheme: IconThemeData(
color: Colors.cyanAccent,
),
),
backgroundColor: Colors.white,
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
SliverList(
delegate: SliverChildListDelegate([
Stack(
children: [
ClipPath(
clipper: WaveClipper(),
child: Container(
height: 300,
color: Colors.amber.shade200,
),
),
],
),
]),
),
],
),
);
}
}
class WaveClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path = Path();
path.lineTo(0, 220);
path.quadraticBezierTo(size.width / 4, 160, size.width / 2, 175);
path.quadraticBezierTo(3 / 4 * size.width, 190, size.width, 130);
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return false;
}
}
Container(
height: device.size.height * 0.3,
child: Scaffold(
body: Stack(
clipBehavior: Clip.none,
children: [
ClipPath(
clipper: WaveClipper(),
child: Container(
color: Colors.cyanAccent,
))
],
),
),
),
Wrap with a Scaffold and give that Scaffold a size using a Container, if anyone has a better solution by all means post it.
import 'package:flutter/material.dart';
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Stack(
children: [
Opacity(opacity: 0.5,
child: ClipPath(
clipper: WaveClipper(),
child: Container(color: Colors.red,
height: 200,),
),
),
ClipPath(
clipper: WaveClipper(),
child: Container(color: Colors.deepOrange,
height: 180,),
),
],
),
),
);
}
}
class WaveClipper extends CustomClipper<Path>{
#override
Path getClip(Size size){
debugPrint(size.width.toString());
var path = new Path();
path.lineTo(0,size.height);
var firstStart = Offset(size.width / 5,size.height);
var firstEnd = Offset(size.width / 2.25,size.height - 50);
path.quadraticBezierTo(firstStart.dx, firstStart.dy, firstEnd.dx, firstEnd.dy);
var secondStart = Offset(size.width -(size.width/3.24), size.height - 105);
var secondEnd = Offset(size.width, size.height - 10);
path.quadraticBezierTo(secondStart.dx, secondStart.dy, secondEnd.dx, secondEnd.dy);
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return false;
}
}
Optionally add dotted_decoration package if needed:
https://pub.dev/packages/dotted_decoration
import 'package:flutter/material.dart';
import 'package:dotted_decoration/dotted_decoration.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: DiscountTile()),
);
}
}
class DiscountTile extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ClipPath(
clipper: _DiscountTileClipper(),
child: Container(
clipBehavior: Clip.antiAlias,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
child: ColoredBox(
color: Colors.blue,
child: Row(
children: [
SizedBox(width: 100),
Expanded(
child: DecoratedBox(
decoration: DottedDecoration(
linePosition: LinePosition.left,
color: Colors.red,
strokeWidth: 3,
),
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text('hello world' * 20),
),
),
),
],
),
),
),
);
}
}
class _DiscountTileClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
const Radius radius = Radius.circular(1);
const bool clockwise = false;
const double clipRadius = 20;
const double decreaseValueWidgetWidth = 100.0;
const double leftPadding = decreaseValueWidgetWidth + 9;
return Path()
..lineTo(leftPadding - clipRadius, 0)
..arcToPoint(
const Offset(leftPadding, 0),
clockwise: clockwise,
radius: radius,
)
..lineTo(size.width, 0)
..lineTo(size.width, size.height)
..lineTo(leftPadding, size.height)
..arcToPoint(
Offset(leftPadding - clipRadius, size.height),
clockwise: clockwise,
radius: radius,
)
..lineTo(0, size.height)
..close();
}
#override
bool shouldReclip(_DiscountTileClipper oldClipper) => true;
}