Good day! I am new to Flutter but I was trapped in the problem to make an AR app
I have almost been frustrated all day because of these problems...
Dart Analysis keeps saying this below...
Non-nullable instance field ['Ar_controller'] must be initialized in flutter. My whole day wasted due to this and tomorrow is my presentation. Please help me out as soon as possible
really appreciate it in advance.
import 'dart:typed_data';
import 'package:flutter_auth_ui/armodelselect.dart';
import 'package:arcore_flutter_plugin/arcore_flutter_plugin.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:network_image_to_byte/network_image_to_byte.dart';
import 'package:vector_math/vector_math_64.dart' as vector;
import 'armodelselect.dart';
class arviewpage extends StatefulWidget {
objmodel modeldes;
arviewpage(this.modeldes);
#override
_arviewpageState createState() => _arviewpageState(this.modeldes);
}
class _arviewpageState extends State<arviewpage> {
_arviewpageState(this.modeldes);
void initstate(){
super.initState();
}
List<double> cubesize;
objmodel modeldes;
ArCoreController arCoreController;
Uint8List byteImage,byteImage1;
Future<bool> _onWillPop() async {
Navigator.of(context).pop(true);
return true;
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
appBar: AppBar(
title: const Text('Xperience'),
),
body: ArCoreView(
onArCoreViewCreated: _onArCoreViewCreated,
),
),
);
}
//functions::::::::::::::::::
void _onArCoreViewCreated(ArCoreController controller) {
arCoreController = controller;
if(modeldes.type=='sphere'){
_addsphere();
}
else if(modeldes.type=="sphere2"){
_addmoonearth();
}
else if(modeldes.type=='cube'){
_addCube();
}
}
//convert image to byteimage
Future<Uint8List> _networkImageToByte(String imageaddress,String imageaddress1) async {
byteImage = await networkImageToByte(imageaddress);
if(imageaddress1!="0"){
byteImage1=await networkImageToByte(imageaddress1);
}
return byteImage;
}
//convert cube size to double
List<String> convertStringtodoublearr(String size){
var cubesize=size.split(",");
for(int i=0;i<cubesize.length;i++){
this.cubesize[i]=double.parse(cubesize[i]);
}
return cubesize;
}
//sphere ar model implementation
Future _addsphere() async {
await _networkImageToByte(modeldes.img_url,modeldes.explain);
//description ar model:::::::::::::::::::::::::::::::
final material = ArCoreMaterial(
metallic: 1,
color: Colors.blue,
//metallic: 1.0,
textureBytes: byteImage1,
);
final cube = ArCoreCube(
materials: [material],
size: vector.Vector3(0.5,0.2,0.1)
);
final node = ArCoreNode(
shape: cube,
position: vector.Vector3(0,-0.4,0.5),
);
//sphere texture model ::::::::::::::::::::::::::::::::::::
final eamaterial = ArCoreMaterial(
metallic: 1,
color: Colors.blue,
textureBytes: byteImage,
);
final sphere = ArCoreSphere(
materials: [eamaterial],
radius: double.parse(modeldes.size[0]),
);
final eanode = ArCoreNode(
shape: sphere,
position: vector.Vector3(0,0,-1),
children: modeldes.explain=="0"?[]:[node],
);
arCoreController.addArCoreNode(eanode);
}
//cube ar model implementation
Future _addCube() async{
await _networkImageToByte(modeldes.img_url,modeldes.explain);
//description ar model::::::::::::::::::::::::::::::::::::
final material = ArCoreMaterial(
metallic: 1,
color: Colors.blue,
//metallic: 1.0,
textureBytes: byteImage1,
);
final cube = ArCoreCube(
materials: [material],
size: vector.Vector3(0.5,0.2,0.1)
);
final node = ArCoreNode(
shape: cube,
position: vector.Vector3(0,-0.4,0.5),
);
//cube texture model ::::::::::::::::::::::::::::::::::;
final cube_material = ArCoreMaterial(
metallic: 1,
color: Colors.blue,
//metallic: 1.0,
textureBytes: byteImage,
);
final cube_cube = ArCoreCube(
materials: [cube_material],
size: vector.Vector3(double.parse(modeldes.size[0]),double.parse(modeldes.size[1]),double.parse(modeldes.size[2])),
);
final cube_node = ArCoreNode(
shape: cube_cube,
children: modeldes.explain=="0"?[]:[node],
position: vector.Vector3(0,0,-1),
);
arCoreController.addArCoreNode(cube_node);
}
Future _addmoonearth() async {
await _networkImageToByte(modeldes.img_url,modeldes.explain);
//description ar model:::::::::::::::::::::::::::::::
final material = ArCoreMaterial(
metallic: 1,
color: Colors.blue,
//metallic: 1.0,
textureBytes: byteImage1,
);
final moon = ArCoreSphere(
materials: [material],
radius: 0.05,
);
final node = ArCoreNode(
shape: moon,
position: vector.Vector3(0.2,0.3,0.5),
);
//sphere texture model ::::::::::::::::::::::::::::::::::::
final eamaterial = ArCoreMaterial(
metallic: 1,
color: Colors.blue,
textureBytes: byteImage,
);
final sphere = ArCoreSphere(
materials: [eamaterial],
radius: double.parse(modeldes.size[0]),
);
final eanode = ArCoreNode(
shape: sphere,
position: vector.Vector3(0,0,-1),
children: modeldes.explain=="0"?[]:[node],
);
arCoreController.addArCoreNode(eanode);
}
#override
void dispose() {
arCoreController.dispose();
super.dispose();
}
}
If some variable is not initialized at definition,
you need to 'late' keyword.
https://dart.dev/null-safety/understanding-null-safety#lazy-initialization
...
late ArCoreController arCoreController;
...
Copy your package name which requires null safety
Go to pub.dev, search for the package name.
Look for similar package with null safety tag.
Copy package name from pub.dev
Paste in your pubspec.yaml in place of old package name.
Click on download button if working in VS Code or type in the command
flutter pub get
Related
I use syncfusion chart for flutter. I have json data on php api at my server.
I had this data in flutter api connect.
My json data structure is like this:
{
"tablo": "neyzi",
"cinsiyet": "erkek",
"boy": {
"P3": [
{
"0.0": 45.9,
"3.0": 56.2,
"6.0": 62.8,
"9.0": 67.4,
"12.0": 70.8,
"15.0": 73.8,
"18.0": 76.4
}
],
},
}
I use this code for prepare data for chart:
import 'package:flutter/material.dart';
import 'package:pediatrirutinmobil/pers_chart/chart_olcumdizisi.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import 'chart_api.dart';
class PersentilChartRepository{
static PersentilChartApiClient _persentilchartApiClient = PersentilChartApiClient();
static List<OlcumDizisi> _p3 =[];
static Future apiden_data_getir ()async{
return await _persentilchartApiClient.veriyigetir();
}
static Future<List<OlcumDizisi>> persentilListesi ()async{
}
static List boyListesi() {
apiden_data_getir().then((value) async{
var P3e = await value.boy.P3[0];
for (final mapEntry in P3e.entries) {
final key = await double.parse(mapEntry.key.toString());
final double value = await double.parse(mapEntry.value.toString());
if (key<=limit){
_p3.add(OlcumDizisi(key, value));
}
}
// _p3.addAll([OlcumDizisi(6,60),OlcumDizisi(7, 80),OlcumDizisi(10, 90)]);
*/
} );
List<ChartSeries<OlcumDizisi,double>> chartSeries = [
new LineSeries<OlcumDizisi, double>(
name: 'P3',
xValueMapper: (OlcumDizisi olcum, _) => olcum.yasay,
yValueMapper: (OlcumDizisi olcum, _) => olcum.olcum,
dataSource: _p3,
color: Colors.red,
width: 0.75,
)
];
return chartSeries;
}
}
class OlcumDizisi {
final double yasay;
final double olcum;
OlcumDizisi(this.yasay, this.olcum);
}
And I use chart page like this:
import 'dart:core';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:pediatrirutinmobil/pers_chart/chart_repo.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
class StackedAreaLineChart extends StatefulWidget {
#override
State<StackedAreaLineChart> createState() => _StackedAreaLineChartState();
}
class _StackedAreaLineChartState extends State<StackedAreaLineChart> {
List _charset;
#override
void initState() async{
_charset = await PersentilChartRepository.boyListesi();
setState(() {
});
// TODO: implement initState
super.initState();
}
#override
void dispose() {
_charset;
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
child: Center(
child: Container(
child: SfCartesianChart(
primaryXAxis: CategoryAxis(),
primaryYAxis: NumericAxis(
numberFormat: NumberFormat.decimalPattern()
),
// Chart title
title: ChartTitle(text: 'TITLE'),
// Enable legend
legend: Legend(isVisible: true),
// Enable tooltip
tooltipBehavior: TooltipBehavior(enable: true),
series:_charset,
enableSideBySideSeriesPlacement: false,
),
),
),
),
);
}
}
so after then all of this page my chart build graphic but if I back another page and reopen chart page Linechart re-build new line beginning point and ending point.
like this:
this
and this
If i use static List data in chart page its perfect but i use static data in future code like this
apiden_data_getir().then((value) async{
_p3.addAll([OlcumDizisi(6,60),OlcumDizisi(7, 80),OlcumDizisi(10, 90)]);
} );
final result same...
is there any idea.
If you have different solution binding api line chart so I thank you for it.
We talk on github and
static Future apiden_data_getir() async {
///this code
if (_p3.isNotEmpty) {
_p3.clear();
}
/// helpfull it's work
final String jsonString = await getJsonFromAssets();
final dynamic jsonResponse = json.decode(jsonString);
var p3e = jsonResponse['boy']['P3'][0];
The problem seems to be with your init state.
Every time you visit the graph page, the same data gets added to the data source repeatedly, creating a closed loop within the graph. You can verify that by putting a debugger point at this line series:_charset to see the repeating values added to the series.
Try wrapping your SfCartesianChart widget with a Future builder and fetching data there instead of making an API call in the initState.
The application that I'm trying to create required the creation of a route that has destinations between the starting and ending point, between the beginning and end provided in the getRouteBetweenCoordinates, I need a way to add a custom Latlong pair that must travel through, instead of finding the quickest route, I need it to route between all points that I provided while still following the road (not just a direct line).
The only method that I could come up with is recalling the setPolyLines function for each stretch that makes up the total route. While this method could get the desired result, it required making multiple API calls, ideally, the entirety of the custom route would be loaded upon that first directions API call.
Here is the code that I'm working with, Is there an easier solution to this problem that I missed? This may be very obvious but I'm new with google maps integration so sorry if that's the case.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
//new polyline between each destination
class Tour extends StatefulWidget {
const Tour({Key? key}) : super(key: key);
#override
_TourState createState() => _TourState();
}
class _TourState extends State<Tour> {
late GoogleMapController mapController;
//poly line variables
Set<Polyline> _polyLine = Set<Polyline>();
List<LatLng> polylineCordinates = [];
late PolylinePoints polylinePoints;
//starting location
static const _start =
CameraPosition(target: LatLng(48.696985, -122.905595), zoom: 17.0);
//METHODS
void _onMapCreated(GoogleMapController controller) {
mapController = controller;
//TODO: provide with start and end point for specific line, end of last ==
//start of next
setPolyLines(PointLatLng(48.696985, -122.905595),
PointLatLng(48.657421, -122.917412));
setPolyLines(PointLatLng(48.657421, -122.917412),
PointLatLng(48.644983, -122.944760));
}
void setPolyLines(PointLatLng start, PointLatLng end) async {
//polyline result DT is a collection of latlng following roads
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
"MY API KEY IS HERE",
//route start
start,
//route end
end);
//list of latlng pairs in order of exectecution
//this is preparing the drawing of the line, the set state plots it out
if (result.status == 'OK') {
result.points.forEach((PointLatLng point) {
polylineCordinates.add(LatLng(point.latitude, point.longitude));
});
}
setState(() {
_polyLine.add(Polyline(
width: 10,
//set id to
polylineId: PolylineId("route"),
color: Color(0xFF00BFA6),
points: polylineCordinates));
});
}
#override
void initState() {
polylinePoints = PolylinePoints();
}
#override
void dispose() {
mapController.dispose();
super.dispose();
}
//upon call, modal sheet toggles from the bottom of screen
modalSheet() {
showModalBottomSheet(
context: context,
builder: (context) {
return Column(
children: [
Container(
height: 200,
color: Colors.amber,
),
Container(
height: 100,
color: Colors.blue,
)
],
);
});
}
//adjusts camera position to the _start location
center() {
mapController.animateCamera(CameraUpdate.newCameraPosition(_start));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GoogleMap(
polylines: _polyLine,
myLocationButtonEnabled: false,
zoomControlsEnabled: false,
onMapCreated: _onMapCreated,
initialCameraPosition: _start),
floatingActionButton: FloatingActionButton(
onPressed: () => center(), child: Icon(Icons.add)),
);
}
}
You can use wayPoints parameter of getRouteBetweenCoordinates method which accepts a list of PolylineWayPoint (List<PolylineWayPoint>).
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
googleAPiKey,
PointLatLng(48.696985, -122.905595),
PointLatLng(48.644983, -122.944760),
wayPoints: [PolylineWayPoint(location: "48.657421,-122.917412")]);
Please see the image below for the result using your sample code.
I have bounds. bounds should be extended with new LatLng.
var bounds = LatLngBounds();
for (var latlng in _list) bounds.extend(latlng);
I want to implement this extension:
extension LatLngBoundsExtend on LatLngBounds {
extend(LatLng _latLng){
//I want to change the object which this method is called from
this = LatLngBounds(southwest: /* some magic code*/, northeast: /* some magic code*/ );
}
}
It would have been possible to change the original object if its properties were not final. This is not the case for LatLngBounds.southwest and LatLngBounds.northeast.
If I remember correctly, LatLngBounds is an immutable class representing a latitude/longitude aligned rectangle.
final myLatLngBounds = LatLngBounds(southwest, northeast);
myLatLngBounds.extend(latlng);
If this extend modifies this how would you access this? 😮
I think this extend should rather be part of some kind of State Management.
See the following, using Riverpod Hooks package:
Instead of changing the object (since it is immutable), you return a new one in your extension:
extension RectX on Rect {
Rect extend(Offset offset) {
return Rect.fromLTRB(
min(left, offset.dx),
min(top, offset.dy),
max(right, offset.dx),
max(bottom, offset.dy),
);
}
}
Then, when you need to modify the object, you work on a State Object managed by Flutter or any other State Management System:
final rectProvider =
StateNotifierProvider<RectNotifier>((ref) => RectNotifier());
class RectNotifier extends StateNotifier<Rect> {
RectNotifier([Rect state])
: super(state ?? Rect.fromLTRB(100, 100, 200, 200));
void extend(Offset offset) {
state = state.extend(offset);
}
}
Here is a Minimal Working Example:
import 'dart:math' show min, max;
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(
ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
),
);
}
class HomePage extends HookWidget {
#override
Widget build(BuildContext context) {
final rect = useProvider(rectProvider.state);
return Scaffold(
body: GestureDetector(
onTapDown: (details) =>
context.read(rectProvider).extend(details.localPosition),
child: Container(
color: Colors.black12,
padding: EdgeInsets.all(16.0),
child: Stack(
children: [
Positioned(
top: rect.top,
left: rect.left,
child: Container(
width: rect.width,
height: rect.height,
color: Colors.amber.shade400),
),
],
),
),
),
);
}
}
final rectProvider =
StateNotifierProvider<RectNotifier>((ref) => RectNotifier());
class RectNotifier extends StateNotifier<Rect> {
RectNotifier([Rect state])
: super(state ?? Rect.fromLTRB(100, 100, 200, 200));
void extend(Offset offset) {
state = state.extend(offset);
}
}
extension RectX on Rect {
Rect extend(Offset offset) {
return Rect.fromLTRB(
min(left, offset.dx),
min(top, offset.dy),
max(right, offset.dx),
max(bottom, offset.dy),
);
}
}
You can change the fields if they are not final. But you cannot change object as this = new Class().
PS.
southwest and northeast are final fields of LatLngBounds. So you can't change them with an extension
https://github.com/flutter/plugins/blob/master/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/location.dart
Extension methods can mutate this, but they cannot reassign/rebind it. Note that that is true for normal methods too and isn't specific to extension methods.
Suppose that reassigning this were possible. Consider the following code:
var foo = Foo();
var sameFoo = foo;
foo.reassignThis();
Would foo and sameFoo still refer to the same object? It would be surprising if they now referred to different objects. However, if they do still refer to the same object, that would mean that the VM/runtime would need to be able to easily and quickly find all references to an object so that it can update them.
Even if the VM/runtime could do that, then consider:
class Base {
Base(this.x);
int x;
void reassignThis() {
this = Base(x + 1); // Not legal!
}
}
class Derived extends Base {
Derived(int x) : super(x);
int y;
}
void main() {
var derived = Derived(0);
derived.reassignThis();
print(derived.y);
}
After calling reassignThis(), what would derived be? Would it still be a Derived object? Would it be just a Base object?
Reassigning this isn't possible, and it isn't something that could be improved; fundamentally it doesn't make much sense.
Now, instead of reassigning this, you could implement something like:
class MyLatLngBounds implements LatLngBounds {
MyLatLngBounds(this._actual);
LatLngBounds _actual;
LatLng get northeast => _actual.northeast;
LatLng get southwest => _actual.southwest;
// ... other methods that forward to [_actual] ...
void extend(LatLng _latLng) {
_actual = LatLngBounds(/* some magic code */);
}
}
and then try to use MyLatLngBounds everywhere instead of LatLngBounds.
I'm stuck with a syntax error in Android Studio with my flutter project, where I could not instantiate a ui.Gradient using its ui.Gradient.linear constructor.
Code
This is within a CustomPainter child class.
I've posted the compiler errors as comments below.
import 'dart:ui';
...
#override
void paint(Canvas canvas, Size size) {
final tracePaint = Paint()
..strokeJoin = StrokeJoin.round
..strokeWidth = 2.0
..color = traceColor
..style = PaintingStyle.stroke;
...
// HERE: I've tried several ways as below and I simply cannot use this constructor.
tracePaint.shader = Gradient.linear(
from: Offset(size.width-currentX.toDouble(), size.height),
to: Offset(size.width-currentX.toDouble(), 0),
colors: colors,
colorStops: colorStops,).createShader(...); // The method 'linear' isn't defined for the class Gradient.
tracePaint.shader = ui.Gradient.linear(...).createShader(...); // Undefined name 'ui'.
tracePaint.shader = dart.ui.Gradient.linear(...).createShader(...); // Undefined name 'dart'.
Efforts made
I've checked the documentation:
https://api.flutter.dev/flutter/dart-ui/Gradient-class.html
https://api.flutter.dev/flutter/dart-ui/Gradient/Gradient.linear.html
, but failed to find where I'm wrong.
Tips will be appreciated.
You can copy paste run full code below
Step 1: import 'dart:ui' as ui;
Step 2: Please remove from: to: colors: colorStops:
working demo
full code
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
class X1Painter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final tracePaint = Paint()
..strokeJoin = StrokeJoin.round
..strokeWidth = 2.0
..color = Colors.blue
..style = PaintingStyle.stroke;
// create a bounding square, based on the centre and radius of the arc
Rect rect = new Rect.fromCircle(
center: new Offset(165.0, 55.0),
radius: 180.0,
);
tracePaint.shader = ui.Gradient.linear(
Offset(0, 0),
Offset(200, 200),
<Color>[
Colors.green.withOpacity(1.0),
Colors.green.withOpacity(0.3),
Colors.yellow.withOpacity(0.2),
Colors.red.withOpacity(0.1),
Colors.red.withOpacity(0.0),
],
[
0.0,
0.5,
0.7,
0.9,
1.0,
],
);
// and draw an arc
canvas.drawArc(rect, pi / 4, pi * 3 / 4, true, tracePaint);
}
#override
bool shouldRepaint(X1Painter oldDelegate) {
return true;
}
}
class X1Demo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: const Text('Arcs etc')),
body: new CustomPaint(
painter: new X1Painter(),
),
);
}
}
void main() {
runApp(
new MaterialApp(
theme: new ThemeData.dark(),
home: new X1Demo(),
),
);
}
I'd like to achieve the path animation effect as seen over here :
This animation (I couldn't include it because the gif is too big)
I only want to achieve the path on the map animation, I know I need to use a stacked, place my map, then use a Painter to paint such path, but how can I animate it ?
I know this question has an accepted answer, but I'd like to show an alternate solution to this problem.
First of all, creating a custom path from individual points is not optimal for the following:
calculating the length of each segment is not trivial
animating the steps evenly at small increments is difficult and resource-heavy
does not work with quadratic / bezier segments
Just like in the good old Android there is this path tracing method, so does a very similar PathMetrics exist in Flutter.
Building upon the accepted answer of this question, here is a much more generic way of animating any path.
So given a path and an animation percent, we need to extract a path from the start until that percent:
Path createAnimatedPath(
Path originalPath,
double animationPercent,
) {
// ComputeMetrics can only be iterated once!
final totalLength = originalPath
.computeMetrics()
.fold(0.0, (double prev, PathMetric metric) => prev + metric.length);
final currentLength = totalLength * animationPercent;
return extractPathUntilLength(originalPath, currentLength);
}
So now I only need to extract a path until a given length (not the percent). We need to combine all existing paths until a certain distance. Then add to this existing path some part of the last path segment.
Doing that is pretty straightforward.
Path extractPathUntilLength(
Path originalPath,
double length,
) {
var currentLength = 0.0;
final path = new Path();
var metricsIterator = originalPath.computeMetrics().iterator;
while (metricsIterator.moveNext()) {
var metric = metricsIterator.current;
var nextLength = currentLength + metric.length;
final isLastSegment = nextLength > length;
if (isLastSegment) {
final remainingLength = length - currentLength;
final pathSegment = metric.extractPath(0.0, remainingLength);
path.addPath(pathSegment, Offset.zero);
break;
} else {
// There might be a more efficient way of extracting an entire path
final pathSegment = metric.extractPath(0.0, metric.length);
path.addPath(pathSegment, Offset.zero);
}
currentLength = nextLength;
}
return path;
}
The rest of the code required to an entire example:
void main() => runApp(
new MaterialApp(
home: new AnimatedPathDemo(),
),
);
class AnimatedPathPainter extends CustomPainter {
final Animation<double> _animation;
AnimatedPathPainter(this._animation) : super(repaint: _animation);
Path _createAnyPath(Size size) {
return Path()
..moveTo(size.height / 4, size.height / 4)
..lineTo(size.height, size.width / 2)
..lineTo(size.height / 2, size.width)
..quadraticBezierTo(size.height / 2, 100, size.width, size.height);
}
#override
void paint(Canvas canvas, Size size) {
final animationPercent = this._animation.value;
print("Painting + ${animationPercent} - ${size}");
final path = createAnimatedPath(_createAnyPath(size), animationPercent);
final Paint paint = Paint();
paint.color = Colors.amberAccent;
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 10.0;
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
class AnimatedPathDemo extends StatefulWidget {
#override
_AnimatedPathDemoState createState() => _AnimatedPathDemoState();
}
class _AnimatedPathDemoState extends State<AnimatedPathDemo>
with SingleTickerProviderStateMixin {
AnimationController _controller;
void _startAnimation() {
_controller.stop();
_controller.reset();
_controller.repeat(
period: Duration(seconds: 5),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: const Text('Animated Paint')),
body: SizedBox(
height: 300,
width: 300,
child: new CustomPaint(
painter: new AnimatedPathPainter(_controller),
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _startAnimation,
child: new Icon(Icons.play_arrow),
),
);
}
#override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}
I created a library for this: drawing_animation
You just have to provide the Path objects to the widget:
Resulting in this image output: imgur
import 'package:drawing_animation/drawing_animation.dart';
//...
List<Paths> dottedPathArray = ...;
bool run = true;
//...
AnimatedDrawing.paths(
this.dottedPathArray,
run: this.run,
animationOrder: PathOrders.original,
duration: new Duration(seconds: 2),
lineAnimation: LineAnimation.oneByOne,
animationCurve: Curves.linear,
onFinish: () => setState(() {
this.run = false;
}),
)),
You don't actually need a Stack; you could use a foregroundPainter over the map image. To animate a CustomPainter pass the AnimationController into its constructor and also to the super constructor. In paint use the value of the animation to decide how much of the path the draw. For example, if value is 0.25, draw just the first 25% of the path.
class AnimatedPainter extends CustomPainter {
final Animation<double> _animation;
AnimatedPainter(this._animation) : super(repaint: _animation);
#override
void paint(Canvas canvas, Size size) {
// _animation.value has a value between 0.0 and 1.0
// use this to draw the first X% of the path
}
#override
bool shouldRepaint(AnimatedPainter oldDelegate) {
return true;
}
}
class PainterDemo extends StatefulWidget {
#override
PainterDemoState createState() => new PainterDemoState();
}
class PainterDemoState extends State<PainterDemo>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _startAnimation() {
_controller.stop();
_controller.reset();
_controller.repeat(
period: Duration(seconds: 5),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: const Text('Animated Paint')),
body: new CustomPaint(
foregroundPainter: new AnimatedPainter(_controller),
child: new SizedBox(
// doesn't have to be a SizedBox - could be the Map image
width: 200.0,
height: 200.0,
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _startAnimation,
child: new Icon(Icons.play_arrow),
),
);
}
}
void main() {
runApp(
new MaterialApp(
home: new PainterDemo(),
),
);
}
Presumably you will have a list of coordinates that define the path. Assuming some list of points you'd draw the complete path with something like:
if (points.isEmpty) return;
Path path = Path();
Offset origin = points[0];
path.moveTo(origin.dx, origin.dy);
for (Offset o in points) {
path.lineTo(o.dx, o.dy);
}
canvas.drawPath(
path,
Paint()
..color = Colors.orange
..style = PaintingStyle.stroke
..strokeWidth = 4.0,
);
When value is less than 1.0 you need to devise a way to draw less than 100% of the path. For example, when value is 0.25, you might only add the first quarter of the points to the path. If your path consisted of relatively few points, you'd probably get the smoothest animation if you calculated the total length of the path and drew just the first segments of the path that added up to a quarter of the total length.