Flutter: How to show CircularprogressIndicator? - flutter

void refresh() async {
await get.getFromFirestore(id);
await get.showData(get.data(), context);
setState(() {
markerList = get.getList();
})
}
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: GoogleMap(
mapType: MapType.normal,
mapToolbarEnabled: false,
initialCameraPosition: _currentlo,
onMapCreated: _onMapCreated,
markers: markerList
),
),
floatingActionButton: SpeedDial(
animatedIcon: AnimatedIcons.menu_close,
animatedIconTheme: IconThemeData(size: 22, color: Colors.black),
closeManually: false,
curve: Curves.bounceIn,
overlayColor: Colors.black,
children: [
SpeedDialChild(
backgroundColor: Colors.white,
child: Icon(Icons.refresh, color: Colors.black,),
onTap: (){
refresh();
}
),
],
),
);
}
This is my code. I programmed refresh method. I want to show CircularprogressIndicator when I tap the SpeedDialChild. My data comes from Firestore. So it takes long time when data size is big. How can I do this?

This is radial progressbar code which you can use to update UI
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
class RadialPainter extends CustomPainter {
final Color bgColor;
final Color lineColor;
final double width;
final double percent;
RadialPainter({this.bgColor, this.lineColor, this.width, this.percent});
#override
void paint(Canvas canvas, Size size) {
Paint bgLine = new Paint()
..color = bgColor
..strokeCap = StrokeCap.round
..style=PaintingStyle.stroke
..strokeWidth=width;
Paint completedLine = new Paint()
..color = lineColor
..strokeCap = StrokeCap.round
..style=PaintingStyle.stroke
..strokeWidth=width;
Offset center= Offset(size.width/2, size.height/2);
double radius= min(size.width/2, size.height/2);
double sweepAngle=2*pi*percent;
canvas.drawCircle(center, radius, bgLine);
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
pi,
sweepAngle,
false,
completedLine
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
To call this function
child: CustomPaint(
foregroundPainter: RadialPainter(
bgColor: Colors.grey[200],
lineColor: getColor(context, percent),
percent: percent,
width: 15.0),
child: Center(
child: Text(
'\$${amountLeft.toStringAsFixed(2)} / \$${widget.category.maxAmount}',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
),
),
keep updating the percent value
double sweepAngle=2*pi*percent
on button click

change your code to this
void refresh() async {
setState(() {
isLoading = true;
});
await get.getFromFirestore(id);
await get.showData(data, context);
setState(() {
isLoading = false;
markerList = get.getList();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: isLoading ? Center(child: CircularProgressIndicator()) : ListView(here your list),
);
}

Related

Flutter Google maps circle isn't loading up on navigation

I have made radius based flutter google maps in which I am using circle property to draw a circle radius based. It only shows up when I increment or decrement the radius but I want it to be showing by default as well when user navigates to location screen in my flutter app. This is how the map screen loads and as soon as i tap the + or - buttons it will start showing the circle like this I don't know why this is happening. If anyone knows what am I doing wrong then please let me know. I am attaching the code of the screen below :
class MapRadius extends StatefulWidget {
const MapRadius({Key? key}) : super(key: key);
#override
State<MapRadius> createState() => _MapRadiusState();
}
class _MapRadiusState extends State<MapRadius> {
// List<Marker> myMarker = [];
final Set<Circle> circle = {};
// late GoogleMapController mapController;
final Completer<GoogleMapController> _controller = Completer();
int _n = 5;
// LatLng startLoc = const LatLng(52.0907374, 5.1214201);
LatLng? currentLatLng;
late GoogleMapController mapController;
Location location = Location();
var latitude;
var longitude;
late LocationData _locationData;
get() async {
_locationData = await location.getLocation();
latitude = _locationData.latitude;
longitude = _locationData.longitude;
setState(() {
currentLatLng = LatLng(latitude, longitude);
});
}
#override
initState() {
super.initState();
get();
}
#override
void setState(VoidCallback fn) {
super.setState(fn);
FirebaseFirestore.instance
.collection("userpreferences")
.doc(FirebaseAuth.instance.currentUser!.uid)
.update({"radius": _n});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFF2A3B6A),
title: const DelayedDisplay(
delay: Duration(seconds: 1), child: Text('Location Range')),
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop();
},
),
actions: const [
Padding(
padding: EdgeInsets.only(right: 20),
child: Tooltip(
showDuration: Duration(seconds: 5),
triggerMode: TooltipTriggerMode.tap,
textStyle: TextStyle(
fontSize: 18, color: Colors.white, fontFamily: "ProductSans"),
message:
'Increase or decrease radius according to your own preference. + and - can be used to add Kilometers.\nThe Kilometers are multiples of 10 (e.g. 5 = 50KM)\nMax limit 100 KM nearby. Default is 50KM.\nRange gets updated as soon as you add or remove a kilometer.',
child: Icon(Icons.info),
),
),
],
),
body: currentLatLng == null
? Center(
child: SpinKitSpinningLines(
color: Theme.of(context).primaryColor,
size: 90.0,
lineWidth: 5,
))
: Stack(children: <Widget>[
GoogleMap(
circles: circle,
myLocationEnabled: true,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
initialCameraPosition:
CameraPosition(target: currentLatLng!, zoom: 12),
),
]),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 40.0),
child: Row(
children: <Widget>[
SizedBox(
width: 40.0,
height: 40.0,
child: FloatingActionButton(
heroTag: "btnAdd",
onPressed: add,
child: LineIcon(
LineIcons.plus,
color: Colors.black,
size: 30,
),
backgroundColor: Colors.white,
),
),
const SizedBox(
width: 5,
),
Text('$_n', style: const TextStyle(fontSize: 26.0)),
const SizedBox(
width: 5,
),
SizedBox(
width: 40.0,
height: 40.0,
child: FloatingActionButton(
heroTag: "btnMinus",
onPressed: minus,
child: LineIcon(
LineIcons.minus,
color: Colors.black,
size: 30,
),
backgroundColor: Colors.white,
),
),
],
),
),
],
),
);
}
void add() {
setState(() {
if (_n < 10) {
_n++;
}
addRadiusToMap(_n);
});
}
void minus() {
if (_n != 1) {
setState(() {
_n--;
addRadiusToMap(_n);
});
}
}
void addRadiusToMap(radius) {
double reciprocal(double d) => 700 * d; // 1000 before
circle.clear();
circle.add(Circle(
circleId: const CircleId("1"),
center: currentLatLng!,
radius: reciprocal(radius.toDouble()),
));
}
}

Flutter canvas : how to avoid drawing outside image area

import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'dart:ui' as ui;
class EditImage extends StatefulWidget {
final String filePath;
EditImage({this.filePath});
#override
_EditImageState createState() => _EditImageState();
}
class _EditImageState extends State<EditImage> {
ui.Image decodedImage;
String newFilePath;
GlobalKey myCanvasKey = GlobalKey();
ImageEditor editor;
Color color = Colors.blue;
#override
void initState() {
loadImage(File(widget.filePath));
super.initState();
}
void loadImage(File image) async {
final data = await image.readAsBytes();
decodedImage = await decodeImageFromList(data);
editor = ImageEditor(image: decodedImage, strokeColor: color);
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: InkWell(
onTap: () {
Navigator.pop(context, newFilePath ?? widget.filePath);
},
child: Icon(Icons.close),
),
//centerTitle: true,
title: Text('Edit'),
actions: [
InkWell(
onTap: () {
editor.undo();
myCanvasKey.currentContext.findRenderObject().markNeedsPaint();
},
child: Icon(Icons.undo),
),
SizedBox(
width: 10.0,
),
InkWell(
onTap: () async {
Color pickedColor;
bool isSelected = false;
await showDialog(
context: context,
child: AlertDialog(
contentPadding: const EdgeInsets.all(8.0),
title: const Text('Stroke Color'),
content: SingleChildScrollView(
child: ColorPicker(
pickerColor: color,
onColorChanged: (color) {
pickedColor = color;
},
enableAlpha: false,
showLabel: false,
pickerAreaHeightPercent: 0.6,
),
),
actions: <Widget>[
FlatButton(
child: const Text('Cancel'),
onPressed: () => Navigator.pop(context),
),
FlatButton(
child: const Text('Select'),
onPressed: () {
isSelected = true;
Navigator.of(context).pop();
},
),
],
),
);
if (isSelected) {
editor.updateStrokeColor(pickedColor);
setState(() {
color = pickedColor;
});
}
},
child: Container(
decoration: BoxDecoration(
//borderRadius: BorderRadius.circular(15.0),
border: Border.all(color: Colors.grey),
shape: BoxShape.circle,
color: color,
),
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Container(
decoration: BoxDecoration(
color: Colors.black26, shape: BoxShape.circle),
child: Padding(
padding: const EdgeInsets.all(3.0),
child: Icon(
Icons.edit_outlined,
color: Colors.white,
semanticLabel: 'Stroke',
),
),
),
),
),
),
SizedBox(
width: 10.0,
),
InkWell(
onTap: () {
Navigator.pop(context, newFilePath ?? widget.filePath);
},
child: Icon(Icons.done),
),
SizedBox(
width: 10.0,
),
],
),
body: decodedImage == null
? Center(child: CircularProgressIndicator())
: Center(
child: FittedBox(
child: SizedBox(
height: decodedImage.height.toDouble(),
width: decodedImage.width.toDouble(),
child: GestureDetector(
onPanDown: (detailData) {
editor.update(detailData.localPosition);
myCanvasKey.currentContext
.findRenderObject()
.markNeedsPaint();
},
onPanUpdate: (detailData) {
editor.update(detailData.localPosition);
myCanvasKey.currentContext
.findRenderObject()
.markNeedsPaint();
},
onPanEnd: (detailData) {
editor.addNewPointsList();
},
child: CustomPaint(
key: myCanvasKey,
painter: editor,
),
),
),
),
),
);
}
}
class ImageEditor extends CustomPainter {
ImageEditor({
this.image,
this.strokeColor = Colors.black,
}) {
_strokes.add(_StrokeData());
}
final ui.Image image;
Color strokeColor;
List<_StrokeData> _strokes = [];
void updateStrokeColor(Color color) {
strokeColor = color;
}
void update(Offset offset) {
if (_strokes.last.color == null) _strokes.last.color = strokeColor;
_strokes.last.points.add(offset);
}
void addNewPointsList() {
print('pan end');
_strokes.add(_StrokeData());
}
void undo() {
if (_strokes.length > 1) _strokes.removeAt(_strokes.length - 2);
}
#override
void paint(Canvas canvas, Size size) {
canvas.drawImage(image, Offset.zero, Paint());
for (int i = 0; i < _strokes.length; i++) {
Paint _painter = Paint();
_painter.color = _strokes[i].color ?? Colors.transparent;
_painter.style = PaintingStyle.stroke;
_painter.strokeWidth = 15;
_painter.isAntiAlias = true;
for (int j = 0; j < _strokes[i].points.length; j++) {
if (j > 0)
canvas.drawLine(
_strokes[i].points[j - 1], _strokes[i].points[j], _painter);
}
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
class _StrokeData {
Color color;
List<Offset> points;
_StrokeData() {
points = List<Offset>();
}
}
How to restrict drawing within the image area ?
Use ClipRRect() as parent of FittedBox. This solves the problem
ClipRRect(child: FittedBox(child: SizedBox(...Painter widget goes here...) ))

Flutter _debugLifecycleState != _ElementLifecycle.defunct': is not true

I am showing some animated custom painter progress bar in my app it's showing some error
Error: The following assertion was thrown while notifying listeners for AnimationController:
'package:flutter/src/widgets/framework.dart': Failed assertion: line 4263 pos 12: '_debugLifecycleState != _ElementLifecycle.defunct': is not true.
I have a simple homePage in which I have a navigation bar. I am simply showing the container when navigation change like this
SingleChildScrollView(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/sidebg.png"),
fit: BoxFit.cover,
),
),
child: Column(
children: [
pageIndex == 0 ? DashboardScreen() : Container(),
pageIndex == 1 ? MapScreen() : Container(),
pageIndex == 3 ? ServiceCenter() : Container(),
pageIndex == 4 ? ProfileScreen() : Container(),
],
)),
),
Issue is as you can see pages changes when index are changing but when i change the page its showing an error as i mention above in continuesly loop not stopping.
If I remove this progress indicator all is working fine.
This is the progress indicator screen
import 'dart:math' as math;
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
import 'package:sleek_circular_slider/sleek_circular_slider.dart';
import 'package:liquid_progress_indicator/liquid_progress_indicator.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:smooth_star_rating/smooth_star_rating.dart';
class DashboardScreen extends StatefulWidget {
#override
_DashboardScreenState createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return Container(
child: Column(
children: [
SizedBox(
height: height * 0.05,
),
Circular_arc(),
],
),
);
}
}
final Gradient gradient = new LinearGradient(
colors: <Color>[
Colors.greenAccent.withOpacity(1.0),
Colors.yellowAccent.withOpacity(1.0),
Colors.redAccent.withOpacity(1.0),
],
stops: [
0.0,
0.5,
1.0,
],
);
class Circular_arc extends StatefulWidget {
const Circular_arc({
Key key,
}) : super(key: key);
#override
_Circular_arcState createState() => _Circular_arcState();
}
class _Circular_arcState extends State<Circular_arc>
with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController animController;
#override
void initState() {
// TODO: implement initState
super.initState();
animController =
AnimationController(duration: Duration(seconds: 3), vsync: this);
final curvedAnimation =
CurvedAnimation(parent: animController, curve: Curves.easeInOutCubic);
animation = Tween<double>(begin: 0.0, end: 2).animate(curvedAnimation)
..addListener(() {
setState(() {});
});
animController.repeat(max: 1);
}
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return Container(
child: Stack(
children: [
CustomPaint(
size: Size(300, 300),
painter: ProgressArc(null, Colors.black54, true),
),
CustomPaint(
size: Size(300, 300),
painter: ProgressArc(animation.value, Colors.redAccent, false),
),
Positioned(
top: height * 0.07,
left: width * 0.2,
child: Column(
children: [
Image.asset(
'images/star-icon-fill#3x.png',
height: height * 0.045,
),
RichText(
text: new TextSpan(
// Note: Styles for TextSpans must be explicitly defined.
// Child text spans will inherit styles from parent
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: '4.6',
style: new TextStyle(
fontSize: 40, fontFamily: 'UbuntuRegular')),
new TextSpan(
text: ' /5',
style: TextStyle(
fontSize: 25,
color: Colors.grey[400],
fontFamily: 'UbuntuRegular')),
],
),
),
Text(
'FIFTEEN DAYS SCORE',
style: TextStyle(
color: Colors.grey[400], fontFamily: 'UbuntuMedium'),
)
],
),
)
],
),
);
}
}
class ProgressArc extends CustomPainter {
bool isBackground;
double arc;
Color progressColor;
ProgressArc(this.arc, this.progressColor, this.isBackground);
#override
void paint(Canvas canvas, Size size) {
final rect = Rect.fromLTRB(0, 0, 300, 300);
final startAngle = -math.pi;
final sweepAngle = arc != null ? arc : math.pi;
final userCenter = false;
final paint = Paint()
..strokeCap = StrokeCap.round
..color = progressColor
..style = PaintingStyle.stroke
..strokeWidth = 10;
if (!isBackground) {
paint.shader = gradient.createShader(rect);
}
canvas.drawArc(rect, startAngle, sweepAngle, userCenter, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
Error is in the continuous loop it's not stopping. And also its starts when I change the page index (mean when I change the navigation from home).
In your _CircularArcState, please...
Call animController.forward(); after animController.repeat(max: 1);
To save the state of CircularArc, add mixin AutomaticKeepAliveClientMixin in _CircularArcState. Then override wantKeepAlive and return true. Also, call super.build(context); inside build(...).
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: PageView(
scrollDirection: Axis.vertical,
children: [
DashboardScreen(),
Container(color: Colors.orange),
Container(color: Colors.blue),
Container(color: Colors.green),
],
),
),
);
}
}
class DashboardScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return Container(
child: Column(
children: [
SizedBox(
height: height * 0.05,
),
CircularArc(),
],
),
);
}
}
final Gradient gradient = new LinearGradient(
colors: <Color>[
Colors.greenAccent.withOpacity(1.0),
Colors.yellowAccent.withOpacity(1.0),
Colors.redAccent.withOpacity(1.0),
],
stops: [0.0, 0.5, 1.0],
);
class CircularArc extends StatefulWidget {
const CircularArc({
Key key,
}) : super(key: key);
#override
_CircularArcState createState() => _CircularArcState();
}
class _CircularArcState extends State<CircularArc>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
double width;
double height;
Animation<double> animation;
#override
void initState() {
super.initState();
final AnimationController animController =
AnimationController(duration: Duration(seconds: 3), vsync: this);
final curvedAnimation =
CurvedAnimation(parent: animController, curve: Curves.easeInOutCubic);
animation = Tween<double>(begin: 0.0, end: 2).animate(curvedAnimation)
..addListener(() {
setState(() {});
});
animController.repeat(max: 1);
animController.forward();
}
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
if (width == null && height == null) {
width = MediaQuery.of(context).size.width;
height = MediaQuery.of(context).size.height;
}
return Container(
child: Stack(
children: [
CustomPaint(
size: Size(300, 300),
painter: ProgressArc(null, Colors.black54, true),
),
CustomPaint(
size: Size(300, 300),
painter: ProgressArc(animation.value, Colors.redAccent, false),
),
Positioned(
top: height * 0.07,
left: width * 0.2,
child: Column(
children: [
// Image.asset(
// 'images/star-icon-fill#3x.png',
// height: height * 0.045,
// ),
RichText(
text: new TextSpan(
// Note: Styles for TextSpans must be explicitly defined.
// Child text spans will inherit styles from parent
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: '4.6',
style: new TextStyle(
fontSize: 40, fontFamily: 'UbuntuRegular')),
new TextSpan(
text: ' /5',
style: TextStyle(
fontSize: 25,
color: Colors.grey[400],
fontFamily: 'UbuntuRegular')),
],
),
),
Text(
'FIFTEEN DAYS SCORE',
style: TextStyle(
color: Colors.grey[400], fontFamily: 'UbuntuMedium'),
)
],
),
)
],
),
);
}
}
class ProgressArc extends CustomPainter {
bool isBackground;
double arc;
Color progressColor;
ProgressArc(this.arc, this.progressColor, this.isBackground);
#override
void paint(Canvas canvas, Size size) {
final rect = Rect.fromLTRB(0, 0, 300, 300);
final startAngle = -math.pi;
final sweepAngle = arc != null ? arc : math.pi;
final userCenter = false;
final paint = Paint()
..strokeCap = StrokeCap.round
..color = progressColor
..style = PaintingStyle.stroke
..strokeWidth = 10;
if (!isBackground) {
paint.shader = gradient.createShader(rect);
}
canvas.drawArc(rect, startAngle, sweepAngle, userCenter, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
first dispose the controller and then use super.dispose()
void dispose() { controller.dispose(); super.dispose(); }

how to make Percent Indicator change color programmatically in flutter?

I'm using a package called percent indicator https://pub.dev/packages/percent_indicator
and I'm currently using its CircularPercentIndicator()
I'm just wondering how to change the progress color when a certain percentage met?
For example: I have a starting progress color of green at 0% when reaching 60% progress color should change to orange and when reaching 80% color should be red.
here's what I got at the moment:
import 'package:flutter/material.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
class RoutinePage extends StatefulWidget {
#override
_RoutinePageState createState() => _RoutinePageState();
}
class _RoutinePageState extends State<RoutinePage> {
double progress = 0;
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
color: Colors.white,
alignment: Alignment(0, 0),
child: CircularPercentIndicator(
animationDuration: 100,
animateFromLastPercent: true,
arcType: ArcType.FULL,
arcBackgroundColor: Colors.black12,
backgroundColor: Colors.white,
progressColor: Colors.green,
percent: progress,
animation: true,
radius: 250.0,
lineWidth: 12.0,
circularStrokeCap: CircularStrokeCap.round,
),
),
Container(
alignment: Alignment(0, 0),
child: Text("${this.progress * 100}%",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
Container(
alignment: Alignment(0.3, 0.5),
child: RaisedButton(
color: Colors.green,
onPressed: () {
final updated = ((this.progress + 0.1).clamp(0.0, 1.0) * 100);
setState(() {
this.progress = updated.round() / 100;
});
print(progress);
},
child: Text('+10%',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),)),
),
Container(
alignment: Alignment(-0.3, 0.5),
child: RaisedButton(
color: Colors.red,
onPressed: () {
final updated = ((this.progress - 0.1).clamp(0.0, 1.0) * 100);
setState(() {
this.progress = updated.round() / 100;
});
print(progress);
},
child: Text('-10%',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),)),
),
],
);
}
}
and I don't know if this will help but this is the code of CircularPercentIndicator()
//import 'dart:math';
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart' as math;
enum CircularStrokeCap { butt, round, square }
enum ArcType {
HALF,
FULL,
}
class CircularPercentIndicator extends StatefulWidget {
///Percent value between 0.0 and 1.0
final double percent;
final double radius;
///Width of the line of the Circle
final double lineWidth;
///Color of the background of the circle , default = transparent
final Color fillColor;
///First color applied to the complete circle
final Color backgroundColor;
Color get progressColor => _progressColor;
Color _progressColor;
///true if you want the circle to have animation
final bool animation;
///duration of the animation in milliseconds, It only applies if animation attribute is true
final int animationDuration;
///widget at the top of the circle
final Widget header;
///widget at the bottom of the circle
final Widget footer;
///widget inside the circle
final Widget center;
final LinearGradient linearGradient;
///The kind of finish to place on the end of lines drawn, values supported: butt, round, square
final CircularStrokeCap circularStrokeCap;
///the angle which the circle will start the progress (in degrees, eg: 0.0, 45.0, 90.0)
final double startAngle;
/// set true if you want to animate the linear from the last percent value you set
final bool animateFromLastPercent;
/// set false if you don't want to preserve the state of the widget
final bool addAutomaticKeepAlive;
/// set the arc type
final ArcType arcType;
/// set a circular background color when use the arcType property
final Color arcBackgroundColor;
/// set true when you want to display the progress in reverse mode
final bool reverse;
/// Creates a mask filter that takes the progress shape being drawn and blurs it.
final MaskFilter maskFilter;
CircularPercentIndicator(
{Key key,
this.percent = 0.0,
this.lineWidth = 5.0,
this.startAngle = 0.0,
#required this.radius,
this.fillColor = Colors.transparent,
this.backgroundColor = const Color(0xFFB8C7CB),
Color progressColor,
this.linearGradient,
this.animation = false,
this.animationDuration = 500,
this.header,
this.footer,
this.center,
this.addAutomaticKeepAlive = true,
this.circularStrokeCap,
this.arcBackgroundColor,
this.arcType,
this.animateFromLastPercent = false,
this.reverse = false,
this.maskFilter})
: super(key: key) {
if (linearGradient != null && progressColor != null) {
throw ArgumentError(
'Cannot provide both linearGradient and progressColor');
}
_progressColor = progressColor ?? Colors.red;
assert(startAngle >= 0.0);
if (percent < 0.0 || percent > 1.0) {
throw Exception("Percent value must be a double between 0.0 and 1.0");
}
if (arcType == null && arcBackgroundColor != null) {
throw ArgumentError('arcType is required when you arcBackgroundColor');
}
}
#override
_CircularPercentIndicatorState createState() =>
_CircularPercentIndicatorState();
}
class _CircularPercentIndicatorState extends State<CircularPercentIndicator>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
AnimationController _animationController;
Animation _animation;
double _percent = 0.0;
#override
void dispose() {
if (_animationController != null) {
_animationController.dispose();
}
super.dispose();
}
#override
void initState() {
if (widget.animation) {
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: widget.animationDuration));
_animation =
Tween(begin: 0.0, end: widget.percent).animate(_animationController)
..addListener(() {
setState(() {
_percent = _animation.value;
});
});
_animationController.forward();
} else {
_updateProgress();
}
super.initState();
}
#override
void didUpdateWidget(CircularPercentIndicator oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.percent != widget.percent ||
oldWidget.startAngle != widget.startAngle) {
if (_animationController != null) {
_animationController.duration =
Duration(milliseconds: widget.animationDuration);
_animation = Tween(
begin: widget.animateFromLastPercent ? oldWidget.percent : 0.0,
end: widget.percent)
.animate(_animationController);
_animationController.forward(from: 0.0);
} else {
_updateProgress();
}
}
}
_updateProgress() {
setState(() {
_percent = widget.percent;
});
}
#override
Widget build(BuildContext context) {
super.build(context);
var items = List<Widget>();
if (widget.header != null) {
items.add(widget.header);
}
items.add(Container(
height: widget.radius + widget.lineWidth,
width: widget.radius,
child: CustomPaint(
painter: CirclePainter(
progress: _percent * 360,
progressColor: widget.progressColor,
backgroundColor: widget.backgroundColor,
startAngle: widget.startAngle,
circularStrokeCap: widget.circularStrokeCap,
radius: (widget.radius / 2) - widget.lineWidth / 2,
lineWidth: widget.lineWidth,
arcBackgroundColor: widget.arcBackgroundColor,
arcType: widget.arcType,
reverse: widget.reverse,
linearGradient: widget.linearGradient,
maskFilter: widget.maskFilter),
child: (widget.center != null)
? Center(child: widget.center)
: Container(),
)));
if (widget.footer != null) {
items.add(widget.footer);
}
return Material(
color: widget.fillColor,
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: items,
)),
);
}
#override
bool get wantKeepAlive => widget.addAutomaticKeepAlive;
}
class CirclePainter extends CustomPainter {
final Paint _paintBackground = Paint();
final Paint _paintLine = Paint();
final Paint _paintBackgroundStartAngle = Paint();
final double lineWidth;
final double progress;
final double radius;
final Color progressColor;
final Color backgroundColor;
final CircularStrokeCap circularStrokeCap;
final double startAngle;
final LinearGradient linearGradient;
final Color arcBackgroundColor;
final ArcType arcType;
final bool reverse;
final MaskFilter maskFilter;
CirclePainter(
{this.lineWidth,
this.progress,
#required this.radius,
this.progressColor,
this.backgroundColor,
this.startAngle = 0.0,
this.circularStrokeCap = CircularStrokeCap.round,
this.linearGradient,
this.reverse,
this.arcBackgroundColor,
this.arcType,
this.maskFilter}) {
_paintBackground.color = backgroundColor;
_paintBackground.style = PaintingStyle.stroke;
_paintBackground.strokeWidth = lineWidth;
if (arcBackgroundColor != null) {
_paintBackgroundStartAngle.color = arcBackgroundColor;
_paintBackgroundStartAngle.style = PaintingStyle.stroke;
_paintBackgroundStartAngle.strokeWidth = lineWidth;
}
_paintLine.color = progressColor;
_paintLine.style = PaintingStyle.stroke;
_paintLine.strokeWidth = lineWidth;
if (circularStrokeCap == CircularStrokeCap.round) {
_paintLine.strokeCap = StrokeCap.round;
} else if (circularStrokeCap == CircularStrokeCap.butt) {
_paintLine.strokeCap = StrokeCap.butt;
} else {
_paintLine.strokeCap = StrokeCap.square;
}
}
#override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
canvas.drawCircle(center, radius, _paintBackground);
if (maskFilter != null) {
_paintLine.maskFilter = maskFilter;
}
if (linearGradient != null) {
/*
_paintLine.shader = SweepGradient(
center: FractionalOffset.center,
startAngle: math.radians(-90.0 + startAngle),
endAngle: math.radians(progress),
//tileMode: TileMode.mirror,
colors: linearGradient.colors)
.createShader(
Rect.fromCircle(
center: center,
radius: radius,
),
);*/
_paintLine.shader = linearGradient.createShader(
Rect.fromCircle(
center: center,
radius: radius,
),
);
}
double fixedStartAngle = startAngle;
double startAngleFixedMargin = 1.0;
if (arcType != null) {
if (arcType == ArcType.FULL) {
fixedStartAngle = 220;
startAngleFixedMargin = 172 / fixedStartAngle;
} else {
fixedStartAngle = 270;
startAngleFixedMargin = 135 / fixedStartAngle;
}
}
if (arcBackgroundColor != null) {
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
math.radians(-90.0 + fixedStartAngle),
math.radians(360 * startAngleFixedMargin),
false,
_paintBackgroundStartAngle,
);
}
if (reverse) {
final start =
math.radians(360 * startAngleFixedMargin - 90.0 + fixedStartAngle);
final end = math.radians(-progress * startAngleFixedMargin);
canvas.drawArc(
Rect.fromCircle(
center: center,
radius: radius,
),
start,
end,
false,
_paintLine,
);
} else {
final start = math.radians(-90.0 + fixedStartAngle);
final end = math.radians(progress * startAngleFixedMargin);
canvas.drawArc(
Rect.fromCircle(
center: center,
radius: radius,
),
start,
end,
false,
_paintLine,
);
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
One of possible solutions is AnimatedBuilder.
I will show you how can we change color of button and you can easily apply approach to progress indicator.
The example below just shows when tap on button start changing animation. Same for you when you need to start progress bat, just run animationController and check result.
If you have further questions, do not hesitate to ask in comments
#override
void initState() {
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_colorTween = ColorTween(begin: Colors.red, end: Colors.green)
.animate(_animationController);
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _colorTween,
builder: (context, child) => RaisedButton(
child: Text("Change my color"),
color: _colorTween.value,
onPressed: () {
if (_animationController.status == AnimationStatus.completed) {
_animationController.reverse();
} else {
_animationController.forward();
}
},
),
);
}
thanks for the response. I also came up with other solution and I think I'm good on what I've ended up with. btw here's what I came up with:
import 'package:flutter/material.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
class RoutinePage extends StatefulWidget {
#override
_RoutinePageState createState() => _RoutinePageState();
}
class _RoutinePageState extends State<RoutinePage> {
double progress = 0;
currentProgressColor() {
if (progress >= 0.6 && progress < 0.8) {
return Colors.orange;
}
if(progress >= 0.8){
return Colors.red;
}
else{
return Colors.green;
}
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
color: Colors.white,
alignment: Alignment(0, 0),
child: CircularPercentIndicator(
animationDuration: 200,
animateFromLastPercent: true,
arcType: ArcType.FULL,
arcBackgroundColor: Colors.black12,
backgroundColor: Colors.white,
progressColor: currentProgressColor(),
percent: progress,
animation: true,
radius: 250.0,
lineWidth: 12.0,
circularStrokeCap: CircularStrokeCap.butt,
),
),
Container(
alignment: Alignment(0, 0),
child: Text(
"${this.progress * 100}%",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
Container(
alignment: Alignment(0.3, 0.5),
child: RaisedButton(
color: Colors.green,
onPressed: () {
final updated = ((this.progress + 0.1).clamp(0.0, 1.0) * 100);
setState(() {
this.progress = updated.round() / 100;
});
print(progress);
},
child: Text(
'+10%',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
)),
),
Container(
alignment: Alignment(-0.3, 0.5),
child: RaisedButton(
color: Colors.red,
onPressed: () {
final updated = ((this.progress - 0.1).clamp(0.0, 1.0) * 100);
setState(() {
this.progress = updated.round() / 100;
});
print(progress);
},
child: Text(
'-10%',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
)),
),
],
);
}
}
class ChangeBackgroundColor StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _changeColor(),
initialData: 0xff0DE95B,
builder: (context, snapshot) {
return LinearPercentIndicator(
percent: 1,
animation: true,
animationDuration: 30000,
progressColor: Colors.grey[200],
backgroundColor: Color(
int.parse(snapshot.data.toString()),
)),
);
},
);
}
}
Stream<int> _changeColor() async* {
yield* Stream.periodic(Duration(seconds: 1), (int a) {
a++;
if (a > 25) {
return 0xffF33709;
} else {
return 0xff0DE95B;
}
});
}

How to erase/clip from Canvas CustomPaint?

I have already tried to use Canvas.clipPath along with GestureDetector to be like eraser on the canvas where i use the CustomPaint inside a Container with imageDecoration set, so i thought maybe there is another workaround this by using Canvas.drawPath along setting
final Paint _eraserPaint = Paint()
..color = Colors.transparent
..blendMode = BlendMode.clear
..strokeWidth = 8
..style = PaintingStyle.stroke
..isAntiAlias = true;
but it draws black lines instead of erasing
any idea how to get around this?
thanks
The key is to call saveLayer before drawing anything that might require erasing. After that's done (thus creating a new layer for you to use), you can then draw with any Color to fill, or draw with BlendMode.clear to erase. Lastly, call restore to "merge" the new layer into other existing layers.
For example, let's draw a red square and subtract a circle from it:
void paint(Canvas canvas, Size size) {
canvas.saveLayer(Rect.largest, Paint());
canvas.drawRect(Rect.fromLTWH(0, 0, 80, 80), Paint()..color = Colors.red);
canvas.drawCircle(Offset(40, 40), 40, Paint()..blendMode = BlendMode.clear);
canvas.restore();
}
Sample result:
May this code can help you!
class DrawingPainter extends CustomPainter {
List<DrawingPoints> pointsList;
List<Offset> offsetPoints = List();
DrawingPainter({
this.pointsList,
});
#override
void paint(Canvas canvas, Size size) {
canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), Paint());
for (int i = 0; i < pointsList.length - 1; i++) {
if (pointsList[i] != null && pointsList[i + 1] != null) {
canvas.drawLine(pointsList[i].points, pointsList[i + 1].points, pointsList[i].paint);
canvas.drawCircle(pointsList[i].points, pointsList[i].paint.strokeWidth/2, pointsList[i].paint);
}
}
canvas.restore();
}
#override
bool shouldRepaint(DrawingPainter oldDelegate) => true;
}
class DrawingPoints {
Paint paint;
Offset points;
DrawingPoints({this.points, this.paint});
}
You need saveLayer, then restores to save Paint
Maybe you need to add this code to te Statefull widget.
void changeBrush(bool isErease){
setState(() {
if ( isErease ){
paint = Paint();
paint.blendMode = BlendMode.clear;
paint.color = Colors.white;
paint.strokeWidth = strokeWidth;
}else{
paint = Paint();
paint.isAntiAlias = true;
paint.color = selectedColor.withOpacity(opacity);
paint.strokeWidth = strokeWidth;
}
});
}
may this code help you...
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:animated_floatactionbuttons/animated_floatactionbuttons.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
var appbarcolor = Colors.blue;
class CanvasPainting_test extends StatefulWidget {
#override
_CanvasPainting_testState createState() => _CanvasPainting_testState();
}
class _CanvasPainting_testState extends State<CanvasPainting_test> {
GlobalKey globalKey = GlobalKey();
List<TouchPoints> points = List();
double opacity = 1.0;
StrokeCap strokeType = StrokeCap.round;
double strokeWidth = 3.0;
double strokeWidthforEraser = 3.0;
Color selectedColor;
Future<void> _pickStroke() async {
//Shows AlertDialog
return showDialog<void>(
context: context,
//Dismiss alert dialog when set true
barrierDismissible: true, // user must tap button!
builder: (BuildContext context) {
//Clips its child in a oval shape
return ClipOval(
child: AlertDialog(
//Creates three buttons to pick stroke value.
actions: <Widget>[
//Resetting to default stroke value
FlatButton(
child: Icon(
Icons.clear,
),
onPressed: () {
strokeWidth = 3.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 24,
),
onPressed: () {
strokeWidth = 10.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 40,
),
onPressed: () {
strokeWidth = 30.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 60,
),
onPressed: () {
strokeWidth = 50.0;
Navigator.of(context).pop();
},
),
],
),
);
},
);
}
Future<void> _opacity() async {
//Shows AlertDialog
return showDialog<void>(
context: context,
//Dismiss alert dialog when set true
barrierDismissible: true,
builder: (BuildContext context) {
//Clips its child in a oval shape
return ClipOval(
child: AlertDialog(
//Creates three buttons to pick opacity value.
actions: <Widget>[
FlatButton(
child: Icon(
Icons.opacity,
size: 24,
),
onPressed: () {
//most transparent
opacity = 0.1;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.opacity,
size: 40,
),
onPressed: () {
opacity = 0.5;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.opacity,
size: 60,
),
onPressed: () {
//not transparent at all.
opacity = 1.0;
Navigator.of(context).pop();
},
),
],
),
);
},
);
}
Future<void> _pickStrokeforEraser() async {
//Shows AlertDialog
return showDialog<void>(
context: context,
//Dismiss alert dialog when set true
barrierDismissible: true, // user must tap button!
builder: (BuildContext context) {
//Clips its child in a oval shape
return ClipOval(
child: AlertDialog(
//Creates three buttons to pick stroke value.
actions: <Widget>[
//Resetting to default stroke value
FlatButton(
child: Icon(
Icons.clear,
),
onPressed: () {
strokeWidthforEraser = 3.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 24,
),
onPressed: () {
strokeWidthforEraser = 10.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 40,
),
onPressed: () {
strokeWidthforEraser = 30.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 60,
),
onPressed: () {
strokeWidthforEraser = 50.0;
Navigator.of(context).pop();
},
),
],
),
);
},
);
}
Future<void> _save() async {
RenderRepaintBoundary boundary =
globalKey.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List();
//Request permissions if not already granted
if (!(await Permission.storage.status.isGranted))
await Permission.storage.request();
final result = await ImageGallerySaver.saveImage(
Uint8List.fromList(pngBytes),
quality: 60,
name: "canvas_image");
print(result);
}
String erase = 'yes';
List<Widget> fabOption() {
return <Widget>[
FloatingActionButton(
backgroundColor: appbarcolor,
heroTag: "camera",
child: Icon(Icons.camera),
tooltip: 'camera',
onPressed: () {
//min: 0, max: 50
setState(() {
erase = 'yes';
this._showDialog();
// _save();
});
},
),
FloatingActionButton(
backgroundColor: appbarcolor,
heroTag: "paint_save",
child: Icon(Icons.file_download),
tooltip: 'Save',
onPressed: () {
//min: 0, max: 50
setState(() {
erase = 'yes';
_save();
});
},
),
FloatingActionButton(
backgroundColor: appbarcolor,
heroTag: "paint_stroke",
child: Icon(Icons.brush),
tooltip: 'Stroke',
onPressed: () {
//min: 0, max: 50
setState(() {
erase = 'yes';
_pickStroke();
});
},
),
// FloatingActionButton(
// heroTag: "paint_opacity",
// child: Icon(Icons.opacity),
// tooltip: 'Opacity',
// onPressed: () {
// //min:0, max:1
// setState(() {
// _opacity();
// });
// },
// ),
FloatingActionButton(
backgroundColor: appbarcolor,
heroTag: "Erase",
child: Icon(Icons.ac_unit),
tooltip: 'Erase',
onPressed: () {
//min: 0, max: 50
setState(() {
// _save();
// selectedColor = Colors.transparent;
// print(Platform.isAndroid);
erase = 'no';
_pickStrokeforEraser();
});
},
),
FloatingActionButton(
backgroundColor: appbarcolor,
heroTag: "Clear All",
child: Icon(Icons.clear),
tooltip: "Clear All",
onPressed: () {
setState(() {
erase = 'yes';
points.clear();
});
}),
FloatingActionButton(
backgroundColor: Colors.white,
heroTag: "color_red",
child: colorMenuItem(Colors.red),
tooltip: 'Color',
onPressed: () {
setState(() {
selectedColor = Colors.red;
});
},
),
FloatingActionButton(
backgroundColor: Colors.white,
heroTag: "color_green",
child: colorMenuItem(Colors.green),
tooltip: 'Color',
onPressed: () {
setState(() {
erase = 'yes';
selectedColor = Colors.green;
});
},
),
FloatingActionButton(
backgroundColor: Colors.white,
heroTag: "color_pink",
child: colorMenuItem(Colors.pink),
tooltip: 'Color',
onPressed: () {
setState(() {
selectedColor = Colors.pink;
});
},
),
FloatingActionButton(
backgroundColor: Colors.white,
heroTag: "color_blue",
child: colorMenuItem(Colors.blue),
tooltip: 'Color',
onPressed: () {
setState(() {
erase = 'yes';
selectedColor = Colors.blue;
});
},
),
];
}
void _showDialog() {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
// title: new Text("Alert Dialog title"),
content: Row(
// mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
onPressed: getImageCamera,
child: Text('From Camera'),
),
SizedBox(
width: 5,
),
RaisedButton(
onPressed: getImageGallery,
child: Text('From Gallery'),
)
],
),
);
},
);
}
File _image;
Future getImageCamera() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
print(image);
if (image != null) {
setState(() {
_image = image;
});
Navigator.of(context, rootNavigator: true).pop('dialog');
}
}
Future getImageGallery() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
print(image);
if (image != null) {
setState(() {
_image = image;
print(_image);
});
Navigator.of(context, rootNavigator: true).pop('dialog');
}
}
/*-------------------------------------*/
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('paint on image and erase'),backgroundColor: Colors.blueGrey
// leading: IconButton(
// icon: Icon(Icons.arrow_back_ios),onPressed: (){
// Navigator.pop(context);
// },),
),
body: GestureDetector(
onPanUpdate: (details) {
setState(() {
RenderBox renderBox = context.findRenderObject();
erase!='no'? points.add(TouchPoints(
points: renderBox.globalToLocal(details.globalPosition),
paint: Paint()
..strokeCap = strokeType
..isAntiAlias = true
..color = selectedColor.withOpacity(opacity)
..strokeWidth = strokeWidth))
: points.add(TouchPoints(
points: renderBox.globalToLocal(details.globalPosition),
paint: Paint()
..color = Colors.transparent
..blendMode = BlendMode.clear
..strokeWidth = strokeWidthforEraser
..style = PaintingStyle.stroke
..isAntiAlias = true
));
});
},
onPanStart: (details) {
setState(() {
RenderBox renderBox = context.findRenderObject();
erase!='no'? points.add(TouchPoints(
points: renderBox.globalToLocal(details.globalPosition),
paint: Paint()
..strokeCap = strokeType
..isAntiAlias = true
..color = selectedColor.withOpacity(opacity)
..strokeWidth = strokeWidth))
: points.add(TouchPoints(
points: renderBox.globalToLocal(details.globalPosition),
paint: Paint()
..color = Colors.transparent
..blendMode = BlendMode.clear
..strokeWidth = strokeWidthforEraser
..style = PaintingStyle.stroke
..isAntiAlias = true
));
});
},
onPanEnd: (details) {
setState(() {
points.add(null);
});
},
child: RepaintBoundary(
key: globalKey,
child: Stack(
children: <Widget>[
Center(
child: _image == null
? Image.asset(
"assets/images/helo.jfif",
)
: Image.file(_image),
),
CustomPaint(
size: Size.infinite,
painter: MyPainter(
pointsList: points,
),
),
],
),
),
),
floatingActionButton: AnimatedFloatingActionButton(
fabButtons: fabOption(),
colorStartAnimation: appbarcolor,
colorEndAnimation: Colors.red[300],
animatedIconData: AnimatedIcons.menu_close),
);
}
Widget colorMenuItem(Color color) {
return GestureDetector(
onTap: () {
setState(() {
selectedColor = color;
});
},
child: ClipOval(
child: Container(
padding: const EdgeInsets.only(bottom: 8.0),
height: 36,
width: 36,
color: color,
),
),
);
}
}
class MyPainter extends CustomPainter {
MyPainter({this.pointsList});
//Keep track of the points tapped on the screen
List<TouchPoints> pointsList;
List<Offset> offsetPoints = List();
//This is where we can draw on canvas.
#override
void paint(Canvas canvas, Size size) {
canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), Paint());
for (int i = 0; i < pointsList.length - 1; i++) {
if (pointsList[i] != null && pointsList[i + 1] != null) {
canvas.drawLine(pointsList[i].points, pointsList[i + 1].points, pointsList[i].paint);
canvas.drawCircle(pointsList[i].points, pointsList[i].paint.strokeWidth/2, pointsList[i].paint);
}
}
canvas.restore();
}
//Called when CustomPainter is rebuilt.
//Returning true because we want canvas to be rebuilt to reflect new changes.
#override
bool shouldRepaint(MyPainter oldDelegate) => true;
}
//Class to define a point touched at canvas
class TouchPoints {
Paint paint;
Offset points;
TouchPoints({this.points, this.paint});
}
Wrap your custom paint widget into an Opacity widget with opacity of 0.99:
Opacity(
opacity: .99,
child: CustomPaint(
size: const Size(double.infinity, 100),
painter: MyPainter(),
),
),
I don't know why, but this fix the problem without any change in your painter class.
Came here looking for the opposite (clip the corners of the square and keep the inside). The goal was to draw a rounded rectangle out of several non rounded overlapping rectangles.
This is what worked for this:
canvas.save();
RRect clipRectangle = RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, 80, 80),
Radius.circular(4),
);
canvas.clipRRect(clipRectangle);
canvas.drawRect(Rect.fromLTWH(0, 0, 80, 80), Paint()..color = Colors.red);
canvas.drawRect(Rect.fromLTWH(0, 20, 80, 80), Paint()..color = Colors.blue);
canvas.restore();