How to change origin of the custom paint in Flutter? - dart

Code for CustomPaint:
return new Scaffold(
body:
new GestureDetector(
onTap: () {
debugPrint("hello");
},
child:
new Container(
alignment: FractionalOffset.center,
child: new CustomPaint(
size: new Size(400.0, 400.0),
painter: new BarChartPainter(currentHeight),
))),
);
//x axis code
canvas.drawLine(new Offset(0.0, 0.0), new Offset(500.0, 0.0), paintAx);```
The x axis code will draw line from (0,0) to (500,0), which is on the top of the box of the Paint. The origin is on the top left of the box. How do I change the origin so that (0,0) is at the bottom left of the paint box?
Here is the screenshot:

Just use the method translate in canvas: canvas.translate(0, size.height). But mind that in such a case, you will need to use negative values in the y axis.
If you want your canvas coordinates to behave like a classical graph, use the method scale:
#override
void paint(Canvas canvas, Size size) {
canvas.translate(0, size.height);
canvas.scale(1, -1);
final paint = Paint();
paint.color = Colors.black;
canvas.drawLine(Offset.zero, Offset(500, 500), paint);
}

I am not really sure how can you manipulate the origin of Canvas area. You can, however, apply a translation on your Offset coordinates, which should allow you to place your line where you want eventually.
I have made this simple example, it may be of help:
import "package:flutter/material.dart";
void main() {
runApp(new MaterialApp(home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
CustomPaint _myCustomPainter = new CustomPaint(
size: new Size(400.0, 400.0),
painter: new Line(),
);
#override
Widget build(BuildContext context) {
final key = new GlobalKey<ScaffoldState>();
return new Scaffold(
key: key,
body:
new GestureDetector(
onTap: () {
debugPrint("hello");
},
child:
new Container(
alignment: FractionalOffset.center,
child: _myCustomPainter
),
),);
}
}
class Line extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
// canvas.translate(0.0, 100.0);
canvas.drawLine(new Offset(100.0, -50.0).translate(0.0, 100.0),
new Offset(0.0, 100.0).translate(0.0, 100.0),
new Paint()
);
}
#override
bool shouldRepaint(Line oldDelegate) {
// Since this Line painter has no fields, it always paints
// the same thing, and therefore we return false here. If
// we had fields (set from the constructor) then we would
// return true if any of them differed from the same
// fields on the oldDelegate.
return false;
}
}

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

Flutter - DragBox Feedback animate to original position

I want to show the animation of feedback of draggable when it is not accepted by the DropTarget.Flutter doesn't show the feedback. Is there any way we can show that or control it. Like this example, I want to achieve this effect. I somehow achieve this effect but it is not proper accurate returning to the original offset. It is moving ahead to its original position.
Animation effect I want.
Here is my Code, I have one drag box when I lift it to a certain position and leave him from there and it should animate back to original position, but it is returning to some other Offset like this.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(body: DragBox()),
);
}
}
class DragBox extends StatefulWidget {
DragBox({
Key key,
}) : super(key: key);
#override
State<StatefulWidget> createState() {
return new _MyDragBox();
}
}
class _MyDragBox extends State<DragBox> with TickerProviderStateMixin {
GlobalKey _globalKey = new GlobalKey();
AnimationController _controller;
Offset begin;
Offset cancelledOffset;
Offset _offsetOfWidget;
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((s) {
_afeteLayout();
});
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 1000),
);
}
void _afeteLayout() {
final RenderBox box = _globalKey.currentContext.findRenderObject();
Offset offset = -box.globalToLocal(Offset(0.0, 0.0));
_offsetOfWidget = offset;
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Center(
child: Draggable(
key: _globalKey,
onDraggableCanceled: (v, o) {
setState(() {
cancelledOffset = o;
});
_controller.forward();
},
feedback: Container(
color: Colors.red,
height: 50,
width: 50,
),
child: Container(
color: Colors.red,
height: 50,
width: 50,
),
),
),
_controller.isAnimating
? SlideTransition(
position: Tween<Offset>(
begin: cancelledOffset * 0.01,
end: _offsetOfWidget * 0.01)
.animate(_controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.stop();
} else {
_controller.reverse();
}
}),
child: Container(
color: Colors.red,
height: 50,
width: 50,
),
)
: Container(
child: Text('data'),
)
],
);
}
}
I think, this documentation about "Animate a widget using a physics simulation" is the closest example suited for what you are trying to achieve.
This recipe demonstrates how to move a widget from a dragged point
back to the center using a spring simulation.
To appreciate it in action, take a look on the example:
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
void main() {
runApp(MaterialApp(home: PhysicsCardDragDemo()));
}
class PhysicsCardDragDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: DraggableCard(
child: FlutterLogo(
size: 128,
),
),
);
}
}
/// A draggable card that moves back to [Alignment.center] when it's
/// released.
class DraggableCard extends StatefulWidget {
final Widget child;
DraggableCard({required this.child});
#override
_DraggableCardState createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
/// The alignment of the card as it is dragged or being animated.
///
/// While the card is being dragged, this value is set to the values computed
/// in the GestureDetector onPanUpdate callback. If the animation is running,
/// this value is set to the value of the [_animation].
Alignment _dragAlignment = Alignment.center;
late Animation<Alignment> _animation;
/// Calculates and runs a [SpringSimulation].
void _runAnimation(Offset pixelsPerSecond, Size size) {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
// Calculate the velocity relative to the unit interval, [0,1],
// used by the animation controller.
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(
mass: 30,
stiffness: 1,
damping: 1,
);
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(simulation);
}
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(() {
setState(() {
_dragAlignment = _animation.value;
});
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return GestureDetector(
onPanDown: (details) {
_controller.stop();
},
onPanUpdate: (details) {
setState(() {
_dragAlignment += Alignment(
details.delta.dx / (size.width / 2),
details.delta.dy / (size.height / 2),
);
});
},
onPanEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size);
},
child: Align(
alignment: _dragAlignment,
child: Card(
child: widget.child,
),
),
);
}
}
Sample output:
A simple way to solve this problem is to create a widget that overrides Draggable and make it a child of an AnimatedPositioned. Here is the example:
import 'package:flutter/material.dart';
class XDraggable extends StatefulWidget {
const XDraggable(
{Key? key,
required this.child,
required this.original_x,
required this.original_y,
this.animation_speed = 200})
: super(key: key);
final Widget child;
final double original_x;
final double original_y;
final double animation_speed;
#override
_XDraggableState createState() => _XDraggableState();
}
class _XDraggableState extends State<XDraggable> {
double x = 200;
double y = 200;
int animation_speed = 0;
#override
void initState() {
x = widget.original_x;
y = widget.original_y;
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedPositioned(
left: x,
top: y,
duration: Duration(milliseconds: animation_speed),
child: Draggable(
onDragUpdate: (details) => {
setState(() {
animation_speed = 0;
x = x + details.delta.dx;
y = y + details.delta.dy;
}),
},
onDragEnd: (details) {
setState(() {
animation_speed = 200;
x = widget.original_x;
y = widget.original_y;
});
},
child: widget.child,
feedback: SizedBox(),
),
);
}
}
Then, just use the widget as a Stack child:
...
child: Stack(
fit: StackFit.expand,
children: [
XDraggable(
original_x: 20,
original_y: 20,
child: Container(
height: 50.0,
width: 50.0,
color: Colors.green,
),
)
],
),
...

How to take screenshot of widget beyond the screen in flutter?

I am using RepaintBoundary to take the screenshot of the current widget which is a listView. But it only captures the content which is visible on the screen at the time.
RepaintBoundary(
key: src,
child: ListView(padding: EdgeInsets.only(left: 10.0),
scrollDirection: Axis.horizontal,
children: <Widget>[
Align(
alignment: Alignment(-0.8, -0.2),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: listLabel(orientation),
)
),
Padding(padding: EdgeInsets.all(5.0)),
Align(
alignment: FractionalOffset(0.3, 0.5),
child: Container(
height: orientation == Orientation.portrait? 430.0: 430.0*0.7,
decoration: BoxDecoration(
border: Border(left: BorderSide(color: Colors.black))
),
//width: 300.0,
child:
Wrap(
direction: Axis.vertical,
//runSpacing: 10.0,
children: colWidget(orientation),
)
)
),
Padding(padding: EdgeInsets.all(5.0)),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: listLabel(orientation),
)
],
),
);
screenshot function:
Future screenshot() async {
RenderRepaintBoundary boundary = src.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List();
print(pngBytes);
final directory = (await getExternalStorageDirectory()).path;
File imgFile =new File('$directory/layout2.pdf');
imgFile.writeAsBytes(pngBytes);
}
Is there any way, so that I can capture the whole listView, i.e., not only the content which is not visible on the screen but the scrollable content also. Or maybe if the whole widget is too large to fit in a picture, it can be captured in multiple images.
I achieve the solution of this problem using this package: Screenshot, that takes a screenshot of the entire widget. It's easy and simple, follow the steps on the PubDev or GitHub and you can make it work.
OBS: To take a full screenshot of the widget make sure that your widget is fully scrollable, and not just a part of it.
(In my case, i had a ListView inside a Container, and the package doesn't take the screenshot of all ListView because i have many itens on it, SO i have wrap my Container inside a SingleChildScrollView and add the NeverScrollableScrollPhysics physics in the ListView and it works! :D).
Screenshot of my screen
More details in this issue
This made me curious whether it was possible so I made a quick mock-up that shows it does work. But please be aware that by doing this you're essentially intentionally breaking the things flutter does to optimize, so you really shouldn't use it beyond where you absolutely have to.
Anyways, here's the code:
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(MyApp());
class UiImagePainter extends CustomPainter {
final ui.Image image;
UiImagePainter(this.image);
#override
void paint(ui.Canvas canvas, ui.Size size) {
// simple aspect fit for the image
var hr = size.height / image.height;
var wr = size.width / image.width;
double ratio;
double translateX;
double translateY;
if (hr < wr) {
ratio = hr;
translateX = (size.width - (ratio * image.width)) / 2;
translateY = 0.0;
} else {
ratio = wr;
translateX = 0.0;
translateY = (size.height - (ratio * image.height)) / 2;
}
canvas.translate(translateX, translateY);
canvas.scale(ratio, ratio);
canvas.drawImage(image, new Offset(0.0, 0.0), new Paint());
}
#override
bool shouldRepaint(UiImagePainter other) {
return other.image != image;
}
}
class UiImageDrawer extends StatelessWidget {
final ui.Image image;
const UiImageDrawer({Key key, this.image}) : super(key: key);
#override
Widget build(BuildContext context) {
return CustomPaint(
size: Size.infinite,
painter: UiImagePainter(image),
);
}
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
GlobalKey<OverRepaintBoundaryState> globalKey = GlobalKey();
ui.Image image;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: image == null
? Capturer(
overRepaintKey: globalKey,
)
: UiImageDrawer(image: image),
floatingActionButton: image == null
? FloatingActionButton(
child: Icon(Icons.camera),
onPressed: () async {
var renderObject = globalKey.currentContext.findRenderObject();
RenderRepaintBoundary boundary = renderObject;
ui.Image captureImage = await boundary.toImage();
setState(() => image = captureImage);
},
)
: FloatingActionButton(
onPressed: () => setState(() => image = null),
child: Icon(Icons.remove),
),
),
);
}
}
class Capturer extends StatelessWidget {
static final Random random = Random();
final GlobalKey<OverRepaintBoundaryState> overRepaintKey;
const Capturer({Key key, this.overRepaintKey}) : super(key: key);
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: OverRepaintBoundary(
key: overRepaintKey,
child: RepaintBoundary(
child: Column(
children: List.generate(
30,
(i) => Container(
color: Color.fromRGBO(random.nextInt(256), random.nextInt(256), random.nextInt(256), 1.0),
height: 100,
),
),
),
),
),
);
}
}
class OverRepaintBoundary extends StatefulWidget {
final Widget child;
const OverRepaintBoundary({Key key, this.child}) : super(key: key);
#override
OverRepaintBoundaryState createState() => OverRepaintBoundaryState();
}
class OverRepaintBoundaryState extends State<OverRepaintBoundary> {
#override
Widget build(BuildContext context) {
return widget.child;
}
}
What it's doing is making a scroll view that encapsulates the list (column), and making sure the repaintBoundary is around the column. With your code where you use a list, there's no way it can ever capture all the children as the list is essentially a repaintBoundary in and of itself.
Note in particular the 'overRepaintKey' and OverRepaintBoundary. You might be able to get away without using it by iterating through render children, but it makes it a lot easier.
There is a simple way
You need wrap SingleChildScrollView Widget to RepaintBoundary. just wrap your Scrollable widget (or his father) with SingleChildScrollView
SingleChildScrollView(
child: RepaintBoundary(
key: _globalKey
)
)

Flutter circle file image with clip oval

I want to clip an image that I extracted from the image picker plugin and it does not work with BoxDecoration.circle , so I want to clip it as circle with oval clipper. How to achive it ?
You can use CircleAvatar widget to display the obtained image to make it circular.
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
void main() => runApp(new MaterialApp(debugShowCheckedModeBanner: false, home: new MyApp()));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
File _image;
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
_image = image;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Home'),
),
body: new Center(
child: _image == null
? new Text('No image selected.')
: new CircleAvatar(backgroundImage: new FileImage(_image), radius: 200.0,),
),
floatingActionButton: new FloatingActionButton(
onPressed: getImage,
tooltip: 'Pick Image',
child: new Icon(Icons.add_a_photo),
),
);
}
}
You can also use ClipOval to circle the image. Just wrap your file image with ClipOval.
ClipOval(
child: FileImage(_image)
),
If you want to make use of BoxDecoration.Circle, this is what you need to do
Container(
width: 120.0,
height: 120.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
fit: BoxFit.fill,
image: FileImage(_image)
)
)
),
I hope this helps
I have figured it out, this a class that I will use later for clipping it's child
class CircleRevealClipper extends CustomClipper<Rect> { CircleRevealClipper();
#override Rect getClip(Size size) {
final epicenter = new Offset(size.width, size.height);
// Calculate distance from epicenter to the top left corner to make sure clip the image into circle.
final distanceToCorner = epicenter.dy;
final radius = distanceToCorner;
final diameter = radius;
return new Rect.fromLTWH(
epicenter.dx - radius, epicenter.dy - radius, diameter, diameter); }
#override bool shouldReclip(CustomClipper<Rect> oldClipper) {
return true; } }

Flutter - Set the scale of a widget

I am trying to create an animation that "pops" the widget to the front and returns to it
import "package:flutter/material.dart";
class ScoreCounter extends StatefulWidget {
#override
_ScoreCounter createState() => new _ScoreCounter();
}
class _ScoreCounter extends State<ScoreCounter> with SingleTickerProviderStateMixin{
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..forward();
}
#override
build(BuildContext context){
return new AnimatedBuilder(
animation: _controller,
child: new Container(width: 200.0, height: 200.0, color: Colors.green),
builder: (BuildContext context, Widget child) {
//What to return that scales the element
},
);
}
}
For rotating, I would use a Transform and return a Matrix. But what should I return to accomplish the scaling animation?
Thanks in advance
Just as a alternative, to set just scale for a specific Widget.
Transform.scale(
scale: 1.0,
child: Container(
height: 200.0,
width: 200.0,
color: Colors.pink,
),
),
If you're going to size your contents manually you could just read controller.value in your builder function and use that to set the size of the container.
Alternatively, you could consider a pair of SizeTransitions for each axis. The Align class may also be useful because you can set a sizeFactor in each dimension.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new HomePage(),
));
}
class HomePage extends StatefulWidget {
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.sort),
onPressed: () {},
),
body: new Center(
child: new ScoreCounter(),
),
);
}
}
class ScoreCounter extends StatefulWidget {
#override
_ScoreCounter createState() => new _ScoreCounter();
}
class _ScoreCounter extends State<ScoreCounter>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)
..forward();
}
#override
build(BuildContext context){
return new AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
double size = _controller.value * 200.0;
return new Container(width: size, height: size, color: Colors.green);
},
);
}
}
Another Approach is to create generalized Scale Transformation.
Simply add this method to your component
Matrix4 scaleXYZTransform({
double scaleX = 1.00,
double scaleY = 1.00,
double scaleZ = 1.00,
}) {
return Matrix4.diagonal3Values(scaleX, scaleY, scaleZ);
}
Now you can easily Scale any widget by wrapping it:
Transform(
transform: scaleXYZTransform(scaleX: .5, scaleY: .5),
child: Container(
child: myAwesomeWidget(),
));

Resources