I want to create something like this.
I have managed to create it but anytime I start the app, I get LateInitializationError which says LateInitializationError: Field 'customImage' has not been initialized.
before the slider shows.
This is my code. What am I doing wrong? I also tried the flutter xlider package but it doesn't work anymore since it doesn't support null safety. If you have a better way of changing the slider thumb, it will be greatly appreciated.
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'package:flutter/services.dart';
class CustomSlider extends StatefulWidget {
const CustomSlider({Key? key}) : super(key: key);
@override
_CustomSliderState createState() => _CustomSliderState();
}
class _CustomSliderState extends State<CustomSlider> {
late ui.Image customImage;
double sliderValue = 0.0;
Future<ui.Image> loadImage(String assetPath) async {
ByteData data = await rootBundle.load(assetPath);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
ui.FrameInfo fi = await codec.getNextFrame();
return fi.image;
}
@override
void initState() {
loadImage('images/star.png').then((image) {
setState(() {
customImage = image;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return SliderTheme(
data: SliderThemeData(
trackHeight: 28,
inactiveTrackColor: Colors.grey.shade300,
activeTrackColor: const Color(0xFFFFE900),
thumbShape: SliderThumbImage(customImage),
),
child: Slider(
value: 50,
min: 0,
max: 100,
onChanged: (value) {},
),
);
}
}
class SliderThumbImage extends SliderComponentShape {
final ui.Image image;
SliderThumbImage(this.image);
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return const Size(0, 0);
}
@override
void paint(PaintingContext context, Offset center,
{required Animation<double> activationAnimation,
required Animation<double> enableAnimation,
required bool isDiscrete,
required TextPainter labelPainter,
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required TextDirection textDirection,
required double value,
required double textScaleFactor,
required Size sizeWithOverflow}) {
final canvas = context.canvas;
final imageWidth = image.width;
final imageHeight = image.height;
Offset imageOffset = Offset(
center.dx - (imageWidth / 2),
center.dy - (imageHeight / 2),
);
Paint paint = Paint()..filterQuality = FilterQuality.high;
canvas.drawImage(image, imageOffset, paint);
}
}
initState
runs before the widget is built, but can't be an async function, so customImage = image;
will almost surely run later than the build
function. And since you are using customImage
in the build
function, and customImage
is marked as late, you receive this error.
You can remove late
keyword, make customImage
nullable, and in the build
method check for its value. If it is null (image not loaded yet), display a progress indicator for example.
But this is not perfect, because depending on the time that image loading takes, there is a chance that setState
is invoked while the widget is being built, which would trigger another error.
Although you can solve the above with an addPostFrameCallback
, the best way is to use a FutureBuilder
, load the image first and build your widget afterwards:
late Future<ui.Image> _loadImage;
@override
void initState()
super.initState();
_loadImage = loadImage();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<ui.Image>(
future: _loadImage,
builder: (context, snapshot) {
if (snapshot.hasData || snapshot.data != null) {
return SliderTheme(
data: SliderThemeData(
trackHeight: 28,
inactiveTrackColor: Colors.grey.shade300,
activeTrackColor: const Color(0xFFFFE900),
thumbShape: SliderThumbImage(snapshot.data!),
),
child: Slider(
value: 50,
min: 0,
max: 100,
onChanged: (value) {},
),
);
}
// progress indicator while loading image,
// you can return and empty Container etc. if you like
return CircularProgressIndicator();
});
}