Related
I'd like to draw the black container shape with flutter.
There are many ways to do this, the first time I thought it was a cut, I was thinking in a ClipPath widget, but now I see you have a Circle widget at the top of your black Container.
This is an example:
class FunnyContainer extends StatelessWidget {
const FunnyContainer({Key? key}) : super(key: key);
Widget _childContainer() {
return Padding(
padding: const EdgeInsets.all(20.0),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.yellow,
width: 4,
),
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 40),
child: Container(
height: 400,
decoration: const BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 40),
Expanded(child: _childContainer()),
Expanded(child: _childContainer()),
Expanded(child: _childContainer()),
],
),
const Positioned(
left: 0,
right: 0,
top: -50,
child: CircleAvatar(
backgroundColor: Colors.white,
radius: 40,
),
)
],
),
),
),
);
}
}
Result:
UPDATE (Using your updated design)
Now the you updated your post, this could be done using Clippers, the problem is that clip needs Shadow, so I took this code : https://gist.github.com/coman3/e631fd55cd9cdf9bd4efe8ecfdbb85a7
And I used on this example:
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Padding(
padding: const EdgeInsets.all(40.0),
child: ClipShadowPath(
clipper: _MyClipper(100),
shadow: const Shadow(
blurRadius: 15,
color: Colors.grey,
offset: Offset(0, 10),
),
child: SizedBox(
height: 400,
child: Container(
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
Spacer(),
],
),
),
),
),
),
),
);
}
}
class _MyClipper extends CustomClipper<Path> {
final double space;
_MyClipper(this.space);
#override
Path getClip(Size size) {
final path = Path();
final halfWidth = size.width / 2;
final halfSpace = space / 2;
final curve = space / 6;
final height = halfSpace / 1.4;
path.lineTo(halfWidth - halfSpace, 0);
path.cubicTo(halfWidth - halfSpace, 0, halfWidth - halfSpace + curve,
height, halfWidth, height);
path.cubicTo(halfWidth, height, halfWidth + halfSpace - curve, height,
halfWidth + halfSpace, 0);
path.lineTo(size.width, 0);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
return path;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) => true;
}
RESULT
You just need to update the Path in order to get the rounded corners of the Container.
You may use Stack which may write draw circle on top of the container like this:
class BlackContainer extends StatelessWidget {
const BlackContainer({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
#override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Container(
child: child,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.black,
boxShadow: const [
BoxShadow(
color: Colors.grey,
spreadRadius: 5,
blurRadius: 10,
),
],
),
),
Positioned(
top: -25,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
width: 40,
height: 40,
),
),
],
);
}
}
And use it like that:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlackContainer(
child: Text(
'Content here',
style: Theme.of(context)
.textTheme
.bodyText2!
.copyWith(color: Colors.white),
),
),
),
);
}
}
Position a transparent png on top of your container.
child: Container(
width: 200,
height: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.black,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: Offset(0, 3),
),
],
),
child: Container(
child: Image.asset('assets/images/half-circle.png', fit: BoxFit.contain, alignment: new Alignment(-1.0, -1.0)),
margin: EdgeInsets.only(left: 50.0, right: 50.0),
),
),
Image:
Live example
I am trying to change the color of a container from say white to red. But I want to do it from left to right that is, from the left of the container the color red starts filling up and expands all the way to the right in some duration. I know animated container will change the color but it seems to change the color of the entire container and not how I want it to
Like the 'Next Episode' of Netflix
You could use the Stack Widget and use an AnimatedContainer in between the background container (grey) and the foreground container (displaying icon & text):
class NetflixCustomButton extends StatefulWidget {
final Duration animationDuration;
final double height;
final double width;
final double borderRadius;
const NetflixCustomButton({
this.animationDuration = const Duration(milliseconds: 800),
this.height = 30,
this.width = 130.0,
this.borderRadius = 10.0,
});
#override
_NetflixCustomButtonState createState() =>
_NetflixCustomButtonState();
}
class _NetflixCustomButtonState
extends State<NetflixCustomButton> {
double _animatedWidth = 0.0;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
height: widget.height,
width: widget.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
widget.borderRadius,
),
color: Colors.grey,
),
),
AnimatedContainer(
duration: widget.animationDuration,
height: widget.height,
width: _animatedWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
widget.borderRadius,
),
color: Colors.white,
),
),
InkWell(
child: Container(
height: widget.height,
width: widget.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
widget.borderRadius,
),
color: Colors.transparent,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(
Icons.play_arrow,
color: Colors.black87,
),
Text(
'Next Episode',
style: TextStyle(
color: Colors.black87,
),
),
],
),
),
onTap: () {
setState(() {
_animatedWidth = widget.width;
});
},
),
],
);
}
}
I am designing a simple screen. I want to acheive this
How can I remove the topLeft border. I try to using border side to show only one side border but it showing error not able to change the another then uniform. Did i remove the app bar then stack the widget. Kindly tell me which thing I am doing wrong.
My Code is
import 'package:flutter/material.dart';
class SendScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final GlobalKey<ScaffoldState> _scaffoldKey =
new GlobalKey<ScaffoldState>();
return Scaffold(
drawer: Drawer(),
key: _scaffoldKey,
appBar: PreferredSize(
preferredSize: Size.fromHeight(100.0),
child: AppBar(
leading: IconButton(
icon: Icon(Icons.access_alarm),
onPressed: () {
_scaffoldKey.currentState.openDrawer();
}),
actions: [
Container(
width: 50,
decoration: BoxDecoration(
color: const Color(0xff7c94b6),
image: const DecorationImage(
image: NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'),
fit: BoxFit.cover,
),
border: Border.all(
color: Colors.black,
// width: 8,
),
borderRadius: BorderRadius.circular(12),
),
),
SizedBox(
width: 20,
)
],
shape: ContinuousRectangleBorder(
borderRadius: BorderRadius.only(
// bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(80),
),
),
title: Text('Sliver AppBar'),
),
),
body: Column(
children: [
Container(
height: 80,
decoration: BoxDecoration(
border: Border.all(
color: Colors.red[500],
),
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(80),
),
),
child: Row(
children: [],
),
)
],
),
);
}
}
This is a known "issue". The problem is that once you assign a border radius, you don't know where the border starts and where it ends. Therefore flutter can't assign a border color to only certain border.
The only trick which works for you situation of everything I tried is to stack 2 container, the one on the back acting to do the border.
Here is the implementation:
import 'package:flutter/material.dart';
main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SendScreen(),
),
);
}
}
class SendScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
return Scaffold(
drawer: Drawer(),
key: _scaffoldKey,
appBar: PreferredSize(
preferredSize: Size.fromHeight(100.0),
child: AppBar(
leading: IconButton(
icon: Icon(Icons.access_alarm),
onPressed: () {
_scaffoldKey.currentState.openDrawer();
}),
actions: [
Container(
width: 50,
decoration: BoxDecoration(
color: const Color(0xff7c94b6),
image: const DecorationImage(
image: NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'),
fit: BoxFit.cover,
),
border: Border.all(
color: Colors.black,
// width: 8,
),
borderRadius: BorderRadius.circular(12),
),
),
SizedBox(
width: 20,
)
],
shape: ContinuousRectangleBorder(
borderRadius: BorderRadius.only(
// bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(80),
),
),
title: Text('Sliver AppBar'),
),
),
body: CustomRectangle(
borderSize: 20,
borderColor: Colors.grey[300],
height: 80.0,
borderRadius: 80.0,
child: Container(
color: Colors.red,
),
),
);
}
}
class CustomRectangle extends StatelessWidget {
final Widget child;
final double height;
final double borderSize;
final double borderRadius;
final Color borderColor;
const CustomRectangle({
Key key,
#required this.height,
#required this.borderSize,
#required this.borderRadius,
#required this.child,
#required this.borderColor,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.only(bottomRight: Radius.circular(borderRadius)),
child: Container(
height: height + borderSize,
decoration: BoxDecoration(
color: borderColor,
),
),
),
ClipRRect(
borderRadius: BorderRadius.only(bottomRight: Radius.circular(borderRadius)),
child: Container(
height: height,
child: child,
),
),
],
);
}
}
Look at CustomRectangle it is my custom widget which solve your problem. You can expand from there.
I am able to create custom button widget and now i want add text to this. I tried several option adding it under return container under Flatbutton but it does't work.
Can someone please guide me where exactly i can add text which i can pass to this widget along with icon and will be displayed below icon.
I can add either text or icon right now and I am looking for text below icon.
import 'package:tirthankar/core/const.dart';
import 'package:flutter/material.dart';
class CustomButtonWidget extends StatelessWidget {
final Widget child;
final double size;
final double borderWidth;
final String image;
final bool isActive;
final VoidCallback onTap;
CustomButtonWidget({
this.child,
#required this.size,
#required this.onTap,
this.borderWidth = 2,
this.image,
this.isActive = false
});
#override
Widget build(BuildContext context) {
var boxDecoration = BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(200),
),
border: Border.all(
width: borderWidth,
color: isActive ? AppColors.darkBlue : AppColors.mainColor,
),
boxShadow: [
BoxShadow(
color: AppColors.lightBlueShadow,
blurRadius: 10,
offset: Offset(5,5),
spreadRadius: 3,
),
BoxShadow(
color: Colors.white60,
blurRadius: 10,
offset: Offset(-5,-5),
spreadRadius: 3,
)
],
);
if (image != null){
boxDecoration = boxDecoration.copyWith(
image: DecorationImage(
image: ExactAssetImage(image),
fit: BoxFit.cover
),
);
}
if (isActive){
boxDecoration = boxDecoration.copyWith(
gradient: RadialGradient(
colors: [
AppColors.lightBlue,
AppColors.darkBlue,
]
),
);
} else {
boxDecoration = boxDecoration.copyWith(
gradient: RadialGradient(
colors: [
AppColors.mainColor,
AppColors.mainColor,
AppColors.mainColor,
Colors.white
]
),
);
}
return Container(
width: size,
height: size,
decoration: boxDecoration,
child: FlatButton(
padding: EdgeInsets.all(0),
onPressed: onTap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(200),)
),
child: child ?? Container(),
)
);
}
}
you can replace your child ?? Container() with Column like below :
return Container(
width: size,
height: size,
decoration: BoxDecoration(),
child: FlatButton(
padding: EdgeInsets.all(0),
onPressed: onTap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(200),
)),
child: Column(
children: <Widget>[
iconWidget, // child ?? Container()
textWidget,
],
),
),
);
also you can handle icon only or text only button with wrapping each iconWidget or textWidget with Visibility widget ...
Is it possible to create an outlined(transparent) button with gradient border in flutter? I tried to use LinearGradient in BorderSide style but it's not allowed.
I spent about two hours on it :)
how to use:
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
UnicornOutlineButton(
strokeWidth: 2,
radius: 24,
gradient: LinearGradient(colors: [Colors.black, Colors.redAccent]),
child: Text('OMG', style: TextStyle(fontSize: 16)),
onPressed: () {},
),
SizedBox(width: 0, height: 24),
UnicornOutlineButton(
strokeWidth: 4,
radius: 16,
gradient: LinearGradient(
colors: [Colors.blue, Colors.yellow],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
child: Text('Wow', style: TextStyle(fontSize: 16)),
onPressed: () {},
),
],
),
),
),
),
);
}
}
and the class itself:
class UnicornOutlineButton extends StatelessWidget {
final _GradientPainter _painter;
final Widget _child;
final VoidCallback _callback;
final double _radius;
UnicornOutlineButton({
#required double strokeWidth,
#required double radius,
#required Gradient gradient,
#required Widget child,
#required VoidCallback onPressed,
}) : this._painter = _GradientPainter(strokeWidth: strokeWidth, radius: radius, gradient: gradient),
this._child = child,
this._callback = onPressed,
this._radius = radius;
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: _painter,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: _callback,
child: InkWell(
borderRadius: BorderRadius.circular(_radius),
onTap: _callback,
child: Container(
constraints: BoxConstraints(minWidth: 88, minHeight: 48),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_child,
],
),
),
),
),
);
}
}
class _GradientPainter extends CustomPainter {
final Paint _paint = Paint();
final double radius;
final double strokeWidth;
final Gradient gradient;
_GradientPainter({#required double strokeWidth, #required double radius, #required Gradient gradient})
: this.strokeWidth = strokeWidth,
this.radius = radius,
this.gradient = gradient;
#override
void paint(Canvas canvas, Size size) {
// create outer rectangle equals size
Rect outerRect = Offset.zero & size;
var outerRRect = RRect.fromRectAndRadius(outerRect, Radius.circular(radius));
// create inner rectangle smaller by strokeWidth
Rect innerRect = Rect.fromLTWH(strokeWidth, strokeWidth, size.width - strokeWidth * 2, size.height - strokeWidth * 2);
var innerRRect = RRect.fromRectAndRadius(innerRect, Radius.circular(radius - strokeWidth));
// apply gradient shader
_paint.shader = gradient.createShader(outerRect);
// create difference between outer and inner paths and draw it
Path path1 = Path()..addRRect(outerRRect);
Path path2 = Path()..addRRect(innerRRect);
var path = Path.combine(PathOperation.difference, path1, path2);
canvas.drawPath(path, _paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => oldDelegate != this;
}
You can achieve this by doing just a simple trick
You have to define two Containers.
First outer container with a gradient background and the second inner container with white background. and as a child of the inner container, you can place anything e.g. TextField, Text, another button, etc.
final kInnerDecoration = BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(32),
);
final kGradientBoxDecoration = BoxDecoration(
gradient: LinearGradient(colors: [Colors.black, Colors.redAccent]),
border: Border.all(
color: kHintColor,
),
borderRadius: BorderRadius.circular(32),
);
Now this is your View
Container(
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Container(
child:Text("Button Title with your style"),
decoration: kInnerDecoration,
),
),
height: 66.0,
decoration: kGradientBoxDecoration,
),
Done
Use OutlinedButton (Recommended)
Create this class (null-safe code)
class MyOutlinedButton extends StatelessWidget {
final VoidCallback onPressed;
final Widget child;
final ButtonStyle? style;
final Gradient? gradient;
final double thickness;
const MyOutlinedButton({
Key? key,
required this.onPressed,
required this.child,
this.style,
this.gradient,
this.thickness = 2,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(gradient: gradient),
child: Container(
color: Colors.white,
margin: EdgeInsets.all(thickness),
child: OutlinedButton(
onPressed: onPressed,
style: style,
child: child,
),
),
);
}
}
Usage:
MyOutlinedButton(
onPressed: () {},
gradient: LinearGradient(colors: [Colors.indigo, Colors.pink]),
child: Text('OutlinedButton'),
)
To change the size, you can insert a Container:
OutlineGradientButton(
child: Container(
constraints: BoxConstraints(maxWidth: 300, maxHeight: 50),
height: 50,
alignment: Alignment.center,
child: Text(
'Text',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white, fontSize: 20, fontWeight: FontWeight.w500),
),
),
gradient: LinearGradient(
colors: [Color(0xfff3628b), Color(0xffec3470)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
strokeWidth: 3,
radius: Radius.circular(25),
),
Now there is a easier way provided. Flutter now has a package which does the job perfectly. I am leaving a link to the documentation for further use
https://pub.dev/packages/gradient_borders.
You can use a structure like below. You can also use it as a flat button by removing BorderRadius.
InkWell(
onTap: () {
print("TAP");
},
child: Container(
height: 85,
width: 85,
padding: EdgeInsets.all(6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
gradient: LinearGradient(
colors: [Colors.blue, Colors.black],
begin: Alignment(-1, -1),
end: Alignment(2, 2),
),
),
child: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(100),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
image: DecorationImage(
image: Image.network("https://images.unsplash.com/photo-1612151855475-877969f4a6cc?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8aGQlMjBpbWFnZXxlbnwwfHwwfHw%3D&w=400&q=80").image,
fit: BoxFit.fitHeight,
), //By deleting the image here, you can only use it text.
color: Colors.white,
border: Border.all(
color: Colors.white,
width: 4,
),
),
child: Center(child: Text("sssss")), //By deleting the text here, you can only use it visually.
width: 75,
height: 75,
),
),
),
),
)
Wrap your widget width CustomPaint widget and use this _CustomGradientBorder that extends CustomPainter.
CustomPaint(painter: const _CustomGradientBorder(thickness: 1,
colors: [Colors.red, Colors.green, Colors.blue, Colors.tealAccent],
radius: 8), child: //widget here)
class _CustomGradientBorder extends CustomPainter{
final double thickness;
final List<Color> colors;
final double radius;
const _CustomGradientBorder({required this.thickness, required this.colors, required this.radius});
#override
void paint(Canvas canvas, Size size) {
final Path path = Path();
path.moveTo(0, size.height/2);
path.lineTo(0, radius);
path.quadraticBezierTo(0, 0, radius, 0);
path.lineTo(size.width-radius, 0);
path.quadraticBezierTo(size.width, 0, size.width, radius);
path.lineTo(size.width, size.height-radius);
path.quadraticBezierTo(size.width, size.height, size.width-radius, size.height);
path.lineTo(radius, size.height);
path.quadraticBezierTo(0, size.height, 0, size.height-radius);
path.close();
final Paint paint = Paint()
..style = PaintingStyle.stroke
..shader = LinearGradient(colors: colors).createShader(Rect.fromCenter(center: Offset(size.width/2, size.height/2), width: size.width, height: size.height))
..strokeWidth = thickness;
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
class GradientBorderWidget extends StatelessWidget {
final Widget child;
const GradientBorderWidget({super.key, required this.child});
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 1.5, vertical: 1.5),
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: const LinearGradient(
colors: [color1, color2, color3],
begin: Alignment.centerLeft,
end: Alignment.centerRight)),
alignment: Alignment.center,
child: ClipRRect(borderRadius: BorderRadius.circular(15), child:
child),
);
}
}
I tried many ways to do that, but all of them had their limitations, then I found a package that worked how I expected: https://pub.dev/packages/outline_gradient_button