Does anyone know how to add a gradient background to CupertinoSliverAppBar? - dart

I am trying to add a gradient background to a CupertinoSliverAppBar in a Flutter app but I cannot seem to figure out how to do it. The SliverAppBar has a flexibleSpace property that would accept a gradient but the CupertinoSliverAppBar only has a backgroundColor property.
Alternatively, if it were possible to move the title in the flexibleSpace more to the left, I could go with that. But I cannot figure that out either.
I read these:
https://github.com/flutter/flutter/issues/25144
Gradient in SliverAppBar (Flutter)?
And this issue opened on the Flutter repository: https://github.com/flutter/flutter/issues/25144
#rmtmckenzie does the trick! It is worth noting this also works with CupertinoSliverNavigationBar. Also, all transition animations are preserved except for that of the background for which you will bee seeing the backgroundColor property being animated. You can cheat by using one of the gradient's colors as the backgroundColor but it is not perfect. The gradient is indeed rendered below the content. See below:

This is actually a bit difficult - as you mentioned the CupertinoSliverAppBar doesn't support 'flexibleSpace' or anything like it, so you're stuck with trying to use the backgroundColor which doesn't do what you need either.
I'd recommend opening an issue on the Flutter repo stating that it isn't possible and why you'd like to do it. They probably won't take action on it right away but if they're aware that people would like to do it it, it might happen.
Not all hope is lost for an immediate fix though, we can cheat! (although I'm hoping you'll use a less terifyingly ugly gradient when you write your version)
What I've done is subclassed Border, and then overridden what it draws so that it draws a gradient you pass in before drawing the actual border. This works because it's given a paint context that covers the entire app bar, and draws over the background of the appbar but below its content (hopefully - it seems to be below the title at least so I assume it's below everything else too). The background color is still drawn under the appbar so if your gradient is somewhat transparent you'll probably want to set the backgroundColor to Colors.transparent.
Here's the code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(GradientAppBar());
class GradientCheatingBorder extends Border {
const GradientCheatingBorder({
this.gradient,
BorderSide top = BorderSide.none,
BorderSide right = BorderSide.none,
BorderSide bottom = BorderSide.none,
BorderSide left = BorderSide.none,
}) : super(top: top, right: right, bottom: bottom, left: left);
const GradientCheatingBorder.fromBorderSide(BorderSide side, {this.gradient})
: super.fromBorderSide(side);
factory GradientCheatingBorder.all({
Color color = const Color(0xFF000000),
double width = 1.0,
BorderStyle style = BorderStyle.solid,
Gradient gradient,
}) {
final BorderSide side =
BorderSide(color: color, width: width, style: style);
return GradientCheatingBorder.fromBorderSide(side, gradient: gradient);
}
final Gradient gradient;
#override
void paint(
Canvas canvas,
Rect rect, {
TextDirection textDirection,
BoxShape shape = BoxShape.rectangle,
BorderRadius borderRadius,
}) {
if (gradient != null) {
canvas.drawRect(
rect,
Paint()
..shader = gradient.createShader(rect)
..style = PaintingStyle.fill,
);
}
super.paint(
canvas,
rect,
textDirection: textDirection,
shape: shape,
borderRadius: borderRadius,
);
}
}
class GradientAppBar extends StatefulWidget {
#override
_GradientAppBarState createState() => _GradientAppBarState();
}
class _GradientAppBarState extends State<GradientAppBar> {
#override
Widget build(BuildContext context) {
return CupertinoApp(
home: CustomScrollView(
slivers: <Widget>[
CupertinoSliverNavigationBar(
largeTitle: Text("Title"),
border: GradientCheatingBorder.fromBorderSide(
BorderSide.none,
gradient: LinearGradient(colors: [Colors.black, Colors.white]),
),
),
SliverList(
delegate: SliverChildListDelegate(
[
Container(
color: Colors.blue,
height: 500,
),
Divider(),
Container(
color: Colors.black12,
height: 500,
),
Divider(),
Container(
color: Colors.lightBlue,
height: 500,
),
Divider(),
Container(
color: Colors.lightGreen,
height: 500,
),
Divider(),
],
),
),
],
),
);
}
}

you can try this
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Colors.red,
Colors.blue
],
),
),
),

Related

Draw lines with flutter

Is there any way to display skew borders at the top and bottom?
I came up with the solution below by using two images (top_layout and bottom_layout.png). Is there any other way to make those color bars with shadows without using static images?
return Container(
color: const Color.fromARGB(255, 236, 0, 140),
child: Container(
padding: const EdgeInsets.all(8.0),
child: Container(
color: Colors.white,
margin:
EdgeInsets.only(top: 60.0, bottom: 20.0, left: 15.0, right: 15.0),
child: Stack(
children: <Widget>[
Positioned.fill(
child: Image.asset(
"assets/imgs/top_layout.png",
fit: BoxFit.fitWidth,
alignment: Alignment.topCenter,
),
),
Positioned.fill(
child: Image.asset(
"assets/imgs/xbottom_layout.png",
fit: BoxFit.fitWidth,
alignment: Alignment.bottomLeft,
),
),
],
),
),
),
);
}
How do draw lines in Flutter using the CustomPaint widget
To paint in Flutter you use the CustomPaint widget. The CustomPaint widget takes a CustomPainter object as a parameter. In that class you have to override the paint method, which gives you a canvas that you can paint on. Here is the code to draw the line in the image above.
#override
void paint(Canvas canvas, Size size) {
final p1 = Offset(50, 50);
final p2 = Offset(250, 150);
final paint = Paint()
..color = Colors.black
..strokeWidth = 4;
canvas.drawLine(p1, p2, paint);
}
Notes:
The drawLine method draws a line connecting the two points you give it.
An Offset is a pair of (dx, dy) doubles, offset from the top left corner of the CustomPaint widget.
Another option
You could do something similar with the drawPoints method using the PointMode.polygon option.
#override
void paint(Canvas canvas, Size size) {
final pointMode = ui.PointMode.polygon;
final points = [
Offset(50, 100),
Offset(150, 75),
Offset(250, 250),
Offset(130, 200),
Offset(270, 100),
];
final paint = Paint()
..color = Colors.black
..strokeWidth = 4
..strokeCap = StrokeCap.round;
canvas.drawPoints(pointMode, points, paint);
}
Context
Here is the main.dart code so that you can see it in context.
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: HomeWidget(),
),
);
}
}
class HomeWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: CustomPaint( // <-- CustomPaint widget
size: Size(300, 300),
painter: MyPainter(),
),
);
}
}
class MyPainter extends CustomPainter { // <-- CustomPainter class
#override
void paint(Canvas canvas, Size size) {
// <-- Insert your painting code here.
}
#override
bool shouldRepaint(CustomPainter old) {
return false;
}
}
See also
See this article for my fuller answer.
In this case, you would like to use Custom Painter widget instead. You can draw the shape based on coordinates.
Refer this tutorial for more info.
Drawing Custom Shapes in Flutter using CustomPainter

Align first widget of Flutter's scrollview initially centeric but all following selected widgets also in the center

What I want to achieve
I want to have the very first widget of a ScrollView initially centered. But if the scrollview gets scrolled, the scrollview should be has the same width as the parent and the centered widget should be the selected one.
Approach
My first idea was to use the initialScrollOffset property. but this seems to be without any effect.
#override
Widget build(BuildContext context) {
return ListView(
scrollDirection: Axis.horizontal,
children: _buildCarouselItems(),
itemExtent: FrettirConstants.cardWidth,
controller: new ScrollController(
debugLabel: "Carousel",
initialScrollOffset: -200,
keepScrollOffset: true
),
);
}
Sketch
This may sound like a bad practice to achieve it, but you can add an empty Container with the 3/4 width of other carousel widgets to the first position.
In my dummy code each carousel widget has length 160, and empty Container must have 3/4 of other widgets. This way first carousel widget is fully visible while second one has 3/4 visibility.
Container(
width: 160.0 * 3 / 4,
color: Colors.transparent,
),
Container(
margin: EdgeInsets.only(
right: 10.0
),
width: 160.0,
color: Colors.red,
),
Container(
margin: EdgeInsets.symmetric(
horizontal: 10.0
),
width: 160.0,
color: Colors.blue,
),

Changing ListView bounce color when using iOS behavior in Flutter

When creating a ListView (example from docs), how do we change the bounce color that appears at the top of the list when scrolling using an iOS emulator with Flutter?
ListView(
children: <Widget>[
ListTile(
leading: Icon(Icons.map),
title: Text('Map'),
),
ListTile(
leading: Icon(Icons.photo_album),
title: Text('Album'),
),
ListTile(
leading: Icon(Icons.phone),
title: Text('Phone'),
),
],
);
Following this question, in iOS development without Flutter, you would do something like this to position a view offscreen to change the bounce color:
override func viewDidLoad() {
super.viewDidLoad()
let topView = UIView(frame: CGRect(x: 0, y: -collectionView!.bounds.height,
width: collectionView!.bounds.width, height: collectionView!.bounds.height))
topView.backgroundColor = .blackColor()
collectionView!.addSubview(topView)
}
Is there an equivalent for Flutter?
In Flutter the bouncing color, is simply the color of the Widget you can see behind the ListView. The default background of the ListView is transparent.
Therefore you can simply wrap it in a Container with another color.
The following example has a green bouncing color:
And the complete source code, for trying out:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(),
home: Scaffold(
body: SafeArea(
child: Container(
color: Colors.green,
child: ListView(
children: generateChildren()
),
),
),
),
);
}
List<Widget> generateChildren() {
List<Widget> result = [];
for (int i = 0; i < 20; i++) {
result.add(Container(
color: Colors.white,
child: ListTile(
leading: Icon(Icons.map),
title: Text('Map'),
),
));
}
return result;
}
}

How to draw a custom rounded rectangle border (ShapeBorder), in Flutter?

I'm trying to extend the ShapeBorder class to add some functionality. But just playing around with the paint method, I found something that I did not expect:
The corners of the border and the corners of the rectangle do not seem to match. I used the following code:
class CustomRoundedRectangleBorder extends ShapeBorder {
final double borderWidth;
final BorderRadius borderRadius;
const CustomRoundedRectangleBorder({
this.borderWidth: 1.0,
this.borderRadius: BorderRadius.zero,
})
: assert(borderRadius != null);
#override
EdgeInsetsGeometry get dimensions {
return new EdgeInsets.all(borderWidth);
}
#override
ShapeBorder scale(double t) {
return new CustomRoundedRectangleBorder(
borderWidth: borderWidth * (t),
borderRadius: borderRadius * (t),
);
}
#override
ShapeBorder lerpFrom(ShapeBorder a, double t) {
assert(t != null);
if (a is CustomRoundedRectangleBorder) {
return new CustomRoundedRectangleBorder(
borderWidth: ui.lerpDouble(a.borderWidth, borderWidth, t),
borderRadius: BorderRadius.lerp(a.borderRadius, borderRadius, t),
);
}
return super.lerpFrom(a, t);
}
#override
ShapeBorder lerpTo(ShapeBorder b, double t) {
assert(t != null);
if (b is CustomRoundedRectangleBorder) {
return new CustomRoundedRectangleBorder(
borderWidth: ui.lerpDouble(borderWidth, b.borderWidth, t),
borderRadius: BorderRadius.lerp(borderRadius, b.borderRadius, t),
);
}
return super.lerpTo(b, t);
}
#override
Path getInnerPath(Rect rect, { TextDirection textDirection }) {
return new Path()
..addRRect(borderRadius.resolve(textDirection).toRRect(rect).deflate(
borderWidth));
}
#override
Path getOuterPath(Rect rect, { TextDirection textDirection }) {
return new Path()
..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
}
#override
void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
rect = rect.deflate(borderWidth / 2.0);
Paint paint;
final RRect borderRect = borderRadius.resolve(textDirection).toRRect(rect);
paint = new Paint()
..color = Colors.red
..style = PaintingStyle.stroke
..strokeWidth = borderWidth;
canvas.drawRRect(borderRect, paint);
}
}
And created the rectangle as follows:
new Container(
height: 100.0,
width: 200.0,
padding: new EdgeInsets.all(10.0),
decoration: new ShapeDecoration(
color: Colors.black,
shape: new CustomRoundedRectangleBorder(
borderRadius: new BorderRadius.all(new Radius.circular(20.0)),
borderWidth: 10.0,
),
// side: new BorderSide(color: Colors.white)
),
child: new Center(child: new Text("My Button"),),
),
I feel like the Flutter source code takes a similar approach, but perhaps I'm not seeing something.
EDIT
Changing the style of my paint to PaintingStyle.fill thus drawing a rectangle over the original rectangle instead of borders, I do seem to get the correct borders:
void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
// rect = rect.deflate(borderWidth / 2.0);
Paint paint;
final RRect borderRect = borderRadius.resolve(textDirection).toRRect(rect);
paint = new Paint()
..color = Colors.red.withOpacity(0.25)
..style = PaintingStyle.fill
..strokeWidth = borderWidth;
canvas.drawRRect(borderRect, paint);
}
I'm still puzzled on how to do this...
You can use canvas.drawRRect :
canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(size.width / 2 - gap - smallMarkWidth - 15,gap * 8,gap + 70,gap * 5,),Radius.circular(15.0)),backgroundPaint);
You should use canvas.drawPath not drawRect
Paint paint = new Paint()
..color = borderColor
..style = PaintingStyle.stroke
..strokeWidth = borderWidth;
canvas.drawPath(getOuterPath(rect), paint);
also if you just want a border, its enough to use
#override
Path getInnerPath(Rect rect, {TextDirection textDirection}) {
return new Path()
..fillType = PathFillType.evenOdd
..addPath(getOuterPath(rect), Offset.zero);
}
This worked for me!
class MyButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DecoratedBox(
decoration:
ShapeDecoration(
shape: RoundedRectangleBorder(
side:new BorderSide(color: Color(0xFF2A8068)),
borderRadius: new BorderRadius.all(new Radius.circular(4))),
color: Color(0xFF2A8068)),
child: Theme(
data: Theme.of(context).copyWith(
buttonTheme: ButtonTheme.of(context).copyWith(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap)),
child: OutlineButton(
shape: RoundedRectangleBorder(
side:new BorderSide(color: Color(0xFF2A8068)), //the outline color
borderRadius: new BorderRadius.all(new Radius.circular(4))),
child: Text(
"ابدأ", //your text here
style: new TextStyle(
color: Colors.white, //your textColor
),
),
onPressed: () => {},
),
),
);
Draw custom rounded border with shadow.
new Container(
decoration:
new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
color: Colors.white,
boxShadow: [
new BoxShadow(
color: Colors.grey,
blurRadius: 3.0,
offset: new Offset(1.0, 1.0))
],
),
)
Draw custom rounded border without shadow.
new Container(
decoration:
new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
color: Colors.grey,
),
)
You can use ClipRRect widget instead of drawRect and it is simple to use.
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Container(),
),
I published a simple package based off #Bram Vanbilsen's code, which gives you control on how you want to draw the shape and it's border whichever way you want.
The package can be found on pub.dev here: https://pub.dev/packages/custom_rounded_rectangle_border

How to use an AnimatedContainer for animated transforms (ex. Scale)

I'm looking to animate the scale of a container using the transform property of an AnimatedContainer; however, the scale is not being transitioned and jumps directly from start to end.
Code Snippet:
var container = new AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: 50.0,
height: 50.0,
// selected is a bool that will be toggled
transform: selected ? new Matrix4.identity() : new Matrix4.identity().scaled(0.2,0.2),
decoration: new BoxDecoration(
shape: BoxShape.circle,
backgroundColor: Colors.blue[500],
),
child: new Center(
child: new Icon(
Icons.check,
color: Colors.white,
),
)
);
Any insight on what's going on?
AnimatedContainer supports animting it's transform value, as follow:
/// scale to 95%, centerred
final width = 200.0;
final height = 300.0;
bool shouldScaleDown = true;// change value when needed
AnimatedContainer(
color: Colors.blueAccent,
width: width,
height: height,
duration: Duration(milliseconds: 100),
transform: (shouldScaleDown
? (Matrix4.identity()
..translate(0.025 * width, 0.025 * height)// translate towards right and down
..scale(0.95, 0.95))// scale with to 95% anchorred at topleft of the AnimatedContainer
: Matrix4.identity()),
child: Container(),
);
I'm afraid transform is one of the properties we don't animate (child is another). If you want to animate the scale, you can use ScaleTransition.
ScaleTransition: https://docs.flutter.io/flutter/widgets/ScaleTransition-class.html
Bug for Matrix lerp: https://github.com/flutter/flutter/issues/443
you can Animate using the Animated Builder,the below code will scale a Text from font Size 20-35 in 4 secs let me break this into steps to make you better understand
1.you need to implement your class from TickerProviderStateMixin.
2.You need an AnimationController and a Animation variables;
3.wrap your widget inside an AnimatedBuilder (note AnimatedBuilder must return a Widget at least a container) and add a controller to the animation as
animation: _controller,
and builder Which returns the AnimatedWidget
4.In the init method intialize the controller with vsync and the Duration of Animation.
and the animation with the Tweenit takes begin and end values which define the initial and final values of your Widget to be animated
for me, in this case, it was text widget so the begin and end Values will be corresponding to the fontSize.which receives variable values as animation.value
5.Finally How do you want the animation effect To be will be specified by the animate which takes in controller and the Curve effect
Here is a Working example
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<SplashScreen>
with TickerProviderStateMixin {
AnimationController _controller;
Animation _animation;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF005CA9),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return Container(
child: Text(
'Hello World',
style: TextStyle(
color: Colors.white,
fontSize: _animation.value,
),
),
);
},
),
));
}
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 4));
_animation = Tween<double>(
begin: 20,
end: 35,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.ease,
),
);
_controller.forward();
}
}
the code above produces the following output

Resources