Can a child RenderObject take a parent transformation into account? - flutter

I have implemented a 'Marker' that uses a GlobalKey to find the location of a Container and draws a blue box around it. This works great as long as no transformation is taking place (e.g. FittedBox with scaleDown). But if there is, the blue box appears at an incorrect position.
Does anyone know how to account for the transformation to make the blue box appear in the correct location?
Full code example:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(const MyApp());
}
var boxKey = GlobalKey();
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Center(
child: SizedBox(
height: 100,
width: 100,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Container(
height: 200,
width: 200,
color: Colors.yellow,
child: Stack(
children: [
const Marker(),
Align(
alignment: const Alignment(-0.5, -0.5),
child: Container(
key: boxKey,
width: 30,
height: 30,
color: Colors.red,
)),
],
),
),
),
),
),
);
}
}
class Marker extends LeafRenderObjectWidget {
const Marker({Key? key}) : super(key: key);
#override
RenderMarker createRenderObject(BuildContext context) {
return RenderMarker();
}
#override
void updateRenderObject(BuildContext context, RenderMarker renderObject) {}
}
class RenderMarker extends RenderProxyBox {
#override
void performLayout() {
super.performLayout();
}
#override
void paint(PaintingContext context, Offset offset) {
super.paint(context, offset);
var renderBox = boxKey.currentContext!.findRenderObject() as RenderBox;
var center = renderBox.localToGlobal(Offset.zero) +
Offset(renderBox.size.width / 2, renderBox.size.height / 2);
context.canvas.drawRect(
Rect.fromCenter(center: center, width: 50, height: 50),
Paint()..color = Colors.blue);
}
}

I found a working solution:
#override
void paint(PaintingContext context, Offset offset) {
super.paint(context, offset);
var renderBox = boxKey.currentContext!.findRenderObject() as RenderBox;
var p = parent! as RenderObject;
var parentOffset = renderBox.localToGlobal(Offset.zero, ancestor: p);
var center = parentOffset +
Offset(renderBox.size.width / 2, renderBox.size.height / 2);
context.canvas.drawRect(
Rect.fromCenter(center: center + offset, width: 50, height: 50),
Paint()..color = Colors.blue);
}
Notice how I calculate everything in relation to the parent now.

Related

How to make a spinning circle animation during a countdown in flutter?

I have a time widget that counts down 30 seconds. I have a circle around the time (I attached a screenshot below). I need to animate this circle so that when the time is counted down for 30 seconds, the circle spins, only the time reaches 00:00 the circle stops and does not spin, by pressing the button, the time counts down again for 30 seconds and the circle starts spinning. Tell me how to make an animation of a spinning circle?
class ResendLoader extends StatelessWidget {
const ResendLoader({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
context.read<TimerBloc>().add(const TimerStarted(duration: 30));
return BlocBuilder<TimerBloc, TimerState>(
buildWhen: (prev, state) => prev.runtimeType != state.runtimeType,
builder: (context, state) {
return Center(
child: Stack(
alignment: AlignmentDirectional.center,
children: [
SizedBox(
width: 70,
height: 70,
child: CustomPaint(
painter: RingPainter(
progress: 0.3,
taskCompletedColor: Colors.white,
taskNotCompletedColor:
constants.Colors.greyDark.withOpacity(0.5)),
),
),
_timerText(context),
],
),
);
},
);
}
class CirclePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final strokeWidth = size.width / 15.0;
final center = Offset(size.width / 2, size.height / 2);
final radius = (size.width - strokeWidth) / 2;
var paint1 = Paint()
..color = const Color(0xff63aa65)
..style = PaintingStyle.fill;
//a circle
canvas.drawCircle(center, radius, paint1);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
You can use AnimatedRotation widget and animate its turns like this
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: AnimatedCircle());
}
}
class AnimatedCircle extends StatefulWidget {
const AnimatedCircle({Key? key}) : super(key: key);
#override
State<AnimatedCircle> createState() => _AnimatedCircleState();
}
class _AnimatedCircleState extends State<AnimatedCircle> {
double rotation = 0;
int seconds = 30;
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MaterialButton(
color: Colors.blue,
child: Text("Start Animation"),
onPressed: () {
seconds = 30;
rotation = 0;
startAnimation();
}),
SizedBox(
height: 20,
),
MaterialButton(
color: Colors.blue,
child: Text("Stop Animation"),
onPressed: () {
seconds = 0;
setState(() {});
}),
SizedBox(
height: 20,
),
AnimatedRotation(
turns: rotation,
duration: Duration(seconds: 1),
curve: Curves.linear,
child: Stack(
alignment: AlignmentDirectional.center,
children: [
SizedBox(
width: 70,
height: 70,
child: CustomPaint(
painter: RingPainter(
progress: 0.3,
taskCompletedColor: Colors.white,
taskNotCompletedColor:
constants.Colors.greyDark.withOpacity(0.5)),
),
),
SizedBox(
height: 20,
),
Text("Seconds: $seconds")
],
),
),
);
}
startAnimation() {
//To slow down the animation
rotation += 0.5;
seconds--;
setState(() {});
if (seconds >= 0) {
Future.delayed(Duration(seconds: 1), () {
startAnimation();
});
}
}
}
Use a FutureBuilder, the future is a this: Future.delayed(Duration(minutes: 1))
After a minute, the builder will update to complete. Change the minutes to whatever you want, you can do minutes, seconds, hours, etc.
In the builder, have an if statement to check the status. in the else, return a CircularProgressIndicator widget. That's what you want.
See the docs: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
FYI, you have way more code than you need.

How to set wave effect for a button when taps on it in Flutter

Hi, I was trying to build this ui, but i couldnt implement the wave effect as shown in the image.
i got some code for the wave effect but it does not fit well. I made the ui code very complex. so i made a similar ui for sharing .
///////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
class Test extends StatefulWidget {
const Test({Key key}) : super(key: key);
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.mainBg3,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
buttonView(0),
buttonView(1),
buttonView(2),
buttonView(4),
],
),
),
);
}
var selectedIndex = 0;
Widget buttonView(int i) {
return Container(
margin: EdgeInsets.only(bottom: 30),
child: InkWell(
onTap: () {
selectedIndex = i;
setState(() {
});
},
child: selectedIndex == i ? WaveAnimation(child: button()) : button(),
),
);
}
Widget button() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
radius: 12,
backgroundColor: Colors.white,
child: Container(
height: 13,
width: 13,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [Color(0xffD053A3), Color(0xff842990)])),
),
),
],
);
}
}
And heres the code of wave animation
class WaveAnimation extends StatefulWidget {
const WaveAnimation({
this.size = 80.0,
#required this.child,
});
final double size;
final Widget child;
#override
_WaveAnimationState createState() => _WaveAnimationState();
}
class _WaveAnimationState extends State<WaveAnimation>
with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
Color _color = Color(0xffB05CA1);
#override
Widget build(BuildContext context) {
return Center(
child: CustomPaint(
painter: CirclePainter(
_controller,
color: _color.withOpacity(0.1),
),
child: SizedBox(
width: widget.size * 2,
height: widget.size * 2,
child: _button(),
),
),
);
}
Widget _button() {
return Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(widget.size),
child: DecoratedBox(
decoration: BoxDecoration(
gradient: RadialGradient(
colors: <Color>[_color, Color.lerp(_color, Colors.black, 0.05)],
),
),
child: ScaleTransition(
scale: Tween(begin: 0.95, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: CurveWave(),
),
),
),
),
),
);
}
}
class CurveWave extends Curve {
const CurveWave();
#override
double transform(double t) {
if (t == 0 || t == 1) {
return 0.01;
}
return math.sin(t * math.pi);
}
}
class CirclePainter extends CustomPainter {
CirclePainter(
this._animation, {
#required this.color,
}) : super(repaint: _animation);
final Color color;
final Animation<double> _animation;
void circle(Canvas canvas, Rect rect, double value) {
final double opacity = (1.0 - (value / 4.0)).clamp(0.0, 0.2);
final Color _color = color.withOpacity(opacity);
final double size = rect.width / 2;
final double area = size * size;
final double radius = math.sqrt(area * value / 4);
final Paint paint = Paint()..color = _color;
canvas.drawCircle(rect.center, radius, paint);
}
#override
void paint(Canvas canvas, Size size) {
final Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
for (int wave = 3; wave >= 0; wave--) {
circle(canvas, rect, wave + _animation.value);
}
}
#override
bool shouldRepaint(CirclePainter oldDelegate) => true;
}
To fit this effect you will use customBorder inside the inkwell.
customBorder:StadiumBorder()

Pass a class as method parameter in Dart

Suppose this class:
class SimpleRectPaint extends Object {
static CustomPaint build( ParamsBundle paramBundle ) {
return CustomPaint(
painter: SimpleRectPainter( paramBundle: paramBundle ),
child: Container(width: paramBundle.width, height: paramBundle.height)
);
}
}
I'd like to abstract the class SimpleRectPaint by means of a ClassReference parameter someClass and make the method more generic. Unfortunately, this isn't valid code:
class SimpleRectPaint extends Object {
static CustomPaint build( ParamsBundle paramBundle, ClassReference someClass ) {
return CustomPaint(
painter: someClass( paramBundle: paramBundle ),
child: Container(width: paramBundle.width, height: paramBundle.height)
);
}
}
Q: How do I have to write it instead?
You could do that by passing a CustomPainterCreator:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
title: 'Generic Painter',
home: Scaffold(
body: MyWidget(),
),
),
);
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: SizedBox(
width: 200,
height: 200,
child: Stack(
fit: StackFit.expand,
children: [
Background(),
MyPainter(creator: RectPainter.creator, color: Color(0xFF104C91)),
MyPainter(creator: OvalPainter.creator, color: Color(0xFF1F8AC0)),
],
),
),
);
}
}
class Background extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Color(0xFFEFC9AF),
border: Border.all(width: 3.0),
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
);
}
}
typedef CustomPainterCreator(Color color);
class MyPainter extends StatelessWidget {
final CustomPainterCreator creator;
final Color color;
const MyPainter({
Key key,
this.creator,
this.color,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: creator(color),
);
}
}
class OvalPainter extends CustomPainter {
static CustomPainterCreator creator = (color) => OvalPainter(color: color);
final Color color;
OvalPainter({this.color = Colors.green});
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
canvas.drawOval(
Rect.fromLTWH(size.width * .4, size.height * .15, size.width * .5,
size.height * .5),
paint,
);
}
#override
bool shouldRepaint(OvalPainter oldDelegate) => false;
}
class RectPainter extends CustomPainter {
static CustomPainterCreator creator = (color) => RectPainter(color: color);
final Color color;
RectPainter({this.color = Colors.indigo});
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
canvas.drawRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(size.width * .15, size.height * .25, size.width * .6,
size.height * .6),
Radius.circular(20)),
paint,
);
}
#override
bool shouldRepaint(RectPainter oldDelegate) => false;
}
But, though the exercise is interesting... Why?
Instead of:
MyPainter(creator: RectPainter.creator, color: Color(0xFF104C91)),
You can just do:
CustomPaint(painter: RectPainter(color: Color(0xFF104C91))),
If, not, what is the specific needs that would require more abstraction?

How to keep clipped area constant while resizing a container dynamically in Flutter?

I have a following code which has an image that is cropped to 150px by default. What I want is to make this cropped area remain constant while resizing the container using the resize handle.
I want the resize happen like so: (See how the cropped area is constant i.e. it doesn't move. It's as if I'm resizing an image of cropped width & height)
But in my code, it works like this:
See how the cropped area also gets cropped when resizing. I want to keep the cropped area constant(not move) while resizing. Any help would be greatly appreciated.
Here's my code:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true;
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
double _cropWidth = 150;
double _width = 300;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned(
top: 0,
left: 0,
child: ClipRect(
clipper: Cropper(
height: 200,
width: _cropWidth,
top: 0,
left: 0,
),
child: Image.network(
"https://picresize.com/images/t1rsz_pexels-photo-1108099.jpg",
height: 200,
width: _width,
fit: BoxFit.fill,
),
),
),
Positioned(
top: 0,
left: 0,
child: Container(
height: 200,
width: _cropWidth,
child: Stack(
children: <Widget>[
Positioned(
top: 80,
left: _cropWidth - 20,
child: GestureDetector(
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_width = _width + d.delta.dx;
setState(() {});
},
child:
Container(height: 20, width: 20, color: Colors.blue),
),
)
],
),
),
),
],
),
);
}
}
class Cropper extends CustomClipper<Rect> {
final double left;
final double top;
final double width;
final double height;
const Cropper({
this.left = 0.0,
this.top = 0.0,
this.width = 0.0,
this.height = 0.0,
});
#override
Rect getClip(Size a) {
return Rect.fromLTWH(left, top, width, height);
}
#override
bool shouldReclip(CustomClipper<Rect> old) => true;
}
There is no problem in cropWidth. The problem is in the original image width.
You may notice the area cropWidth and width should be related in some ratio. You should calculate crop width from width (or reverse).
In your problem, the ratio seems to be a fixed number:
double _ratio = 0.5;
double _cropWidth = 150;
double _width = 300;
...
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_width = _cropWidth / _ratio;
setState(() {});
}
...
If you want, you can change the crop ratio by modifying it
Positioned(
top: 120,
left: _cropWidth - 20,
child: GestureDetector(
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_ratio = _cropWidth / _width;
setState(() {});
},
child:
Container(height: 20, width: 20, color: Colors.green),
),
),
Is this what you want?
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true;
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
double _cropWidth = 300;
double _width = 300;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned(
top: 0,
left: 0,
child: ClipRect(
clipper: Cropper(
height: 200,
width: _cropWidth,
top: 0,
left: 0,
),
child: Image.network(
"https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg",
height: 200,
width: _width,
fit: BoxFit.fill,
),
),
),
Positioned(
top: 0,
left: 0,
child: Container(
height: 200,
width: _cropWidth,
child: Stack(
children: <Widget>[
Positioned(
top: 80,
left: _cropWidth - 20,
child: GestureDetector(
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_width = _width + d.delta.dx;
setState(() {});
},
child:
Container(height: 20, width: 20, color: Colors.blue),
),
)
],
),
),
),
],
),
);
}
}
class Cropper extends CustomClipper<Rect> {
final double left;
final double top;
final double width;
final double height;
const Cropper({
this.left = 0.0,
this.top = 0.0,
this.width = 0.0,
this.height = 0.0,
});
#override
Rect getClip(Size a) {
return Rect.fromLTWH(left, top, width, height);
}
#override
bool shouldReclip(CustomClipper<Rect> old) => true;
}
i am also try this to do thing, may be its helpful for you
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true;
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
double _cropWidth = 350;
double _width = 350;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
ClipRect(
clipper: Cropper(
height: 200,
width: _cropWidth,
),
child: Image.network(
"https://images.pexels.com/photos/1108099/pexels-photo-1108099.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
height: 200,
width: _width,
fit: BoxFit.fill,
),
),
Container(
height: 200,
width: _cropWidth,
child: Stack(
children: <Widget>[
Positioned(
top: 80,
left: _cropWidth - 20,
child: GestureDetector(
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_width = _width + d.delta.dx;
setState(() {});
},
child:
Container(height: 20, width: 20, color: Colors.blue),
),
)
],
),
),
],
),
);
}
}
class Cropper extends CustomClipper<Rect> {
final double left;
final double top;
final double width;
final double height;
const Cropper({
this.left = 0.0,
this.top = 0.0,
this.width = 0.0,
this.height = 0.0,
});
#override
Rect getClip(Size a) {
return Rect.fromLTWH(left, top, width, height);
}
#override
bool shouldReclip(CustomClipper<Rect> old) => true;
}

How to get offset of canvas in CustomPainter's paint function?

lets say that we have a background image:
I want to render few views with shared background:
I think that I can use CustomPainter and draw my background image translated by canvas offset, but I don't know how to get that property inside paint function:
class PanelBackgroundPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var rect = Offset.zero & size;
canvas.clipRect(rect);
canvas.drawImage(image, new Offset(-canvasOffsetX, -canvasOffsetY), new Paint());
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
How can I calculate canvasOffsetX and canvasOffsetY?
I am using rows and columns to lay out my panels:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new Column(
children: <Widget>[
new Expanded(
child: new Container(
color: new Color.fromARGB(255, 128, 128, 128),
),
flex: 100,
),
new Container(height: 10.0),
new Container(
color: new Color.fromARGB(255, 128, 128, 128),
height: 120.0,
),
new Container(height: 10.0),
new Expanded(
child: new Container(
child: new Row(
children: <Widget>[
new Expanded(
child: new Container(
color: new Color.fromARGB(255, 128, 128, 128),
),
flex: 50,
),
new Container(width: 10.0),
new Expanded(
child: new Container(
color: new Color.fromARGB(255, 128, 128, 128),
),
flex: 50,
),
],
),
),
flex: 200,
),
],
),
);
}
}
While I don't know that it is necessarily a great idea, it is possible. The best explanation is probably just to look at this code:
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
class SharedBackgroundPainter extends CustomPainter {
final ui.Image image;
final RenderBox renderBox;
final RenderBox parentRender;
SharedBackgroundPainter({#required this.image, #required this.renderBox, #required this.parentRender});
#override
void paint(Canvas canvas, Size size) {
var rect = ui.Offset.zero & size;
var globalThisTopLeft = renderBox.localToGlobal(new ui.Offset(0.0, 0.0), ancestor: parentRender);
canvas.clipRect(rect);
canvas.drawImage(image, new Offset(-globalThisTopLeft.dx, -globalThisTopLeft.dy), new Paint());
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
class BackgroundBuilder extends StatelessWidget {
final Size size;
final ui.Image image;
final RenderBox parentRender;
const BackgroundBuilder({Key key, #required this.size, #required this.image, #required this.parentRender})
: super(key: key);
#override
Widget build(BuildContext context) {
return new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
RenderBox box = context.findRenderObject();
assert(box != null);
return new CustomPaint(
size: new Size(constraints.maxWidth, constraints.maxHeight),
painter: new SharedBackgroundPainter(image: image, renderBox: box, parentRender: parentRender),
);
});
}
}
class SharedBackgroundBuilder extends StatelessWidget {
final ui.Image toDraw;
final Size size;
final RenderBox parentRender;
const SharedBackgroundBuilder({Key key, #required this.toDraw, #required this.size, #required this.parentRender})
: super(key: key);
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Expanded(
child: new BackgroundBuilder(
image: toDraw,
size: size,
parentRender: parentRender,
),
flex: 100,
),
new SizedBox(height: 10.0),
new SizedBox(
height: 120.0,
child: new BackgroundBuilder(
image: toDraw,
size: size,
parentRender: parentRender,
),
),
new SizedBox(height: 10.0),
new Expanded(
child: new Container(
child: new Row(
children: <Widget>[
new Expanded(
child: new BackgroundBuilder(
image: toDraw,
size: size,
parentRender: parentRender,
),
flex: 50,
),
new Container(width: 10.0),
new Expanded(
child: new BackgroundBuilder(
image: toDraw,
size: size,
parentRender: parentRender,
),
flex: 50,
),
],
),
),
flex: 200,
),
],
);
}
}
/// This is to resize an image to the size of the overall structure. Note that this will
/// only work in an environment where LayoutBuilder can find constraints.
class ImageResizingPainter extends CustomPainter {
final ui.Image image;
final BoxFit boxfit;
ImageResizingPainter(this.image, this.boxfit);
#override
void paint(ui.Canvas canvas, ui.Size size) {
final ui.Rect rect = ui.Offset.zero & size;
final Size imageSize = new Size(image.width.toDouble(), image.height.toDouble());
FittedSizes sizes = applyBoxFit(boxfit, imageSize, size);
// if you don't want it centered for some reason change this.
final Rect inputSubrect = Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
final Rect outputSubrect = Alignment.center.inscribe(sizes.destination, rect);
canvas.drawImageRect(image, inputSubrect, outputSubrect, new Paint());
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
class ImageResizer extends StatelessWidget {
final ui.Image image;
final BoxFit boxFit;
// if you want a different boxfit you can set it. See BoxFit documentation.
const ImageResizer({Key key, #required this.image, this.boxFit = BoxFit.cover}) : super(key: key);
ui.Image getResizedImage(Size size) {
var pictureRecorder = new ui.PictureRecorder();
Canvas canvas = new Canvas(pictureRecorder);
CustomPainter painter = new ImageResizingPainter(image, boxFit);
painter.paint(canvas, size);
return pictureRecorder.endRecording().toImage(size.width.floor(), size.height.floor());
}
Widget build(BuildContext context) {
return new LayoutBuilder(builder: (context, constraints) {
RenderBox box = context.findRenderObject();
var size = new Size(constraints.maxWidth, constraints.maxHeight);
// This might not be a good idea to do in the build function, but we're going to ignore that
// and do it anyways.
ui.Image resizedImage = getResizedImage(size);
return new SharedBackgroundBuilder(toDraw: resizedImage, size: size, parentRender: box);
});
}
}
class ImageDrawer extends CustomPainter {
final ui.Image image;
ImageDrawer(this.image);
#override
void paint(ui.Canvas canvas, ui.Size size) {
canvas.drawImage(image, ui.Offset.zero, new Paint());
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
/// This is just to build an image to display. I'm too lazy to load one =D
class GradientPainter extends CustomPainter {
#override
void paint(ui.Canvas canvas, ui.Size size) {
Rect rect = ui.Offset.zero & size;
var linearGradient = new LinearGradient(
colors: [Colors.red, Colors.green],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
var shader = linearGradient.createShader(rect);
canvas.drawRect(rect, new Paint()..shader = shader);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
class GradientGetter extends StatelessWidget {
// I'm too lazy to create an image so I'm just drawing a gradient
ui.Image getGradientImage(Size size) {
var pictureRecorder = new ui.PictureRecorder();
Canvas canvas = new Canvas(pictureRecorder);
GradientPainter painter = new GradientPainter();
painter.paint(canvas, size);
return pictureRecorder.endRecording().toImage(size.width.floor(), size.height.floor());
}
#override
Widget build(BuildContext context) {
return new LayoutBuilder(builder: (context, constraints) {
var size = new Size(constraints.maxWidth, constraints.maxHeight);
// remove the / 20 to get it smooth again, it's just there to test
// out the scaling.
var changedSize = size / 20.0;
ui.Image gradientImage = getGradientImage(changedSize);
assert(gradientImage != null);
return new ImageResizer(
image: gradientImage,
);
});
}
}
/// This is just so that you can copy/paste this and have it run.
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new Padding(
padding: const EdgeInsets.all(30.0),
child: new GradientGetter(),
));
}
}
As I went through it I realized that it needs to be more complex than I originally thought - you have to deal with scaling the image to the size of the container etc. This does all that now, although I haven't tested it super extensively.
Also note that this gives no consideration to performance - it's almost for sure bad performance-wise.