How to add Signature in flutter? - flutter

I have implemented signature_pad in my flutter project and it works fine.
Unfortunately when I place it inside SingleChildScrollView, the signature was not drawn. It scrolled instead of signed.
It seems like is the GestureDetector but I have no idea how to fix it.
Can someone give me some clue on this?
Thanks.

Signature Class need to be modified to respond to VerticalDrag , I renamed it to Signature1
now signature area pad should not scroll , you can check the complete code below as it behaves. you will find out that Signature area is no more scrolling with the SingleChildScrollView.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:async';
import 'dart:ui' as ui;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var color = Colors.black;
var strokeWidth = 3.0;
final _sign = GlobalKey<Signature1State>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body:
SingleChildScrollView(
child: Column(
children: <Widget>[
_showCategory(),
SizedBox(height: 15),
_showCategory(),
SizedBox(height: 15),
_showCategory(),
SizedBox(height: 15),
_showCategory(),
SizedBox(height: 15),
_showCategory(),
_showCategory(),
SizedBox(height: 15),
_showCategory(),
SizedBox(height: 15),
_showCategory(),
SizedBox(height: 15),
_showCategory(),
SizedBox(height: 15),
_showCategory(),
_showSignaturePad()
],
),
)
,
);
}
Widget _showCategory() {
return TextField(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
style: TextStyle(fontSize: 12.0, height: 1.0),
decoration: InputDecoration(hintText: "TextView"));
}
Widget _showSignaturePad() {
return Container(
width: double.infinity,
height: 200,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 200,
//color: Colors.red,
child: Signature1(
color: color,
key: _sign,
strokeWidth: strokeWidth,
),
),
),
color: Colors.grey.shade300,
);
}
}
class Signature1 extends StatefulWidget {
final Color color;
final double strokeWidth;
final CustomPainter backgroundPainter;
final Function onSign;
Signature1({
this.color = Colors.black,
this.strokeWidth = 5.0,
this.backgroundPainter,
this.onSign,
Key key,
}) : super(key: key);
Signature1State createState() => Signature1State();
static Signature1State of(BuildContext context) {
return context.findAncestorStateOfType<Signature1State>();
}
}
class _SignaturePainter extends CustomPainter {
Size _lastSize;
final double strokeWidth;
final List<Offset> points;
final Color strokeColor;
Paint _linePaint;
_SignaturePainter({#required this.points, #required this.strokeColor, #required this.strokeWidth}) {
_linePaint = Paint()
..color = strokeColor
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;
}
#override
void paint(Canvas canvas, Size size) {
_lastSize = size;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) canvas.drawLine(points[i], points[i + 1], _linePaint);
}
}
#override
bool shouldRepaint(_SignaturePainter other) => other.points != points;
}
class Signature1State extends State<Signature1> {
List<Offset> _points = <Offset>[];
_SignaturePainter _painter;
Size _lastSize;
Signature1State();
void _onDragStart(DragStartDetails details){
RenderBox referenceBox = context.findRenderObject();
Offset localPostion = referenceBox.globalToLocal(details.globalPosition);
setState(() {
_points = List.from(_points)
..add(localPostion)
..add(localPostion);
});
}
void _onDragUpdate (DragUpdateDetails details) {
RenderBox referenceBox = context.findRenderObject();
Offset localPosition = referenceBox.globalToLocal(details.globalPosition);
setState(() {
_points = List.from(_points)..add(localPosition);
if (widget.onSign != null) {
widget.onSign();
}
});
}
void _onDragEnd (DragEndDetails details) => _points.add(null);
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => afterFirstLayout(context));
_painter = _SignaturePainter(points: _points, strokeColor: widget.color, strokeWidth: widget.strokeWidth);
return ClipRect(
child: CustomPaint(
painter: widget.backgroundPainter,
foregroundPainter: _painter,
child: GestureDetector(
onVerticalDragStart: _onDragStart,
onVerticalDragUpdate: _onDragUpdate,
onVerticalDragEnd: _onDragEnd,
onPanStart: _onDragStart,
onPanUpdate: _onDragUpdate,
onPanEnd: _onDragEnd
),
),
);
}
Future<ui.Image> getData() {
var recorder = ui.PictureRecorder();
var origin = Offset(0.0, 0.0);
var paintBounds = Rect.fromPoints(_lastSize.topLeft(origin), _lastSize.bottomRight(origin));
var canvas = Canvas(recorder, paintBounds);
if(widget.backgroundPainter != null) {
widget.backgroundPainter.paint(canvas, _lastSize);
}
_painter.paint(canvas, _lastSize);
var picture = recorder.endRecording();
return picture.toImage(_lastSize.width.round(), _lastSize.height.round());
}
void clear() {
setState(() {
_points = [];
});
}
bool get hasPoints => _points.length > 0;
List<Offset> get points => _points;
afterFirstLayout(BuildContext context) {
_lastSize = context.size;
}
}

you need to create a CustomGestureDetector.
Check this updated version of Signature that I just changed to you:
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class Signature extends StatefulWidget {
final Color color;
final double strokeWidth;
final CustomPainter backgroundPainter;
final Function onSign;
Signature({
this.color = Colors.black,
this.strokeWidth = 5.0,
this.backgroundPainter,
this.onSign,
Key key,
}) : super(key: key);
SignatureState createState() => SignatureState();
static SignatureState of(BuildContext context) {
return context.findAncestorStateOfType<SignatureState>();
}
}
class CustomPanGestureRecognizer extends OneSequenceGestureRecognizer {
final Function onPanStart;
final Function onPanUpdate;
final Function onPanEnd;
CustomPanGestureRecognizer({#required this.onPanStart, #required this.onPanUpdate, #required this.onPanEnd});
#override
void addPointer(PointerEvent event) {
onPanStart(event.position);
startTrackingPointer(event.pointer);
resolve(GestureDisposition.accepted);
}
#override
void handleEvent(PointerEvent event) {
if (event is PointerMoveEvent) {
onPanUpdate(event.position);
}
if (event is PointerUpEvent) {
onPanEnd(event.position);
stopTrackingPointer(event.pointer);
}
}
#override
String get debugDescription => 'customPan';
#override
void didStopTrackingLastPointer(int pointer) {}
}
class _SignaturePainter extends CustomPainter {
Size _lastSize;
final double strokeWidth;
final List<Offset> points;
final Color strokeColor;
Paint _linePaint;
_SignaturePainter({#required this.points, #required this.strokeColor, #required this.strokeWidth}) {
_linePaint = Paint()
..color = strokeColor
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;
}
#override
void paint(Canvas canvas, Size size) {
_lastSize = size;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) canvas.drawLine(points[i], points[i + 1], _linePaint);
}
}
#override
bool shouldRepaint(_SignaturePainter other) => other.points != points;
}
class SignatureState extends State<Signature> {
List<Offset> _points = <Offset>[];
_SignaturePainter _painter;
Size _lastSize;
SignatureState();
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => afterFirstLayout(context));
_painter = _SignaturePainter(points: _points, strokeColor: widget.color, strokeWidth: widget.strokeWidth);
return ClipRect(
child: CustomPaint(
painter: widget.backgroundPainter,
foregroundPainter: _painter,
child: RawGestureDetector(
gestures: {
CustomPanGestureRecognizer: GestureRecognizerFactoryWithHandlers<CustomPanGestureRecognizer>(
() => CustomPanGestureRecognizer(
onPanStart: (position) {
RenderBox referenceBox = context.findRenderObject();
Offset localPostion = referenceBox.globalToLocal(position);
setState(() {
_points = List.from(_points)..add(localPostion)..add(localPostion);
});
return true;
},
onPanUpdate: (position) {
RenderBox referenceBox = context.findRenderObject();
Offset localPosition = referenceBox.globalToLocal(position);
setState(() {
_points = List.from(_points)..add(localPosition);
if (widget.onSign != null) {
widget.onSign();
}
});
},
onPanEnd: (position) {
_points.add(null);
},
),
(CustomPanGestureRecognizer instance) {},
),
},
),
),
);
}
Future<ui.Image> getData() {
var recorder = ui.PictureRecorder();
var origin = Offset(0.0, 0.0);
var paintBounds = Rect.fromPoints(_lastSize.topLeft(origin), _lastSize.bottomRight(origin));
var canvas = Canvas(recorder, paintBounds);
if (widget.backgroundPainter != null) {
widget.backgroundPainter.paint(canvas, _lastSize);
}
_painter.paint(canvas, _lastSize);
var picture = recorder.endRecording();
return picture.toImage(_lastSize.width.round(), _lastSize.height.round());
}
void clear() {
setState(() {
_points = [];
});
}
bool get hasPoints => _points.length > 0;
List<Offset> get points => _points;
afterFirstLayout(BuildContext context) {
_lastSize = context.size;
}
}
Special attention to CustomPanGestureRecognizer
You can read more in:
Gesture Disambiguation

This is happening because the gesture from SingleChildScrollView overrides your Signature widget’s gesture as the SingleChildScrollView is the parent. There are few ways to solve it as in the other responses in this thread. But the easiest one is using the existing package. You can simply use the below Syncfusion's Flutter SignaturePad widget which I am using now for my application. This widget will work on Android, iOS, and web platforms.
Package - https://pub.dev/packages/syncfusion_flutter_signaturepad
Features - https://www.syncfusion.com/flutter-widgets/flutter-signaturepad
Documentation - https://help.syncfusion.com/flutter/signaturepad/getting-started

Related

Flutter moving point over image

I want to create a moving point that moves across an image. The picture is drawn on canvas. The point should approach the specified x and y coordinates.Image and point are generated. The point should move to the new specified position every 500ms by a timer.But it only moves when I move the mouse over buttons on the app.
class five_og extends StatefulWidget {
#override
State createState() {
return _five_og();
}
}
class _five_og extends State<five_og> {
ui.Image? image;
Timer? timer;
void refreshImage() {
if (i >= xList.length - 1) {
i = 0;
}
ImagePainter(image!);
i++;
}
#override
void dispose() {
super.dispose();
timer!.cancel();
}
#override
void initState() {
super.initState();
loadImage('assets/images/Erdgeschoss.png');
timer = Timer.periodic(
Duration(milliseconds: 500),
(timer) {
refreshImage();
},
);
}
Future loadImage(String path) async {
final data = await rootBundle.load(path);
final bytes = data.buffer.asUint8List();
final image = await decodeImageFromList(bytes);
setState(() {
this.image = image;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Test'),
),
body: Center(
child: image == null
? CircularProgressIndicator()
: Container(
height: 600,
width: 600,
child: FittedBox(
child: SizedBox(
width: image!.width.toDouble(),
height: image!.width.toDouble(),
child: CustomPaint(
painter: ImagePainter(image!),
),
),
)
)
),
);
}
}
class ImagePainter extends CustomPainter {
final ui.Image image;
const ImagePainter(this.image);
#override
void paint(Canvas canvas, Size size) {
//final paint = Paint();
canvas.drawImage(image, Offset.zero, Paint());
final paint = Paint()
..color = Color.fromARGB(255, 15, 182, 253)
..style = PaintingStyle.fill;
canvas.drawCircle(Offset(xList[i], yList[i]), 13, paint);
}
#override
bool shouldRepaint(ImagePainter oldDelegate) {
return false;
}
}

Is it possible to add a Draggable and DragTarget widget inside the Customized Containers in Flutter?

Here is the code of Customized Three Containers using ClipPath for obtaining alphabet "A". This Code can also draw inside these three containers. But the drawing can be done in any direction. I want to make this code into drawing from top to bottom inside first container ,when the first drawing done then the second container is activated, after drawing of second Container then the third one to be activated (Tracing Alphabet). I think there should be 3 pairs of Draggable and DragTarget. Drawing is done by using GestureDetector. Is there is any way draw line using Draggable widget, Please respond anyone konws the solution
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'package:flutter/services.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setEnabledSystemUIOverlays ([]);
SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeRight,DeviceOrientation.landscapeLeft]).then((_){
runApp(MyApp());
});
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Offset> points1 = [];
List<Offset> points2 = [];
List<Offset> points3 = [];
#override
Widget build(BuildContext context) {
final double width = MediaQuery.of(context).size.width;
final double height = MediaQuery.of(context).size.height;
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: (){
points1.clear();
points2.clear();
points3.clear();
},
),
backgroundColor: Colors.teal,
body: SafeArea(
child:Center(
child: Stack(
children: [
Positioned(
left: MediaQuery.of(context).size.width*0.381,
top:MediaQuery.of(context).size.height*0.560,
child: ClipPath(
clipper: CPath3(),
child: Container(
height: height*0.07,
width: width*0.18,
child: GestureDetector(
onPanStart: (details){
this.setState(() {
points3.add(details.localPosition);
});
},
onPanUpdate: (details){
this.setState(() {
points3.add(details.localPosition);
});
},
onPanEnd: (details){
this.setState(() {
points3.add(null);
});
},
child: CustomPaint(
painter: MyPainter3(points3: points3),
),
),
),
),
),//THIRD PATH
Positioned(
left: MediaQuery.of(context).size.width*0.283,
// bottom: 0,
top: MediaQuery.of(context).size.height*0.194,
//right: 0,
child: ClipPath(
clipper: CPath1(),
child: Container(
height: height*0.60,
width: width*0.20,
child: GestureDetector(
onPanStart: (details){
this.setState(() {
points1.add(details.localPosition);
});
},
onPanUpdate: (details){
this.setState(() {
points1.add(details.localPosition);
});
},
onPanEnd: (details){
this.setState(() {
points1.add(null);
});
},
child: CustomPaint(
painter: MyPainter1(points1: points1),
),
),
),
),
),//FIRST PATH
Positioned(
left: MediaQuery.of(context).size.width*0.467,
// bottom: 0,
top: MediaQuery.of(context).size.height*0.194,
//right: 0,
child: ClipPath(
clipper: CPath2(),
child: Container(
height: height*0.60,
width: width*0.20,
child: GestureDetector(
onPanStart: (details){
this.setState(() {
points2.add(details.localPosition);
});
},
onPanUpdate: (details){
this.setState(() {
points2.add(details.localPosition);
});
},
onPanEnd: (details){
this.setState(() {
points2.add(null);
});
},
child: CustomPaint(
painter: MyPainter2(points2: points2),
),
),
),
),
),//SECOND PATH
],
),
),
),
);
}
}
class CPath1 extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path_0 = Path();
path_0.moveTo(0,size.height);
path_0.lineTo(size.width*0.2983628,size.height);
path_0.lineTo(size.width,0);
path_0.lineTo(size.width*0.7591676,0);
path_0.lineTo(0,size.height);
path_0.close();
return path_0;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
// TODO: implement shouldReclip
return true;
}
}
class MyPainter1 extends CustomPainter {
List<Offset> points1;
MyPainter1({this.points1});
#override
void paint(Canvas canvas, Size size) {
Paint background = Paint()..color = Colors.white;
Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.drawRect(rect, background);
Paint paint = Paint();
paint.color = Colors.deepPurpleAccent;
paint.strokeWidth = 70;
paint.isAntiAlias = true;
paint.strokeCap = StrokeCap.round;
for(int x=0;x<points1.length-1;x++)
{
if(points1[x] != null && points1[x+1] != null)
{
canvas.drawLine(points1[x], points1[x+1], paint);
}
else if(points1[x] != null && points1[x+1] == null)
{
canvas.drawPoints(ui.PointMode.points,[points1[x]], paint);
}
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
class CPath2 extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path_0 = Path();
path_0.moveTo(size.width*0.6738603,size.height);
path_0.lineTo(size.width,size.height);
path_0.lineTo(size.width*0.1934179,0);
path_0.lineTo(size.width*0.08001079,0);
path_0.lineTo(0,size.height*0.1078064);
path_0.lineTo(size.width*0.6738603,size.height);
path_0.close();
return path_0;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
// TODO: implement shouldReclip
return true;
}
}
class MyPainter2 extends CustomPainter {
List<Offset> points2;
MyPainter2({this.points2});
#override
void paint(Canvas canvas, Size size) {
Paint background = Paint()..color = Colors.white;
Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.drawRect(rect, background);
Paint paint = Paint();
paint.color = Colors.deepPurpleAccent;
paint.strokeWidth = 70;
paint.isAntiAlias = true;
paint.strokeCap = StrokeCap.round;
for(int x=0;x<points2.length-1;x++)
{
if(points2[x] != null && points2[x+1] != null)
{
canvas.drawLine(points2[x], points2[x+1], paint);
}
else if(points2[x] != null && points2[x+1] == null)
{
canvas.drawPoints(ui.PointMode.points,[points2[x]], paint);
}
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
class CPath3 extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path_0 = Path();
path_0.moveTo(size.width*0.9108546,0);
path_0.lineTo(size.width*0.08746101,0);
path_0.lineTo(0,size.height);
path_0.lineTo(size.width,size.height*0.9771505);
path_0.lineTo(size.width*0.9108546,0);
path_0.close();
return path_0;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
// TODO: implement shouldReclip
return true;
}
}
class MyPainter3 extends CustomPainter {
List<Offset> points3;
MyPainter3({this.points3});
#override
void paint(Canvas canvas, Size size) {
Paint background = Paint()..color = Colors.white;
Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.drawRect(rect, background);
Paint paint = Paint();
paint.color = Colors.deepPurpleAccent;
paint.strokeWidth = 70;
paint.isAntiAlias = true;
paint.strokeCap = StrokeCap.round;
for(int x=0;x<points3.length-1;x++)
{
if(points3[x] != null && points3[x+1] != null)
{
canvas.drawLine(points3[x], points3[x+1], paint);
}
else if(points3[x] != null && points3[x+1] == null)
{
canvas.drawPoints(ui.PointMode.points,[points3[x]], paint);
}
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}

Update CustomPaint drawing

I have a Problem with the CustomPainter Widget. I want to draw a PieChart which works fine, then I added a Variable which draws the Chart to until it reached this angle. Now I want to animate it, I used the Future.delayed function and in there with setState I wanted to update the variable but that doesn't work unfortunately.
I am developing for the web. Thanks for helping!
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:stats/data/listLanguages.dart';
import 'painter/pieChartPainter.dart';
class Chart extends StatefulWidget {
ListLanguages listLanguages;
Chart({ListLanguages listLanguages}) {
if (listLanguages == null) {
listLanguages = new ListLanguages();
}
this.listLanguages = listLanguages;
}
#override
_ChartState createState() => _ChartState();
}
class _ChartState extends State<Chart> {
#override
Widget build(BuildContext context) {
List angles = widget.listLanguages.calcCounts();
int angle = 0;
Future.delayed(new Duration(seconds: 2), (){
setState(() {
angle = 360;
print("test");
});
});
return Column(
children: [
Spacer(flex: 2),
Row(
children: [
Spacer(),
CustomPaint(
size: Size.square(400),
painter: PieChartPainter(
angles: angles,
colors: new List()
..add(Colors.green)
..add(Colors.blue)
..add(Colors.brown)
..add(Colors.pink)
..add(Colors.orange)
..add(Colors.grey.shade700),
angle: angle,
),
),
Spacer(flex: 10),
],
),
Spacer(flex: 3),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math.dart' as vm;
class PieChartPainter extends CustomPainter {
List angles, colors;
int angle;
PieChartPainter(
{#required List angles, #required List colors, int angle: 360}) {
this.angles = angles;
this.colors = colors;
this.angle = angle;
}
#override
void paint(Canvas canvas, Size size) {
Paint p = new Paint();
double start = -90;
double tmp = 0;
for (int i = 0; i < angles.length; i++) {
if (i < 5) {
p.color = colors[i];
} else {
p.color = colors[5];
}
if (tmp + angles[i] < angle) {
canvas.drawArc(Rect.fromLTRB(0, 0, size.width, size.height),
vm.radians(start), vm.radians(angles[i]), true, p);
start = start + angles[i];
tmp = tmp + angles[i];
} else {
double x = angle - tmp;
canvas.drawArc(Rect.fromLTRB(0, 0, size.width, size.height),
vm.radians(start), vm.radians(x), true, p);
return;
}
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
this is the complete code I have to create the Pie Chart
You can copy paste run full code below
In your case, to work with Future.delayed, you can move logic from build to initState and use addPostFrameCallback
working demo change angle in 2, 4, 6 seconds and angle is 150, 250, 360
code snippet
class _ChartState extends State<Chart> {
int angle = 0;
List angles;
#override
void initState() {
angles = widget.listLanguages.calcCounts();
WidgetsBinding.instance.addPostFrameCallback((_) {
Future.delayed(Duration(seconds: 2), () {
setState(() {
angle = 150;
});
});
Future.delayed(Duration(seconds: 4), () {
setState(() {
angle = 250;
});
});
Future.delayed(Duration(seconds: 6), () {
setState(() {
angle = 360;
});
});
});
working demo
full code
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math.dart' as vm;
class ListLanguages {
List calcCounts() {
return [10.0, 20.0, 100.0, 150.0, 250.0, 300.0];
}
}
class Chart extends StatefulWidget {
ListLanguages listLanguages;
Chart({ListLanguages listLanguages}) {
if (listLanguages == null) {
listLanguages = ListLanguages();
}
this.listLanguages = listLanguages;
}
#override
_ChartState createState() => _ChartState();
}
class _ChartState extends State<Chart> {
int angle = 0;
List angles;
#override
void initState() {
angles = widget.listLanguages.calcCounts();
WidgetsBinding.instance.addPostFrameCallback((_) {
Future.delayed(Duration(seconds: 2), () {
print("delay");
setState(() {
angle = 150;
print("test");
});
});
Future.delayed(Duration(seconds: 4), () {
print("delay");
setState(() {
angle = 250;
print("test");
});
});
Future.delayed(Duration(seconds: 6), () {
print("delay");
setState(() {
angle = 360;
print("test");
});
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Spacer(flex: 2),
Row(
children: [
Spacer(),
CustomPaint(
size: Size.square(400),
painter: PieChartPainter(
angles: angles,
colors: List()
..add(Colors.green)
..add(Colors.blue)
..add(Colors.brown)
..add(Colors.pink)
..add(Colors.orange)
..add(Colors.grey.shade700),
angle: angle,
),
),
Spacer(flex: 10),
],
),
Spacer(flex: 3),
],
);
}
}
class PieChartPainter extends CustomPainter {
List angles, colors;
int angle;
PieChartPainter(
{#required List angles, #required List colors, int angle: 360}) {
this.angles = angles;
this.colors = colors;
this.angle = angle;
}
#override
void paint(Canvas canvas, Size size) {
Paint p = Paint();
double start = -90;
double tmp = 0;
for (int i = 0; i < angles.length; i++) {
if (i < 5) {
p.color = colors[i];
} else {
p.color = colors[5];
}
if (tmp + angles[i] < angle) {
canvas.drawArc(Rect.fromLTRB(0, 0, size.width, size.height),
vm.radians(start), vm.radians(angles[i]), true, p);
start = start + angles[i];
tmp = tmp + angles[i];
} else {
double x = angle - tmp;
canvas.drawArc(Rect.fromLTRB(0, 0, size.width, size.height),
vm.radians(start), vm.radians(x), true, p);
return;
}
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Chart(
listLanguages: ListLanguages(),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
I can not use your code so that I can run it (since it's only a small part) but what you need is:
Define an animation and animation controller in your state
Surround your CustomPainter with an "AnimatedBuilder" which will use this animation and will pass the value between 0 to 360 to your CustomPainter in 2 seconds.
Below is an example with comments (which you will have to take parts from and put in to your widget).
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
// NOTE: You need to add "SingleTickerProviderStateMixin" for animation to work
class _TestState extends State<Test> with SingleTickerProviderStateMixin {
Animation _animation; // Stores animation
AnimationController _controller; // Stores controller
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
); // Create a 2 second duration controller
_animation = IntTween(begin: 0, end: 360)
.animate(_controller); // Create the animation using controller with a tween from 0 to 360
WidgetsBinding.instance.addPostFrameCallback((_) {
_controller.forward(); // Start the animation when widget is displayed
});
}
#override
void dispose() {
_controller.dispose(); // Don't forget to dispose your controller
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder( // AnimatedBuilder using the animation
animation: _animation,
builder: (context, _){
return CustomPaint(
size: Size.square(400),
painter: PieChartPainter(
angles: angles,
colors: new List()
..add(Colors.green)
..add(Colors.blue)
..add(Colors.brown)
..add(Colors.pink)
..add(Colors.orange)
..add(Colors.grey.shade700),
angle: _animation.value, // Pass _animation.value (0 to 360) as your angle
),
);
},
);
}
}

Flutter Custom Painter drawing is laggy

I am trying to code a drawing app, in which users can choose different pen color and draw colorful drawings. I have created a class PointsGroup which stores list of offsets and associated color. In GestureDetector's onPanUpdate, the PointsGroup is appended to list of PointsGroup and passed to SignaturePainter.
But the drawing is bit laggy, it is not drawn as soon as pen moves.
You can see the video https://free.hubcap.video/v/LtOqoEj9H0dY9F9xC_jSst9HT3tSOJlTi
import 'package:flutter/material.dart';
List<Color> colorList = [
Colors.indigo,
Colors.blue,
Colors.green,
Colors.yellow,
Colors.orange,
Colors.red
];
void main() => runApp(MaterialApp(
home: HomePage(),
debugShowCheckedModeBanner: false,
));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Offset> _points = <Offset>[];
List<Offset> _setPoints = <Offset>[];
List<PointsGroup> _ptsGroupList = <PointsGroup>[];
int startIndex;
int endIndex;
#override
void initState() {
ColorChoser.penColor = Colors.black;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
GestureDetector(
onPanStart: (details) {
setState(() {
_points.clear();
startIndex = _ptsGroupList.length;
ColorChoser.showColorSelector = false;
});
},
onPanUpdate: (DragUpdateDetails details) {
setState(() {
RenderBox object = context.findRenderObject();
Offset _localPosition =
object.globalToLocal(details.globalPosition);
_points = new List.from(_points)..add(_localPosition);
_setPoints = new List.from(_points);
_ptsGroupList.add(new PointsGroup(
setPoints: _setPoints, setColor: ColorChoser.penColor));
});
},
onPanEnd: (DragEndDetails details) {
setState(() {
_points.add(null);
ColorChoser.showColorSelector = true;
endIndex = _ptsGroupList.length;
if (startIndex < endIndex) {
_ptsGroupList.replaceRange(
startIndex, endIndex - 1, [_ptsGroupList.removeLast()]);
}
});
},
child: CustomPaint(
painter: SignaturePainter(grpPointsList: _ptsGroupList),
size: Size.infinite,
),
),
ColorChoser(),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.undo),
onPressed: () {
setState(() {
if (_ptsGroupList.length > 0) {
_ptsGroupList.removeLast();
}
});
}),
);
}
}
class ColorChoser extends StatefulWidget {
const ColorChoser({
Key key,
}) : super(key: key);
static Color backgroundColor = Colors.white;
static Color penColor = Colors.blue;
static bool showColorSelector = true;
#override
_ColorChoserState createState() => _ColorChoserState();
}
class _ColorChoserState extends State<ColorChoser> {
#override
Widget build(BuildContext context) {
return Visibility(
visible: ColorChoser.showColorSelector,
child: Positioned(
bottom: 0,
left: 0,
width: MediaQuery.of(context).size.width,
child: Container(
height: 60,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: colorList.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
setState(() {
ColorChoser.penColor = colorList[index];
});
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4.0, vertical: 5.0),
child: Container(
color: colorList[index],
// height: 30,
width: 45,
),
),
);
}),
),
),
);
}
}
class SignaturePainter extends CustomPainter {
List<Offset> points;
List<PointsGroup> grpPointsList = <PointsGroup>[];
var paintObj;
SignaturePainter({
this.grpPointsList = const [],
});
#override
void paint(Canvas canvas, Size size) {
for (PointsGroup pts in grpPointsList) {
points = pts.setPoints;
paintObj = Paint()
..color = pts.setColor
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], paintObj);
}
}
}
}
#override
bool shouldRepaint(SignaturePainter oldDelegate) =>
oldDelegate.points != points;
}
class PointsGroup {
List<Offset> setPoints = <Offset>[];
Color setColor;
PointsGroup({this.setPoints, this.setColor});
}
Also the drawing is not shown for the very first draw. As soon as pen
is lifted it starts showing.
P.S. If there is any alternate way is to achieve the desired multi-colored drawing, it will be okay.
You are clearing all the points whenever onPanStart is triggered (when the user places their finger on the screen). If you remove _points.clear() from onPanStart: (details) {} you will retain all the points that the user draws.
The application begins to lag and framerate is impacted after many points are drawn. You'll notice this when the user has drawn a decent amount on the canvas. To prevent lag from occurring, one strategy is to reduce the number of points being drawn. You can halve the number of points and still give the user the autonomy to draw what they desire by doing this:
final int THRESHOLD = 2;
if (totalPoints % THRESHOLD == 0){
_points = new List.from(_points)..add(_localPosition);
}
totalPoints is a counter you increment by one in onPanUpdate: (details) {}
Another technique is to wrap the subclass widget that extends CustomPainter, in this case CustomPaint, with a RepaintBoundary widget https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html. This widget will ensure that only the regions of the canvas where painting occurs is redrawn when needed. By limiting refresh rendering to one widget, you will speed up the process and deliver better results.
RepaintBoundary(
child: CustomPaint(
isComplex: true,
willChange: false,
painter: Painter(
points: _points,
),
),
),

Flutter DropDown button for account settings

Want to design a custom drop down button for account settings with logout option.
How to change drop down list background color?
How to customize drop down list?
DropdownButton(
hint: ButtonText(
buttonText: "Select a option",
textColor: ThemeColors.grey8,
buttonFontSize: 20.0,
),
value: _selectedOption,
onChanged: (value) {
setState(() {
_selectedOption = value;
});
},
items: _options.map((option) {
return DropdownMenuItem(
child: ButtonText(
buttonText: option,
buttonFontSize: 16.0,
textColor: ThemeColors.grey8,
),
value: option,
);
}).toList(),
i want design a custom drop down button with a account dashboard.
I had a similar situation. I needed to implement a dropdown dialog. I used the DropdownButton sources and modified them.
My widget:
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
const double _hintWidgetHeight = 34;
const double _hintWidgetWidth = 140;
class MyHintWidget extends StatefulWidget {
const MyHintWidget({
Key key,
#required this.title,
#required this.style,
#required this.child,
}) : super(key: key);
final String title;
final TextStyle style;
final Widget child;
#override
_MyHintState createState() => _MyHintState();
}
class _MyHintState extends State<MyHintWidget> with WidgetsBindingObserver {
static const EdgeInsets _hintItemPadding = EdgeInsets.symmetric(horizontal: 16.0);
_HintRoute route;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_removeRoute();
super.dispose();
}
#override
void didChangeMetrics() {
_removeRoute();
}
void _removeRoute() {
route?._dismiss();
route = null;
}
void _handleTap() {
final RenderBox itemBox = context.findRenderObject() as RenderBox;
final Rect itemRect = itemBox.localToGlobal(Offset.zero) & itemBox.size;
final TextDirection textDirection = Directionality.of(context);
route = _HintRoute(
title: widget.title,
style: widget.style,
buttonRect: _hintItemPadding.resolve(textDirection).inflateRect(itemRect),
padding: _hintItemPadding.resolve(textDirection),
theme: ThemeData(
brightness: Brightness.light,
),
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
);
Navigator.push(context, route).then<void>((_) {
route = null;
});
}
#override
Widget build(BuildContext context) {
return Semantics(
button: true,
child: GestureDetector(
onTap: _handleTap,
behavior: HitTestBehavior.opaque,
child: widget.child,
),
);
}
}
class _HintRoute extends PopupRoute<void> {
_HintRoute({
#required this.title,
#required this.style,
this.padding,
this.buttonRect,
this.theme,
this.barrierLabel,
});
final String title;
final TextStyle style;
final EdgeInsetsGeometry padding;
final Rect buttonRect;
final ThemeData theme;
#override
Duration get transitionDuration => const Duration(milliseconds: 300);
#override
bool get barrierDismissible => true;
#override
Color get barrierColor => null;
#override
final String barrierLabel;
#override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
return _HintRoutePage(
title: title,
style: style,
route: this,
constraints: constraints,
padding: padding,
buttonRect: buttonRect,
theme: theme,
);
});
}
void _dismiss() => navigator?.removeRoute(this);
}
class _HintRoutePage extends StatelessWidget {
const _HintRoutePage({
Key key,
#required this.title,
#required this.style,
this.route,
this.constraints,
this.padding,
this.buttonRect,
this.theme,
}) : super(key: key);
final String title;
final TextStyle style;
final _HintRoute route;
final BoxConstraints constraints;
final EdgeInsetsGeometry padding;
final Rect buttonRect;
final ThemeData theme;
#override
Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
final double availableHeight = constraints.maxHeight;
final double maxHintHeight = availableHeight - 2.0 * _hintWidgetHeight;
final double buttonTop = buttonRect.top;
final double buttonBottom = math.min(buttonRect.bottom, availableHeight);
final double topLimit = math.min(_hintWidgetHeight, buttonTop);
final double bottomLimit = math.max(availableHeight - _hintWidgetHeight, buttonBottom);
final double selectedItemOffset = _hintWidgetHeight + kMaterialListPadding.top;
double hintTop =
(buttonTop - selectedItemOffset) - (_hintWidgetHeight * 2 - buttonRect.height) / 3;
final double preferredHintHeight = _hintWidgetHeight + kMaterialListPadding.vertical;
final double hintHeight = math.min(maxHintHeight, preferredHintHeight);
double hintBottom = hintTop + hintHeight;
if (hintTop < topLimit) hintTop = math.min(buttonTop, topLimit);
if (hintBottom > bottomLimit) {
hintBottom = math.max(buttonBottom, bottomLimit);
hintTop = hintBottom - hintHeight;
}
final TextDirection textDirection = Directionality.of(context);
Widget hint = _HintWidget(
title: title,
style: style,
route: route,
padding: padding.resolve(textDirection),
);
if (theme != null) hint = Theme(data: theme, child: hint);
return MediaQuery.removePadding(
context: context,
removeTop: true,
removeBottom: true,
removeLeft: true,
removeRight: true,
child: Builder(
builder: (BuildContext context) {
return CustomSingleChildLayout(
delegate: _HintRouteLayout(
buttonRect: buttonRect,
hintTop: hintTop,
hintHeight: hintHeight,
textDirection: textDirection,
),
child: hint,
);
},
),
);
}
}
class _HintRouteLayout extends SingleChildLayoutDelegate {
_HintRouteLayout({
#required this.buttonRect,
#required this.hintTop,
#required this.hintHeight,
#required this.textDirection,
});
final Rect buttonRect;
final double hintTop;
final double hintHeight;
final TextDirection textDirection;
#override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
final double maxHeight = math.max(0.0, constraints.maxHeight - 2 * _hintWidgetHeight);
const double width = _hintWidgetWidth;
return BoxConstraints(
minWidth: width,
maxWidth: width,
minHeight: 0.0,
maxHeight: maxHeight,
);
}
#override
Offset getPositionForChild(Size size, Size childSize) {
assert(() {
final Rect container = Offset.zero & size;
if (container.intersect(buttonRect) == buttonRect) {
assert(hintTop >= 0.0);
assert(hintTop + hintHeight <= size.height);
}
return true;
}());
assert(textDirection != null);
double left;
if (buttonRect.left > size.width - childSize.width / 1.1) {
left = size.width - childSize.width;
} else if (buttonRect.left < childSize.width / 5) {
left = 0;
} else {
left = buttonRect.left - (buttonRect.right - buttonRect.left) / 3;
}
return Offset(left, hintTop);
}
#override
bool shouldRelayout(_HintRouteLayout oldDelegate) {
return buttonRect != oldDelegate.buttonRect ||
hintTop != oldDelegate.hintTop ||
hintHeight != oldDelegate.hintHeight ||
textDirection != oldDelegate.textDirection;
}
}
class _HintWidget extends StatefulWidget {
const _HintWidget({
Key key,
#required this.title,
#required this.style,
this.padding,
this.route,
}) : super(key: key);
final String title;
final TextStyle style;
final _HintRoute route;
final EdgeInsets padding;
#override
_HintState createState() => _HintState();
}
class _HintState extends State<_HintWidget> {
CurvedAnimation _fadeOpacity;
CurvedAnimation _resize;
#override
void initState() {
super.initState();
_fadeOpacity = CurvedAnimation(
parent: widget.route.animation,
curve: const Interval(0.0, 0.25),
reverseCurve: const Interval(0.75, 1.0),
);
_resize = CurvedAnimation(
parent: widget.route.animation,
curve: const Interval(0.25, 0.5),
reverseCurve: const Threshold(0.0),
);
}
#override
Widget build(BuildContext context) {
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final _HintRoute route = widget.route;
const double unit = 0.5 / 1.5;
CurvedAnimation opacity;
final double start = (0.5 + 1 * unit).clamp(0.0, 1.0) as double;
final double end = (start + 1.5 * unit).clamp(0.0, 1.0) as double;
opacity = CurvedAnimation(parent: route.animation, curve: Interval(start, end));
return FadeTransition(
opacity: _fadeOpacity,
child: CustomPaint(
painter: _HintPainter(
color: Theme.of(context).canvasColor,
elevation: 8,
resize: _resize,
),
child: Semantics(
scopesRoute: true,
namesRoute: true,
explicitChildNodes: true,
label: localizations.popupMenuLabel,
child: Material(
type: MaterialType.transparency,
child: Padding(
padding: kMaterialListPadding,
child: FadeTransition(
opacity: opacity,
child: InkWell(
onTap: () => Navigator.pop(context),
child: Container(
padding: widget.padding,
child: Text(
widget.title,
style: widget.style,
textAlign: TextAlign.center,
),
),
),
),
),
),
),
),
);
}
}
class _HintPainter extends CustomPainter {
_HintPainter({
this.color,
this.elevation,
this.resize,
}) : _painter = BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(6.0),
boxShadow: kElevationToShadow[elevation],
).createBoxPainter(),
super(repaint: resize);
final Color color;
final int elevation;
final Animation<double> resize;
final BoxPainter _painter;
#override
void paint(Canvas canvas, Size size) {
final double selectedItemOffset = _hintWidgetHeight + kMaterialListPadding.top;
final Tween<double> top = Tween<double>(
begin: selectedItemOffset.clamp(0.0, size.height - _hintWidgetHeight) as double,
end: 0.0,
);
final Tween<double> bottom = Tween<double>(
begin: (top.begin + _hintWidgetHeight).clamp(_hintWidgetHeight, size.height) as double,
end: size.height,
);
final Rect rect = Rect.fromLTRB(0.0, top.evaluate(resize), size.width, bottom.evaluate(resize));
_painter.paint(canvas, rect.topLeft, ImageConfiguration(size: rect.size));
}
#override
bool shouldRepaint(_HintPainter oldPainter) {
return oldPainter.color != color ||
oldPainter.elevation != elevation ||
oldPainter.resize != resize;
}
}
So it is used:
return MyHintWidget(
title: 'some text',
style: //text style[![enter image description here][1]][1],
child: //child,
);
Result