newbie in Flutter. How show markers in the circle in flutter_map. Which day already trying to solve this problem. I can't make the markers in the radius change, or just change color. That's what i have achieved:
Position _currentPosition;
static final List<LatLng> _points = [
LatLng(55.749122, 37.659750),
LatLng(55.770854, 37.626963),
LatLng(55.776937, 37.637949),
LatLng(55.739266, 37.637434),
];
static const _markerSize = 40.0;
List<Marker> _markers;
List<CircleMarker> circle;
#override
void initState() {
super.initState();
_markers = _points
.map(
(LatLng point) => Marker(
point: point,
width: _markerSize,
height: _markerSize,
builder: (_) => Icon(Icons.location_on, size: _markerSize),
),
).toList();
_getCurrentLocation();
}
_getCurrentLocation() {
geolocator
.getCurrentPosition(desiredAccuracy: LocationAccuracy.best)
.then((Position position) {
setState(() {
_currentPosition = position;
});
}).catchError((e) {
print(e);
});
}
#override
Widget build(BuildContext context) {
return FlutterMap(
options: new MapOptions(
minZoom: 16.0,
center: new LatLng(_currentPosition.latitude,_currentPosition.longitude)),
layers: [new TileLayerOptions(urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a','b','c']),
new MarkerLayerOptions(
markers: [
new Marker(
width: 45.0,
height: 45.0,
point: new LatLng(_currentPosition.latitude,_currentPosition.longitude),
builder: (context)=>new Container(
child: IconButton(icon: Icon(Icons.accessibility), onPressed: () {print('Marker tapped!');}),
))]),
MarkerLayerOptions(markers: _markers),
CircleLayerOptions(circles: [ CircleMarker(
point: LatLng(_currentPosition.latitude,_currentPosition.longitude),
color: Colors.blue.withOpacity(0.7),
borderStrokeWidth: 2,
useRadiusInMeter: true,
radius: 2000 // 2000 meters | 2 km
)]),
],
);
}
my result - (this is image)
How to change the markers in the blue circle. For example change the marker icon?
Have you tried changing the builder of your marker, like:
_markers = _points
.map(
(LatLng point) => Marker(
point: point,
width: _markerSize,
height: _markerSize,
builder: (_) => Icon(
Icons.location_city,
size: _markerSize,
color: Colors.red,
),
),
).toList();
First, you need to find the points inside your circle. You can use something like this before mentioned here:
static double getDistanceFromGPSPointsInRoute(List<LatLng> gpsList) {
double totalDistance = 0.0;
for (var i = 0; i < gpsList.length; i++) {
var p = 0.017453292519943295;
var c = cos;
var a = 0.5 -
c((gpsList[i + 1].latitude - gpsList[i].latitude) * p) / 2 +
c(gpsList[i].latitude * p) *
c(gpsList[i + 1].latitude * p) *
(1 - c((gpsList[i + 1].longitude - gpsList[i].longitude) * p)) /
2;
double distance = 12742 * asin(sqrt(a));
totalDistance += distance;
print('Distance is ${12742 * asin(sqrt(a))}');
}
print('Total distance is $totalDistance');
return totalDistance;
}
Then you need to signature inside points with a boolean field.
static final List<NewModel> _points = []; // NewModel contains boolean field with LatLng
After that you can easily customize the icons of the inside points like this:
_markers = _points
.map(
(NewModel newModel) => Marker(
point: newModel.point,
width: _markerSize,
height: _markerSize,
builder: (_) =>
newModel.isInside
? Icon(Icons.location_city, size: _markerSize),
: Icon(Icons.location_on, size: _markerSize),
),
).toList();
1- you can use decoration in container:
new Marker(
width: 45.0,
height: 45.0,
point: new LatLng(_currentPosition.latitude,_currentPosition.longitude),
builder: (context)=> new Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20),
),
child: IconButton(
icon: Icon(Icons.accessibility, color: Colors.white),
onPressed: () {print('Marker tapped!');},
),
)
2- or you can create a custom marker from a image :
import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
...
Future<BitmapDescriptor> createMarkIcon(assetsPath, size) async {
ByteData data = await rootBundle.load(assetsPath);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(),
targetWidth: size);
ui.FrameInfo fi = await codec.getNextFrame();
return BitmapDescriptor.fromBytes(
(await fi.image.toByteData(format: ui.ImageByteFormat.png))
.buffer
.asUint8List(),
);
}
...
BitmapDescriptor _markIcon =
await createMarkIcon('assets/images/marker_icon.png', 60);
Marker _marker = Marker(
position: _latlng,
icon: _markIcon,
onTap: () {
// your code
});
Related
There are other ways of handling this overlay, but AFAIK this should work, and it does sometimes, but not always on Android. On Windows it appeared to work perfectly. I have the latest Flutter (3.0.3) and the latest Dart (2.17.5) and Flutter doctor shows no problems. I have tested this on 2 physical devices (Android 6.0.1 - level 23, and Android 11 - level 30). I have also tested it on an emulator (Android 11 - level 30). The results on all three were very similar - the overlay doesn't always show. I also tested it on Windows and it worked perfectly for all attempts (30).
The code below is a cut-down version of the code to demonstrate the problem. The problem is that the overlay does not always display. After a reboot it will often work well for about 10-12 reloads of the program, and then it is inconsistent, sometimes showing the overlay and sometimes not. As I said, it worked perfectly on Windows.
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
const D_OVERLAY_HEIGHT = 290.0;
const D_BTN_HEIGHT = 45.0;
void main() => runApp(OverlayTestApp());
class OverlayTestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: OverlayTest(),
);
}
}
class OverlayTest extends StatefulWidget {
#override
_PageOverlayTest createState() => _PageOverlayTest();
}
//==== THE MAIN PAGE FOR THE APP ====//
class _PageOverlayTest extends State<OverlayTest> {
TextEditingController? _wController = TextEditingController();
ClassOverlay? _clOverlay;
#override
Widget build(BuildContext context) {
WidgetsFlutterBinding.ensureInitialized();
WidgetsBinding.instance
.addPostFrameCallback((_) => fnOnBuildComplete(context));
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back), onPressed: () => fnExit()),
title: Center(child: Text('Overlay Test'))),
body: WillPopScope(
onWillPop: () async => await _fnDispose(),
child: Column(
children: [
SizedBox(height: 50),
TextField(
controller: _wController,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(3),
),
),
style: TextStyle(fontSize: 16),
autofocus: true,
showCursor: true,
readOnly: true,
toolbarOptions: ToolbarOptions(
copy: false,
cut: false,
paste: false,
selectAll: false,
),
),
],
),
));
}
fnExit() {
dispose();
SystemNavigator.pop();
exit(0);
}
#override
void dispose() {
_fnDispose();
super.dispose();
}
Future<bool> _fnDispose() async {
if (_wController != null) {
_wController!.dispose();
_wController = null;
}
if (_clOverlay != null) {
_clOverlay!.fnDispose();
}
return true;
}
void fnOnBuildComplete(BuildContext context) async {
if (_clOverlay == null) {
double dScreenWidth = MediaQuery.of(context).size.width;
dScreenWidth = dScreenWidth < 510 ? dScreenWidth : 500;
double dScreenHeight = MediaQuery.of(context).size.height;
_clOverlay = ClassOverlay(dScreenWidth, dScreenHeight - 320);
}
_clOverlay!.fnInitContext(context, _wController!);
}
}
//==== CLASS FOR THE OVERLAY ====//
class ClassOverlay {
TextEditingController? _wCtlText;
OverlayEntry? _wOverlayEntry;
final double dScreenWidth;
final double dOverlayTop;
Material? _wOverlayWidget;
ClassOverlay(this.dScreenWidth, this.dOverlayTop) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top]);
_wOverlayWidget = fnCreateOverlayWidget(dScreenWidth: dScreenWidth);
}
//==== AFTER BUILD OF CALLER INITIALIZE WITH CONTEXT AND OVERLAYSTATE ====//
void fnInitContext(BuildContext context, TextEditingController wCtlText) {
try {
_wCtlText = wCtlText;
if (_wOverlayEntry != null) {
_wOverlayEntry!.remove();
_wOverlayEntry = null;
}
if (_wOverlayWidget == null) {
throw ('_wOverlayWidget is null');
}
_wOverlayEntry = OverlayEntry(builder: (context) {
return Positioned(left: 0, top: dOverlayTop, child: _wOverlayWidget!);
});
OverlayState? wOverlayState = Navigator.of(context).overlay;
if (wOverlayState == null) {
throw ('Failed to get OverlayState');
}
if (_wOverlayEntry == null) {
throw ('OverlayEntry is null');
}
wOverlayState.insert(_wOverlayEntry!);
if (!(wOverlayState.mounted)) {
wOverlayState.activate();
}
} catch (vError) {
_wCtlText!.text = '**** Fatal Error ${vError.toString()}';
}
}
void fnDispose() {
if (_wOverlayEntry != null) {
_wOverlayEntry!.remove();
_wOverlayEntry = null;
}
}
}
//==== CLASS - OVERLAY WIDGET ====//
Material fnCreateOverlayWidget({required double dScreenWidth}) {
final double dBtnWidth = (dScreenWidth - 60) / 10;
final double dLineSeparator = 10;
return Material(
child: Container(
height: (D_OVERLAY_HEIGHT),
width: dScreenWidth - 10,
padding: EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey.shade500, width: 2),
borderRadius: BorderRadius.circular(10)),
child: Column(
children: [
fnCreateRow('1234567890', dBtnWidth),
SizedBox(height: dLineSeparator),
fnCreateRow('qwertyuiop', dBtnWidth),
SizedBox(height: dLineSeparator),
fnCreateRow('asdfghjkl', dBtnWidth),
SizedBox(height: dLineSeparator),
fnCreateRow(',zxcvbnm.', dBtnWidth),
SizedBox(height: dLineSeparator),
fnCreateRow('1234567890', dBtnWidth)
],
),
));
}
//==== FUNCTION - CREATE A ROW OF TEXT BUTTONS ====//
Row fnCreateRow(String sRow, double dBtnWidth) {
List<Widget> wRow = [];
double dSpace = sRow.length == 10 ? 0 : (dBtnWidth * (10 - sRow.length)) / 2;
if (dSpace > 0) {
wRow.add(SizedBox(width: dSpace));
}
for (int iNdx = 0; iNdx < sRow.length; iNdx++) {
double dFontSize = sRow[iNdx] == '.' || sRow[iNdx] == ',' ? 30 : 20;
wRow.add(SizedBox(
height: D_BTN_HEIGHT,
width: dBtnWidth,
child: TextButton(
onPressed: () {},
child: Text(sRow[iNdx],
style: TextStyle(
color: Colors.black,
fontSize: dFontSize,
fontWeight: FontWeight.w400)),
)));
}
if (dSpace > 0) {
wRow.add(SizedBox(width: dSpace));
}
return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: wRow);
}
It took a lot of time to find the reason for this problem. I was not aware of the fact that MediaQuery can return zero for the height or width because of a timing issue, and the application program must cater for that. It appears that this was working correctly, but the height and/or width was set to zero. There must be a lot of programs that don't check for that.
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;
});
}
}
So I built my first app. It's a weather app. So far everything works as intended. But there is one problem, whenever I close the app and then reopen it, everything is null (weather forecast, location name, max and min temperature). When I press the refresh button it null is updated to current condition. What I'd like t be able to do is, instead of showing null, I'd like the app to show the last refresh and update it if I press the refresh button. How can I do this.
Keep in mind I'm a newbie.
main.dart:
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'GetLocation.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
void main() {
runApp(AuraWeather());
}
class AuraWeather extends StatefulWidget {
#override
_AuraWeatherState createState() => _AuraWeatherState();
}
class _AuraWeatherState extends State<AuraWeather> {
var apiKey = '5f10958d807d5c7e333ec2e54c4a5b16';
var description;
var city;
var maxTemp;
var minTemp;
var temp;
#override
Widget build(BuildContext context) {
setState(() {
getLocation();
});
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(displayBackground()),
),
),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaY: 2, sigmaX: 2),
child: Container(
color: Colors.black.withOpacity(0.5),
child: Scaffold(
backgroundColor: Colors.transparent,
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
children: <Widget>[
Container(
child: Center(
child: Text(
'$city',
style: TextStyle(
fontSize: 35,
color: Colors.white,
),
),
),
),
Container(
child: Icon(
FontAwesomeIcons.locationArrow,
color: Colors.white,
),
),
Container(
margin: EdgeInsets.only(top: 80),
child: Text(
'$temp' + '°',
style: TextStyle(
fontSize: 50,
color: Colors.white,
fontWeight: FontWeight.w600),
),
),
],
),
Container(
margin: EdgeInsets.only(top: 30),
child: Icon(
Icons.wb_sunny,
color: Colors.white,
size: 100,
),
),
Container(
child: Center(
child: Text(
'$maxTemp ° | $minTemp °',
style: TextStyle(fontSize: 20, color: Colors.white),
),
),
),
Container(
child: Text(
'$description',
style: TextStyle(fontSize: 20, color: Colors.white),
),
),
Container(
child: FlatButton(
child: Icon(
Icons.refresh,
color: Colors.white,
size: 40,
),
color: Colors.transparent,
onPressed: () {
setState(
() {
getLocation();
},
);
},
),
),
],
),
),
),
),
),
);
}
// display background images based on current time
displayBackground() {
var now = DateTime.now();
final currentTime = DateFormat.jm().format(now);
if (currentTime.contains('AM')) {
return 'images/Blood.png';
} else if (currentTime.contains('PM')) {
return 'images/Sun.png';
}
}
//getLocation
void getLocation() async {
Getlocation getlocation = Getlocation();
await getlocation.getCurrentLocation();
print(getlocation.latitude);
print(getlocation.longitude);
print(getlocation.city);
city = getlocation.city;
getTemp(getlocation.latitude, getlocation.longitude);
}
//Get current temp
Future<void> getTemp(double lat, double lon) async {
http.Response response = await http.get(
'https://api.openweathermap.org/data/2.5/weather?lat=$lat&lon=$lon&appid=$apiKey&units=metric');
//print(response.body);
var dataDecoded = jsonDecode(response.body);
description = dataDecoded['weather'][0]['description'];
temp = dataDecoded['main']['temp'];
temp = temp.toInt();
maxTemp = dataDecoded['main']['temp_max'];
maxTemp = maxTemp.toInt();
minTemp = dataDecoded['main']['temp_min'];
minTemp = minTemp.toInt();
print(temp);
}
}
GetLocation.dart:
import 'package:geolocator/geolocator.dart';
class Getlocation {
double latitude;
double longitude;
var city;
//Get current location
Future<void> getCurrentLocation() async {
try {
Position position = await Geolocator()
.getCurrentPosition(desiredAccuracy: LocationAccuracy.best);
latitude = position.latitude;
longitude = position.longitude;
city = await getCityName(position.latitude, position.longitude);
} catch (e) {
print(e);
}
}
//Get city name
Future<String> getCityName(double lat, double lon) async {
List<Placemark> placemark =
await Geolocator().placemarkFromCoordinates(lat, lon);
print('city name is: ${placemark[0].locality}');
return placemark[0].locality;
}
}
Easy solution, would be to use SharedPreferences, then after you refresh weather save every variable to it, like
Future<void> _saveStringInSharedPrefs(String key, String value) async =>
SharedPreferences.getInstance().then((prefs) => prefs.setString(key, value));
for each of the values. You may also want to change vars to types, like double, String and so on.
Then you can add initState to your state, where you set each of the variables to SharedPreferences.getString(variable_key). It will be convenient to use SharedPreferences prefs = SharedPreferences.getInstance() and then call prefs.getString() You could add it in build, but you probably should not, build methods are meant to be really fast, so they could run up to 60 times/s.
Edit:
http.Response response = await http.get(
'https://api.openweathermap.org/data/2.5/weather?lat=$lat&lon=$lon&appid=$apiKey&units=metric');
//print(response.body);
await SharedPreferences.getInstance().then((prefs) {prefs.setString('weather_data', response.body}) // add this line
var dataDecoded = jsonDecode(response.body);
// (rest of the code)
This will save your json to SharedPrefs. Now you only need to extract the function that works with JSON and sets the variables. So it would look something like this:
void _setData(String jsonString) {
var dataDecoded = jsonDecode(response.body);
description = dataDecoded['weather'][0]['description'];
// here it would be safer to have temp be of type 'int' instead of 'var' and set it like this:
// temp = dataDecoded['main']['temp'].toInt();
temp = dataDecoded['main']['temp'];
temp = temp.toInt();
maxTemp = dataDecoded['main']['temp_max'];
maxTemp = maxTemp.toInt();
minTemp = dataDecoded['main']['temp_min'];
minTemp = minTemp.toInt();
}
You can then split getTemp like that:
Future<void> getTemp(double lat, double lon) async {
http.Response response = await http.get(
'https://api.openweathermap.org/data/2.5/weather?lat=$lat&lon=$lon&appid=$apiKey&units=metric');
//print(response.body);
_setData(response.body);
}
And then, when you launch the app you want it to load the values from SharedPreferences. So add this to _AuraWeatherState:
#override
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_setData(prefs.getString('weather_data'));
}
This should work but I didn't have time to check if it executes. So if you have any other questions, I'll be glad to help :)
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.
I'm new to Dart/Flutter framework and I'm still exploring their possibilities.
I know in Android it's possible to take a picture and extract the main color value from it programmatically. (Android example)
I wonder, how would this be achieved in pure Dart? I would like it to be compatible with both iOS and Android operating system.
Here's a simple function which returns the dominant color given an ImageProvider. This shows the basic usage of Palette Generator without all the boilerplate.
import 'package:palette_generator/palette_generator.dart';
// Calculate dominant color from ImageProvider
Future<Color> getImagePalette (ImageProvider imageProvider) async {
final PaletteGenerator paletteGenerator = await PaletteGenerator
.fromImageProvider(imageProvider);
return paletteGenerator.dominantColor.color;
}
Then use FutureBuilder on the output to build a Widget.
I probably think you got a fix but for future searches to this question, I suggest you check Pallete Generator by the flutter team.
I will try and give a simple explanation of how the code works but for a detailed example head over to the plugin's GitHub repo.
The example below is going to take an image then select the dominant colors from it and then display the colors
First, we add the required imports
import 'package:palette_generator/palette_generator.dart';
After that let's create the main application class.
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
...
home: const HomePage(
title: 'Colors from image',
image: AssetImage('assets/images/artwork_default.png',),
imageSize: Size(256.0, 170.0),
...
),
);
}
}
In the image field above, place the image that you want to extract the dominant colors from, i used the image shown here.
Next, we create the HomePage class
#immutable
class HomePage extends StatefulWidget {
/// Creates the home page.
const HomePage({
Key key,
this.title,
this.image,
this.imageSize,
}) : super(key: key);
final String title; //App title
final ImageProvider image; //Image provider to load the colors from
final Size imageSize; //Image dimensions
#override
_HomePageState createState() {
return _HomePageState();
}
}
Lets create the _HomePageState too
class _HomePageState extends State<HomePage> {
Rect region;
PaletteGenerator paletteGenerator;
final GlobalKey imageKey = GlobalKey();
#override
void initState() {
super.initState();
region = Offset.zero & widget.imageSize;
_updatePaletteGenerator(region);
}
Future<void> _updatePaletteGenerator(Rect newRegion) async {
paletteGenerator = await PaletteGenerator.fromImageProvider(
widget.image,
size: widget.imageSize,
region: newRegion,
maximumColorCount: 20,
);
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _kBackgroundColor,
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new AspectRatio(
aspectRatio: 15 / 15,
child: Image(
key: imageKey,
image: widget.image,
),
),
Expanded(child: Swatches(generator: paletteGenerator)),
],
),
);
}
}
The code above just lays out the image and the Swatches which is a class defined below. In initState, we first select a region which the colors will be derived from which in our case is the whole image.
After that we create a class Swatches which receives a PalleteGenerator and draws the swatches for it.
class Swatches extends StatelessWidget {
const Swatches({Key key, this.generator}) : super(key: key);
// The PaletteGenerator that contains all of the swatches that we're going
// to display.
final PaletteGenerator generator;
#override
Widget build(BuildContext context) {
final List<Widget> swatches = <Widget>[];
//The generator field can be null, if so, we return an empty container
if (generator == null || generator.colors.isEmpty) {
return Container();
}
//Loop through the colors in the PaletteGenerator and add them to the list of swatches above
for (Color color in generator.colors) {
swatches.add(PaletteSwatch(color: color));
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
//All the colors,
Wrap(
children: swatches,
),
//The colors with ranking
Container(height: 30.0),
PaletteSwatch(label: 'Dominant', color: generator.dominantColor?.color),
PaletteSwatch(
label: 'Light Vibrant', color: generator.lightVibrantColor?.color),
PaletteSwatch(label: 'Vibrant', color: generator.vibrantColor?.color),
PaletteSwatch(
label: 'Dark Vibrant', color: generator.darkVibrantColor?.color),
PaletteSwatch(
label: 'Light Muted', color: generator.lightMutedColor?.color),
PaletteSwatch(label: 'Muted', color: generator.mutedColor?.color),
PaletteSwatch(
label: 'Dark Muted', color: generator.darkMutedColor?.color),
],
);
}
}
After that lets create a PaletteSwatch class. A palette swatch is just a square of color with an optional label
#immutable
class PaletteSwatch extends StatelessWidget {
// Creates a PaletteSwatch.
//
// If the [color] argument is omitted, then the swatch will show a
// placeholder instead, to indicate that there is no color.
const PaletteSwatch({
Key key,
this.color,
this.label,
}) : super(key: key);
// The color of the swatch. May be null.
final Color color;
// The optional label to display next to the swatch.
final String label;
#override
Widget build(BuildContext context) {
// Compute the "distance" of the color swatch and the background color
// so that we can put a border around those color swatches that are too
// close to the background's saturation and lightness. We ignore hue for
// the comparison.
final HSLColor hslColor = HSLColor.fromColor(color ?? Colors.transparent);
final HSLColor backgroundAsHsl = HSLColor.fromColor(_kBackgroundColor);
final double colorDistance = math.sqrt(
math.pow(hslColor.saturation - backgroundAsHsl.saturation, 2.0) +
math.pow(hslColor.lightness - backgroundAsHsl.lightness, 2.0));
Widget swatch = Padding(
padding: const EdgeInsets.all(2.0),
child: color == null
? const Placeholder(
fallbackWidth: 34.0,
fallbackHeight: 20.0,
color: Color(0xff404040),
strokeWidth: 2.0,
)
: Container(
decoration: BoxDecoration(
color: color,
border: Border.all(
width: 1.0,
color: _kPlaceholderColor,
style: colorDistance < 0.2
? BorderStyle.solid
: BorderStyle.none,
)),
width: 34.0,
height: 20.0,
),
);
if (label != null) {
swatch = ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 130.0, minWidth: 130.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
swatch,
Container(width: 5.0),
Text(label),
],
),
);
}
return swatch;
}
}
Hope this helps, thank you.
//////////////////////////////
//
// 2019, roipeker.com
// screencast - demo simple image:
// https://youtu.be/EJyRH4_pY8I
//
// screencast - demo snapshot:
// https://youtu.be/-LxPcL7T61E
//
//////////////////////////////
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:image/image.dart' as img;
import 'package:flutter/services.dart' show rootBundle;
void main() => runApp(const MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String imagePath = 'assets/5.jpg';
GlobalKey imageKey = GlobalKey();
GlobalKey paintKey = GlobalKey();
// CHANGE THIS FLAG TO TEST BASIC IMAGE, AND SNAPSHOT.
bool useSnapshot = true;
// based on useSnapshot=true ? paintKey : imageKey ;
// this key is used in this example to keep the code shorter.
late GlobalKey currentKey;
final StreamController<Color> _stateController = StreamController<Color>();
//late img.Image photo ;
img.Image? photo;
#override
void initState() {
currentKey = useSnapshot ? paintKey : imageKey;
super.initState();
}
#override
Widget build(BuildContext context) {
final String title = useSnapshot ? "snapshot" : "basic";
return SafeArea(
child: Scaffold(
appBar: AppBar(title: Text("Color picker $title")),
body: StreamBuilder(
initialData: Colors.green[500],
stream: _stateController.stream,
builder: (buildContext, snapshot) {
Color selectedColor = snapshot.data as Color ?? Colors.green;
return Stack(
children: <Widget>[
RepaintBoundary(
key: paintKey,
child: GestureDetector(
onPanDown: (details) {
searchPixel(details.globalPosition);
},
onPanUpdate: (details) {
searchPixel(details.globalPosition);
},
child: Center(
child: Image.asset(
imagePath,
key: imageKey,
//color: Colors.red,
//colorBlendMode: BlendMode.hue,
//alignment: Alignment.bottomRight,
fit: BoxFit.contain,
//scale: .8,
),
),
),
),
Container(
margin: const EdgeInsets.all(70),
width: 50,
height: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: selectedColor!,
border: Border.all(width: 2.0, color: Colors.white),
boxShadow: [
const BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, 2))
]),
),
Positioned(
child: Text('${selectedColor}',
style: const TextStyle(
color: Colors.white,
backgroundColor: Colors.black54)),
left: 114,
top: 95,
),
],
);
}),
),
);
}
void searchPixel(Offset globalPosition) async {
if (photo == null) {
await (useSnapshot ? loadSnapshotBytes() : loadImageBundleBytes());
}
_calculatePixel(globalPosition);
}
void _calculatePixel(Offset globalPosition) {
RenderBox box = currentKey.currentContext!.findRenderObject() as RenderBox;
Offset localPosition = box.globalToLocal(globalPosition);
double px = localPosition.dx;
double py = localPosition.dy;
if (!useSnapshot) {
double widgetScale = box.size.width / photo!.width;
print(py);
px = (px / widgetScale);
py = (py / widgetScale);
}
int pixel32 = photo!.getPixelSafe(px.toInt(), py.toInt());
int hex = abgrToArgb(pixel32);
_stateController.add(Color(hex));
}
Future<void> loadImageBundleBytes() async {
ByteData imageBytes = await rootBundle.load(imagePath);
setImageBytes(imageBytes);
}
Future<void> loadSnapshotBytes() async {
RenderRepaintBoundary boxPaint =
paintKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
//RenderObject? boxPaint = paintKey.currentContext.findRenderObject();
ui.Image capture = await boxPaint.toImage();
ByteData? imageBytes =
await capture.toByteData(format: ui.ImageByteFormat.png);
setImageBytes(imageBytes!);
capture.dispose();
}
void setImageBytes(ByteData imageBytes) {
List<int> values = imageBytes.buffer.asUint8List();
photo;
photo = img.decodeImage(values)!;
}
}
// image lib uses uses KML color format, convert #AABBGGRR to regular #AARRGGBB
int abgrToArgb(int argbColor) {
int r = (argbColor >> 16) & 0xFF;
int b = argbColor & 0xFF;
return (argbColor & 0xFF00FF00) | (b << 16) | r;
}