How to create an infinite rain of icons in Flutter? - android

I want to create an infinite rain of icons in Flutter. Let me explain.
Here is the widget that will be performing the raining/falling animation. It uses an AnimationController along with a GravitySimulation to make the widget fall. It takes in an endDistance, which is how far it should go down. It renders a simple icon (also an argument).
import 'package:clickr/providers/GameEngineProvider.dart';
import 'package:clickr/utils/functions/randInt.dart';
import "package:flutter/material.dart";
import 'package:flutter/physics.dart';
import 'package:provider/provider.dart';
class FallingIcon extends StatefulWidget {
final double endDistance;
final double? iconLeft;
final double? iconRight;
final double? iconBottom;
final Widget? child;
const FallingIcon({
Key? key,
required this.endDistance,
this.iconLeft,
this.iconRight,
this.iconBottom,
this.child,
}) : super(key: key);
#override
_FallingIconState createState() => _FallingIconState();
}
class _FallingIconState extends State<FallingIcon>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(
milliseconds: randInt(2000, 3000),
),
);
_controller.animateWith(
GravitySimulation(
10,
0,
widget.endDistance,
0,
),
);
_controller.forward();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget? child) {
return Positioned(
child: child!,
top: _controller.value * widget.endDistance,
left: widget.iconLeft,
right: widget.iconRight,
bottom: widget.iconBottom,
);
},
child: widget.child ?? const FlutterLogo(),
);
}
}
Here, I am using this widget inside a stack:
import 'package:clickr/providers/GameEngineProvider.dart';
import 'package:clickr/providers/IconSetsProvider.dart';
import 'package:clickr/screens/HomeScreen.Authenticated.dart';
import 'package:clickr/widgets/CircleSticker.dart';
import 'package:clickr/widgets/FallingIcon.dart';
import 'package:clickr/widgets/StatusBarSpacer.dart';
import "package:flutter/material.dart";
import 'package:provider/provider.dart';
import '../widgets/FloatingActionButtonCustomBar.dart';
class PlayScreen extends StatefulWidget {
const PlayScreen({Key? key}) : super(key: key);
static const routeName = "/play";
#override
State<PlayScreen> createState() => _PlayScreenState();
}
class _PlayScreenState extends State<PlayScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
final iconSetsProvider =
Provider.of<IconSetsProvider>(context, listen: false);
final gameEngineProvider =
Provider.of<GameEngineProvider>(context, listen: true);
return Scaffold(
body: Column(
children: [
const StatusBarSpacer(),
Expanded(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Stack(
children: [
FallingIcon(
endDistance: constraints.maxHeight,
child: Image.asset(
iconSetsProvider.getRandomIconFromIconSet(
iconSetsProvider.currentIconSet!,
),
width: 40,
),
),
],
);
},
),
),
FloatingActionButtonCustomBar(
children: [
FloatingActionButton(
onPressed: () {
Navigator.of(context).pushReplacementNamed(
HomeScreenAuthenticatated.routeName);
},
child: const Icon(Icons.home),
heroTag: "home",
),
FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.pause),
heroTag: "pause",
),
],
),
],
),
);
}
}
The above code renders one FallingIcon, which successfully animates and falls until it reaches the end of the screen and cannot be seen any more. Now, I want to multiply this icon, so there should be multiple icons falling on the screen. And I want this to never stop. It should just be an infinite rain of icons. And it shouldn't be systematic, like one wave after the other. It should just be random infinite rain of icons.
I have attempted to use a stream and a new event would be pushed onto the stream randomly. But then I couldn't figure out how to render FallingIcons based on the items in the stream.
I wonder if this is possible in Flutter. Thanks.

this kind of "raining/falling animation" could be implemented with one CustomPaint widget (instead of rebuilding lots of widgets with AnimatedBuilder)
the core idea is to provide a Listenable variable to CustomPainter.repaint property (see super(repaint: ...) below) - of course you need to change your CustomPainter so that it draws your vertically falling images
class FooSpritePaint extends StatefulWidget {
#override
State<FooSpritePaint> createState() => _FooSpritePaintState();
}
class _FooSpritePaintState extends State<FooSpritePaint> with TickerProviderStateMixin {
late Ticker ticker;
final notifier = ValueNotifier(Duration.zero);
ui.Image? sprite;
#override
void initState() {
super.initState();
ticker = Ticker(_tick);
rootBundle.load('images/sprites.png')
.then((data) => decodeImageFromList(data.buffer.asUint8List()))
.then(_setSprite);
}
_tick(Duration d) => notifier.value = d;
_setSprite(ui.Image image) {
setState(() {
// print('image: $image');
sprite = image;
ticker.start();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomPaint(
foregroundPainter: SpritePainter(sprite, notifier),
child: Center(
child: TextButton(
onPressed: () => ticker.isTicking? ticker.stop() : ticker.start(),
child: const Text('click to stop / start', textScaleFactor: 1.5),
),
),
),
);
}
}
class SpritePainter extends CustomPainter {
final ui.Image? sprite;
final ValueNotifier<Duration> notifier;
final p = Paint();
SpritePainter(this.sprite, this.notifier) : super(repaint: notifier);
#override
void paint(Canvas canvas, Size size) {
canvas.clipRect(Offset.zero & size);
if (sprite != null) {
final ms = notifier.value.inMilliseconds;
final frame = ms ~/ 80;
// print(frame);
final frames = [frame, frame + 2, frame + 4].map((f) => f % 6).toList();
final spritePhases = [
phase(size.width, ms * 0.066),
phase(size.width, ms * 0.075 + 40),
phase(size.width, ms * 0.075 + 80),
];
final transforms = [
for (int i = 0; i < spritePhases.length; i++)
ui.RSTransform(1, 0, spritePhases[i][0], size.height / 2 - 45),
];
final rects = [
for (int i = 0; i < spritePhases.length; i++)
Rect.fromLTWH(frames[i] * 100, spritePhases[i][1] * 100, 100, 100),
];
canvas.drawAtlas(sprite!, transforms, rects, null, null, null, p);
}
}
List<double> phase(double width, double x) {
final w = width + 100;
x = x % (2 * w);
return x < w? [x - 100, 0] : [2 * w - x - 100, 1];
}
#override
bool shouldRepaint(SpritePainter oldDelegate) => false;
}
the above sample code uses the following image that should be placed in images/sprites.png folder:
EDIT
and as a proof of concept here you have such a sample CustomPainter:
class Bird {
Bird(int ms, this.rect, List<double> r, Size size) :
startTimeMs = ms,
scale = lerpDouble(1, 0.3, r[0])!,
rotation = pi * lerpDouble(-1, 1, r[2])!,
xSimulation = FrictionSimulation(0.75, r[1] * size.width, lerpDouble(size.width / 2, -size.width / 2, r[1])!),
ySimulation = GravitySimulation(lerpDouble(10, 1000, r[0])!, -rect.height / 2, size.height + rect.height / 2, 100);
final int startTimeMs;
final Rect rect;
final Simulation xSimulation;
final Simulation ySimulation;
final double scale;
final double rotation;
double x(int ms) => xSimulation.x(_normalizeTime(ms));
double y(int ms) => ySimulation.x(_normalizeTime(ms));
bool isDead(int ms) => ySimulation.isDone(_normalizeTime(ms));
double _normalizeTime(int ms) => (ms - startTimeMs) / Duration.millisecondsPerSecond;
RSTransform transform(int ms, Size size) {
final translateY = y(ms);
return RSTransform.fromComponents(
translateX: x(ms),
translateY: translateY,
anchorX: rect.width / 2,
anchorY: rect.height / 2,
rotation: rotation * translateY / size.height,
scale: scale,
);
}
}
class FallingBirdsPainter extends CustomPainter {
final ui.Image? sprite;
final ValueNotifier<Duration> notifier;
final imagePaint = Paint();
final backgroundPaint = Paint()..color = Colors.black26;
final random = Random();
final birds = <Bird>[];
int nextReport = 0;
static const spriteRects = [
Rect.fromLTRB(000, 0, 103, 140),
Rect.fromLTRB(103, 0, 217, 140),
Rect.fromLTRB(217, 0, 312, 140),
Rect.fromLTRB(312, 0, 410, 140),
];
FallingBirdsPainter(this.sprite, this.notifier) : super(repaint: notifier);
#override
void paint(Canvas canvas, Size size) {
canvas.clipRect(Offset.zero & size);
canvas.drawPaint(backgroundPaint);
if (sprite != null) {
final ms = DateTime.now().millisecondsSinceEpoch;
if (random.nextDouble() < 0.15) {
// drop new bird
birds.add(Bird(ms, spriteRects[random.nextInt(4)], List.generate(3, (i) => random.nextDouble()), size));
}
final transforms = birds.map((bird) => bird.transform(ms, size)).toList();
final rects = birds.map((bird) => bird.rect).toList();
canvas.drawAtlas(sprite!, transforms, rects, null, null, null, imagePaint);
// dead birds cleanup
birds.removeWhere((bird) => bird.isDead(ms));
if (ms >= nextReport) {
nextReport = ms + 6000;
print('flying birds population: ${birds.length}');
}
}
}
#override
bool shouldRepaint(FallingBirdsPainter oldDelegate) => false;
}
it uses the following image:
all you need is to replace
foregroundPainter: SpritePainter(sprite, notifier),
with
foregroundPainter: FallingBirdsPainter(sprite, notifier),
and
rootBundle.load('images/sprites.png')
with
rootBundle.load('images/birds.png')
the final result is similar to this:

Related

Resizing a rotated container in flutter from corners and middle points ( 8 points)

I am developing an shape editor in flutter. For that, I need to add a feature of adding shapes dynamically to the screen. Each of them should be able to independently scale, position, and rotate. There should be an anchor point like in word or in Photoshop to indicate the active shapes and to scale and rotate them. The shape should be positioned with drag and drop.
I found this answer :
Rotate and resize container
But the answer only rotate the shape from the center, I want to scale the shape from the corners also.
The code :
class FooResizer extends StatefulWidget {
#override
_FooResizerState createState() => _FooResizerState();
}
class _FooResizerState extends State<FooResizer> with TickerProviderStateMixin, ChangeNotifier {
Sizer currentSizer;
double angle = 0.0;
AnimationController ctrl;
final Map<String, Sizer> sizers = {
'l': Sizer('l', Alignment.centerLeft, {'t': 0.5, 'b': 0.5, 'M': 0.5}),
't': Sizer('t', Alignment.topCenter, {'l': 0.5, 'r': 0.5, 'M': 0.5}),
'r': Sizer('r', Alignment.centerRight, {'t': 0.5, 'b': 0.5, 'R': 1.0, 'M': 0.5}),
'b': Sizer('b', Alignment.bottomCenter, {'l': 0.5, 'r': 0.5, 'R': 1.0, 'M': 0.5}),
'R': Sizer('R', Alignment.bottomRight, {}),
'M': Sizer('M', Alignment.center, {}),
};
#override
void initState() {
super.initState();
ctrl = AnimationController(vsync: this, duration: Duration(milliseconds: 300), value: 1.0);
}
#override
Widget build(BuildContext context) {
return ClipRect(
child: ColoredBox(
color: Colors.black12,
// CustomMultiChildLayoutPainter:
// https://gist.github.com/pskink/0f82724b41d9ebe89604782fbf62fe03#file-multi_layout_painter-dart-L447
child: CustomMultiChildLayoutPainter(
delegate: _FooResizerDelegate(sizers, this),
children: [
LayoutId(
id: 'body',
child: AnimatedBuilder(
animation: this,
builder: (ctx, child) {
return Transform.rotate(
angle: angle,
child: child,
);
},
child: Material(color: Colors.grey[300], elevation: 4, child: FlutterLogo()),
),
),
...sizers.values.map(_sizerBuilder),
],
),
),
);
}
Widget _sizerBuilder(Sizer sizer) {
final colors = {
'M': Colors.orange,
'R': Colors.teal,
};
return LayoutId(
id: sizer.id,
child: GestureDetector(
onPanStart: (details) => _panStart(sizer),
onPanUpdate: _panUpdate,
onPanEnd: _panEnd,
child: AnimatedBuilder(
animation: ctrl,
builder: (context, child) {
final color = colors[sizer.id] ?? Colors.green;
return Opacity(
opacity: currentSizer == sizer? 1.0 : Curves.ease.transform(ctrl.value),
child: Container(
decoration: ShapeDecoration(
shape: CircleBorder(side: BorderSide(width: lerpDouble(0.0, 2.0, ctrl.value), color: Colors.black38)),
color: currentSizer == sizer? Color.lerp(Colors.deepPurple, color, ctrl.value) : color,
shadows: [BoxShadow.lerp(BoxShadow(spreadRadius: 2, blurRadius: 4, offset: Offset(2, 2)), null, ctrl.value)],
),
),
);
}
),
),
);
}
_panStart(Sizer sizer) {
currentSizer = sizer;
ctrl.reverse();
}
_panUpdate(DragUpdateDetails details) {
assert(currentSizer != null);
if (currentSizer.id == 'M') {
// move
sizers.values.forEach((sizer) => sizer.center += details.delta);
} else
if (currentSizer.id == 'R') {
// rotate
final localCenter = sizers['M'].center;
final globalCenter = (context.findRenderObject() as RenderBox).localToGlobal(localCenter);
final angle0 = (details.globalPosition - details.delta - globalCenter).direction;
final angle1 = (details.globalPosition - globalCenter).direction;
final deltaAngle = angle1 - angle0;
sizers.values
.where((sizer) => sizer.id != 'M')
.forEach((sizer) {
final vector = sizer.center - localCenter;
sizer.center = localCenter + Offset.fromDirection(vector.direction + deltaAngle, vector.distance);
});
angle += deltaAngle;
} else {
// resize
final adjustedAngle = angle + currentSizer.angleAdjustment;
final rotatedDistance = details.delta.distance * math.cos(details.delta.direction - adjustedAngle);
final vector = Offset.fromDirection(adjustedAngle, rotatedDistance);
currentSizer.center += vector;
currentSizer.dependents.forEach((id, factor) => sizers[id].center += vector * factor);
}
notifyListeners();
}
_panEnd(DragEndDetails details) {
assert(currentSizer != null);
// currentSizer = null;
ctrl.forward();
}
}
class _FooResizerDelegate extends MultiChildLayoutPainterDelegate {
static const SIZE = 48.0;
final Map<String, Sizer> sizers;
_FooResizerDelegate(this.sizers, Listenable relayout) : super(relayout: relayout);
#override
void performLayout(Size size) {
sizers['M'].center ??= init(size);
for (var sizer in sizers.values) {
layoutChild(sizer.id, BoxConstraints.tight(Size(SIZE, SIZE)));
positionChild(sizer.id, sizer.center - Offset(SIZE / 2, SIZE / 2));
}
final w = (sizers['l'].center - sizers['r'].center).distance;
final h = (sizers['t'].center - sizers['b'].center).distance;
layoutChild('body', BoxConstraints.tight(Size(w, h)));
positionChild('body', sizers['M'].center - Offset(w / 2, h / 2));
}
Offset init(Size size) {
final rect = (Offset.zero & size).deflate(24);
print('init rect: $rect');
for (var sizer in sizers.values) {
sizer
..center = sizer.alignment.withinRect(rect)
..angleAdjustment = sizer.alignment.x == 0? math.pi / 2 : 0;
}
return sizers['M'].center;
}
#override
bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => true;
#override
void foregroundPaint(Canvas canvas, Size size) {
}
#override
void paint(Canvas canvas, Size size) {
final w = (sizers['r'].center - sizers['l'].center).distance;
final h = (sizers['b'].center - sizers['t'].center).distance;
final rect = Rect.fromCenter(center: sizers['M'].center, width: w, height: h);
final angle = (sizers['r'].center - sizers['l'].center).direction;
final matrix = Matrix4.identity()
..translate(rect.center.dx, rect.center.dy)
..rotateZ(angle)
..translate(-rect.center.dx, -rect.center.dy);
final transformedRect = MatrixUtils.transformRect(matrix, rect);
final points = [
Offset(transformedRect.left, 0), Offset(transformedRect.left, size.height),
Offset(0, transformedRect.top), Offset(size.width, transformedRect.top),
Offset(transformedRect.right, 0), Offset(transformedRect.right, size.height),
Offset(0, transformedRect.bottom), Offset(size.width, transformedRect.bottom),
];
canvas.drawPoints(PointMode.lines, points, Paint());
}
}
class Sizer {
final String id;
final Alignment alignment;
final Map<String, double> dependents;
Offset center;
double angleAdjustment;
Sizer(this.id, this.alignment, this.dependents);
}
The result :
what I am trying to achieve :

How to set any widget on screen at specific position with responsiveness in flutter

I am trying to set any widget to screen at any position so if user open app in other devices the widget must should remain at that give position , for example if I set widget on 1/4 part of the screen then it should be the same in every device . I tested in simulator from Iphone 8 to Ipad 8th gen.
Below is my code I am using Positioned.fromRect and passing Offset(dx,dy). I also tried MediaQuery but all in vain . Please guide me even if you find my question stupid.
Reference app Story click or post maker app with pre built in templates.
This is simple constructor.
FlutterSimpleStickerImage(
this.image, {
Key key,
this.width,
this.height,
this.viewport,
this.minScale = 1.0,
this.maxScale = 2.0,
this.onTapRemove,
}) : super(key: key);
Widget image;
final double width;
final double height;
final Size viewport;
final double minScale;
final double maxScale;
A class for positioning widget in stack the Position.fromRect()
class _FlutterSimpleStickerImageState extends State<FlutterSimpleStickerImage> {
_FlutterSimpleStickerImageState();
double _scale = 1.0;
double _previousScale = 1.0;
Offset _previousOffset = Offset(0, 0);
Offset _startingFocalPoint = Offset(0, 0);
Offset _offset = Offset(0, 0);
double _rotation = 0.0;
double _previousRotation = 0.0;
bool _isSelected = false;
#override
void dispose() {
super.dispose();
_offset = Offset(0, 0);
_scale = 1.0;
}
#override
Widget build(BuildContext context) {
double sizewidth=183;
double sizeheight=0;
return Positioned.fromRect(
rect: Rect.fromPoints(Offset(sizewidth, sizeheight),Offset(sizewidth , sizeheight )),
child: Container(
child: Stack(
children: <Widget>[
Center(
child: Transform(
transform: Matrix4.diagonal3(Vector3(_scale, _scale, _scale)),
// ..setRotationZ(_rotation),
alignment: FractionalOffset.center,
child: GestureDetector(
onScaleStart: (ScaleStartDetails details) {
_startingFocalPoint = Offset(sizewidth,sizeheight);
_previousOffset = Offset(sizewidth,sizeheight);
_previousRotation = _rotation;
_previousScale = _scale;
// print(
// "begin - focal : ${details.focalPoint}, local : ${details.localFocalPoint}");
},
onScaleUpdate: (ScaleUpdateDetails details) {
_scale = min(
max(_previousScale * details.scale, widget.minScale),
widget.maxScale);
_rotation = details.rotation;
final Offset normalizedOffset =
(_startingFocalPoint - _previousOffset) /
_previousScale;
Offset __offset =
details.focalPoint - (normalizedOffset * _scale);
__offset = Offset(max(__offset.dx, -widget.width),
max(__offset.dy, -widget.height));
__offset = Offset(min(__offset.dx, widget.viewport.width),
min(__offset.dy, widget.viewport.height));
setState(() {
_offset = __offset;
// print("move - $_offset, scale : $_scale");
});
},
onTap: () {
setState(() {
_isSelected = !_isSelected;
});
},
onTapCancel: () {
setState(() {
_isSelected = false;
});
},
onDoubleTap: () {
setState(() {
_scale = 1.0;
});
},
onLongPress: (){
setState(() {
widget.image=Container(height: 100,width: 100,color: Color.fromRGBO(255, 150, 0, 1.0),);
});
},
child: Container(child: widget.image),
),
),
),
_isSelected
? Positioned(
top: 12,
right: 12,
width: 24,
height: 24,
child: Container(
child: IconButton(
padding: EdgeInsets.zero,
icon: Icon(Icons.remove_circle),
color: Color.fromRGBO(255, 150, 0, 1.0),
onPressed: () {
print('tapped remove sticker');
if (this.widget.onTapRemove != null) {
this.widget.onTapRemove(this.widget);
}
},
),
),
)
: Container(),
],
),
),
);
}
void hideRemoveButton() {
setState(() {
_isSelected = false;
});
}
}

'The method 'call' was called on null.' in Flutter

I am trying to extract a value, touchedIndex, from a child widget PieChartWidget that I have to make a pie chart with a hole in the middle using fl_chart, into its parent widget Co2Tracker to allow me to create some text in the middle of the pie chart that changes when a segment of the pie chart is pressed. This returns an error but everything works visually other than the pie chart segments no longer expand.To make debugging easier I replaced the setState(){isTouched = isTouchedNew} with print(isTouchedNew) but this still returns the error below, however, the pie chart now expands.
PieChart example
When using a callback function
int isTouched = -1;
setIsTouched(int isTouchedNew) {
print(isTouchedNew);
}
integrated within a stateful widget I get an output of
════════ Exception caught by gesture ═══════════════════════════════════════════════════════════════
The method 'call' was called on null.
Receiver: null
Tried calling: call(-1)
parent main page
class Co2Tracker extends StatefulWidget {
#override
_Co2TrackerState createState() => _Co2TrackerState();
}
class _Co2TrackerState extends State<Co2Tracker> {
List<String> pieNames = ['Travel', 'Gas', 'Electricity', 'Food shop', 'Water',];
int isTouched = -1;
double radiusNormal = 60;
double centerRadius = 50;
double radiusExpansion = 1.2;
setIsTouched(int isTouchedNew) {
print(isTouchedNew);
}
#override
Widget build(BuildContext context) {
//double co2 = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: appBarTemplate('CO\u2082 Tracker'),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: textStyleTemplateLarge(text: 'Your CO\u2082 emissions today', textColor: Colors.green[400]),
),
AspectRatio(
aspectRatio: 200/((radiusExpansion*radiusNormal) + centerRadius),
child: Row(
children: <Widget>[
Expanded(
child: Stack(
children: <Widget>[
Center(
child: FlatButton(child: textStyleTemplateLarge(text: '70kg', textColor: Colors.black),),
),
Center(
child: PieChartWidget(radiusNormal: radiusNormal, centerRadius: centerRadius, radiusExpanded: (radiusExpansion*radiusNormal), pieNames: pieNames,),
),
],
),
),
],
),
),
],
),
);
}
}
child pie chart widget
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
class PieChartWidget extends StatefulWidget {
final double radiusNormal;
final double radiusExpanded;
final double centerRadius;
final double fontSizeNormal;
final double fontSizeExpanded;
final List<String> pieNames;
final Function(int) setIsTouched;
PieChartWidget({this.radiusNormal, this.radiusExpanded, this.centerRadius, this.fontSizeNormal, this.fontSizeExpanded, this.pieNames, this.setIsTouched});
#override
_PieChartWidgetState createState() => _PieChartWidgetState(radiusNormal: radiusNormal, radiusExpanded: radiusExpanded, centerRadius: centerRadius,
fontSizeNormal: fontSizeNormal, fontSizeExpanded: fontSizeExpanded, pieNames: pieNames, setIsTouched: setIsTouched,);
}
class _PieChartWidgetState extends State<PieChartWidget> {
int touchedIndex = -1;
double radiusNormal;
double radiusExpanded;
double centerRadius;
double fontSizeNormal;
double fontSizeExpanded;
List<String> pieNames;
Function(int) setIsTouched;
_PieChartWidgetState({this.radiusNormal = 50, this.radiusExpanded = 60, this.centerRadius = 40, this.fontSizeNormal = 16, this.fontSizeExpanded = 25,
this.pieNames, this.setIsTouched,});
#override
Widget build(BuildContext context) {
return PieChart(
PieChartData(
sections: pieSections(pieNames),
borderData: FlBorderData(show: false),
sectionsSpace: 0,
centerSpaceRadius: centerRadius,
pieTouchData: PieTouchData(touchCallback: (pieTouchResponse) {
setState(() {
if (pieTouchResponse.touchInput is FlLongPressEnd ||
pieTouchResponse.touchInput is FlPanEnd) {
touchedIndex = -1;
} else {
touchedIndex = pieTouchResponse.touchedSectionIndex;
}
});
widget.setIsTouched(touchedIndex);
}),
),
);
}
-----------------I-think-everything-below-this-is-irrelevant-but-I-could-be-wrong-------------------------------------------
List<PieChartSectionData> pieSections(List<String> pieNames,) {
return List.generate(5, (i) {
final isTouched = i == touchedIndex;
final double fontSize = isTouched ? 25 : 16;
final double radius = isTouched ? radiusExpanded : radiusNormal;
final int colorNumber = (1+i) * 100;
final List<double> valueList = [20, 10, 10, 20, 30];
final double total = valueList.fold(0, (a, b) => a+b);
switch (i) {
case 0:
return sectionData(valueList[i], Colors.pink[300], title(isTouched, valueList[i], total, pieNames[i]), radius, fontSize);
case 1:
return sectionData(valueList[i], Colors.blue[600], title(isTouched, valueList[i], total, pieNames[i]), radius, fontSize);
case 2:
return sectionData(valueList[i], Colors.amber[300], title(isTouched, valueList[i], total, pieNames[i]), radius, fontSize);
case 3:
return sectionData(valueList[i], Colors.purple[300], title(isTouched, valueList[i], total, pieNames[i]), radius, fontSize);
case 4:
return sectionData(valueList[i], Colors.blue[300], title(isTouched, valueList[i], total, pieNames[i]), radius, fontSize);
default:
return null;
}
});
}
PieChartSectionData sectionData(double value, Color color, String title, double radius, double fontSize,){
return PieChartSectionData(
value: value, color: color, title: title, radius: radius,
titleStyle: TextStyle(color: Colors.black, fontSize: fontSize, fontWeight: FontWeight.bold),
);
}
String title(bool isTouched, double value, double total, String title,) {
return isTouched ? '${double.parse((value/total*100).toStringAsFixed(2))}%' : '$title';
}
}
My thoughts so far have been that maybe it is to do with initialization of variables or maybe this touchCallback function from the child page line 43 but no luck so far.
Thanks in advance for your time.
The problem is that when your printing an integer.
setIsTouched(int isTouchedNew) { print(isTouchedNew); }
You're supposed to print it like this
print("${isTouchedNew}")
Umm..not sure if you tried this,
int isTouched = -1;
setIsTouched(int isTouchedNew) {
setState(() {
isTouched=isTouchedNew;
print(isTouchedNew);
}
}

How to move around your widgets (Image) after zooming (Flutter)?

I want to zoom an image in flutter and I am able to do it using GestureDetector and TransForm but I am not able to move around my image . It is just zooming . I want implement something dragging so that after zooming I can move around my image .
How can I do this ?
P.S. I tried using onPanStart ... in GestureDetector but I can't use both onScaleStart and onPanStart .
Here is my code :
class _ImageViewState extends State<ImageView> {
File img;
double _scale = 1.0;
double _prev = 1.0;
_ImageViewState(this.img);
#override
Widget build(BuildContext context) {
return SafeArea(
child: GestureDetector(
onScaleStart: (ScaleStartDetails details){
setState(() {
_scale = _prev;
});
},
onScaleUpdate: (ScaleUpdateDetails details){
setState(() {
_scale = _prev * details.scale;
});
print(_scale);
},
onScaleEnd: (ScaleEndDetails details){
setState(() {
_prev = _scale;
});
},
child : Transform(
alignment : FractionalOffset.center,
transform: Matrix4.diagonal3(Vector3(_scale , _scale , _scale)),
child: Image.file(img),
)
),
);
}
}
I have tried to implement something similar in my project. I'm using the matrix_gesture_detector package and created the following custom widget:
import 'package:flutter/material.dart';
import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';
class ZoomableWidget extends StatefulWidget {
final Widget child;
const ZoomableWidget({Key key, this.child}) : super(key: key);
#override
_ZoomableWidgetState createState() => _ZoomableWidgetState();
}
class _ZoomableWidgetState extends State<ZoomableWidget> {
Matrix4 matrix = Matrix4.identity();
Matrix4 zerada = Matrix4.identity();
#override
Widget build(BuildContext context) {
return GestureDetector(
onDoubleTap: (){
setState(() {
matrix = zerada;
});
},
child: MatrixGestureDetector(
shouldRotate: false,
onMatrixUpdate: (Matrix4 m, Matrix4 tm, Matrix4 sm, Matrix4 rm) {
setState(() {
matrix = m;
});
},
child: Transform(
transform: matrix,
child: widget.child,
),
),
);
}
}

Changing colour of CustomPaint changes for all previous points

So I'm trying to create a draw app using Flutter following the "signature canvas" method. However, I'm having trouble being able to change the colour of the CustomPaint object without it already changing the colours for each line draw before the change as shown here:
As you can see, the colour change happens once the Page Widget's state is changed (either by clicking on the main FAB or if I were to draw on the canvas again). Here is my code below for my DrawPage:
class DrawPage extends StatefulWidget {
#override
DrawPageState createState() => new DrawPageState();
}
class DrawPageState extends State<DrawPage> with TickerProviderStateMixin {
AnimationController controller;
List<Offset> points = <Offset>[];
Color color = Colors.black;
StrokeCap strokeCap = StrokeCap.round;
double strokeWidth = 5.0;
#override
void initState() {
super.initState();
controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
RenderBox object = context.findRenderObject();
Offset localPosition =
object.globalToLocal(details.globalPosition);
points = new List.from(points);
points.add(localPosition);
});
},
onPanEnd: (DragEndDetails details) => points.add(null),
child: CustomPaint(
painter: Painter(
points: points,
color: color,
strokeCap: strokeCap,
strokeWidth: strokeWidth),
size: Size.infinite,
),
),
),
floatingActionButton:
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Container(
height: 70.0,
width: 56.0,
alignment: FractionalOffset.topCenter,
child: ScaleTransition(
scale: CurvedAnimation(
parent: controller,
curve: Interval(0.0, 1.0 - 0 / 3 / 2.0, curve: Curves.easeOut),
),
child: FloatingActionButton(
mini: true,
child: Icon(Icons.clear),
onPressed: () {
points.clear();
},
),
),
),
Container(
height: 70.0,
width: 56.0,
alignment: FractionalOffset.topCenter,
child: ScaleTransition(
scale: CurvedAnimation(
parent: controller,
curve: Interval(0.0, 1.0 - 1 / 3 / 2.0, curve: Curves.easeOut),
),
child: FloatingActionButton(
mini: true,
child: Icon(Icons.lens),
onPressed: () {},
),
),
),
Container(
height: 70.0,
width: 56.0,
alignment: FractionalOffset.topCenter,
child: ScaleTransition(
scale: CurvedAnimation(
parent: controller,
curve:
Interval(0.0, 1.0 - 2 / 3 / 2.0, curve: Curves.easeOut),
),
child: FloatingActionButton(
mini: true,
child: Icon(Icons.color_lens),
onPressed: () async {
Color temp;
temp = await showDialog(
context: context,
builder: (context) => ColorDialog());
if (temp != null) {
setState(() {
color = temp;
});
}
}))),
FloatingActionButton(
child: AnimatedBuilder(
animation: controller,
builder: (BuildContext context, Widget child) {
return Transform(
transform: Matrix4.rotationZ(controller.value * 0.5 * math.pi),
alignment: FractionalOffset.center,
child: Icon(Icons.brush),
);
},
),
onPressed: () {
if (controller.isDismissed) {
controller.forward();
} else {
controller.reverse();
}
},
),
]),
);
}
}
What I've tried so far:
I've tried playing around with how the points are added to my List of Offsets as this list is recreated after each "draw" gesture such as just adding to the current List without recreating it but this breaks the "draw" gesture:
setState(() {
RenderBox object = context.findRenderObject();
Offset localPosition =
object.globalToLocal(details.globalPosition);
points = new List.from(points);
points.add(localPosition);
});
I've tried making a reference to the CustomPaint object or my Painter object outside of the build() scope and updating the color property that way but this also breaks the "draw" gesture.
Any help would be greatly appreciated!
Also, here's the code for my Painter class in case people wish to see it:
class Painter extends CustomPainter {
List<Offset> points;
Color color;
StrokeCap strokeCap;
double strokeWidth;
Painter({this.points, this.color, this.strokeCap, this.strokeWidth});
#override
void paint(Canvas canvas, Size size) {
Paint paint = new Paint();
paint.color = color;
paint.strokeCap = strokeCap;
paint.strokeWidth = strokeWidth;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], paint);
}
}
}
#override
bool shouldRepaint(Painter oldPainter) => oldPainter.points != points;
}
I think, for different colors you have to use different Paints. I've added small changes to your code, it works.
class DrawPageState extends State<DrawPage> with TickerProviderStateMixin {
...
List<Painter> painterList = [];
#override
Widget build(BuildContext context) {
...
child: CustomPaint(
painter: Painter(
points: points, color: color, strokeCap: strokeCap, strokeWidth: strokeWidth, painters: painterList),
size: Size.infinite,
),
...
onPressed: () async {
Color temp;
temp = await showDialog(
context: context,
builder: (context) => ColorDialog());
if (temp != null) {
setState(() {
painterList
.add(Painter(points: points.toList(), color: color, strokeCap: strokeCap, strokeWidth: strokeWidth));
points.clear();
strokeCap = StrokeCap.round;
strokeWidth = 5.0;
color = temp;
});
}
...
}
}
class Painter extends CustomPainter {
List<Offset> points;
Color color;
StrokeCap strokeCap;
double strokeWidth;
List<Painter> painters;
Painter({this.points, this.color, this.strokeCap, this.strokeWidth, this.painters = const []});
#override
void paint(Canvas canvas, Size size) {
for (Painter painter in painters) {
painter.paint(canvas, size);
}
Paint paint = new Paint()
..color = color
..strokeCap = strokeCap
..strokeWidth = strokeWidth;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], paint);
}
}
}
#override
bool shouldRepaint(Painter oldDelegate) => oldDelegate.points != points;
}
2020, I have a nice solution for this, because the actually chosen doesn't work for me and I see some redundant calls.
So, I start creating a small class:
class _GroupPoints {
Offset offset;
Color color;
_GroupPoints({this.offset, this.color});
}
next, i declare my CustomPainter like this:
class Signature extends CustomPainter {
List<_GroupPoints> points;
Color color;
Signature({
this.color,
this.points,
});
#override
void paint(Canvas canvas, Size size) {
Paint paint = new Paint()
// if you need this next params as dynamic, you can move it inside the for part
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < newPoints.length - 1; i++) {
paint.color = points[i].color;
if (points[i].offset != null && points[i + 1].offset != null) {
canvas.drawLine(points[i].offset, points[i + 1].offset, paint);
}
canvas.clipRect(Offset.zero & size);
}
}
#override
bool shouldRepaint(Signature oldDelegate) => true;
}
And on my widget:
...
class _MyPageState extends State<MyPage> {
...
List<_GroupPoints> points = [];
...
Container(
height: 500,
width: double.infinity,
child: GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
points = new List.from(points)
..add(
new _GroupPoints(
offset: details.localPosition,
color: myDynamicColor,
),
);
});
},
onPanEnd: (DragEndDetails details) {
points.add(
_GroupPoints(
color: myDynamicColor,
offset: null),
);
},
child: CustomPaint(
painter: Signature(
newPoints: points,
color: myDynamicColor,
),
),
),
),
}
On this way we can use multiple draws of points with their respective color. Hope this can help anybody.

Categories

Resources