Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw a Widget on a Canvas?

I am trying to customize the slider widget to have a custom thumb shape. In order to do this I have to extend SliderComponentShape. This class requires me to implement my own paint method. The paint method only gives me a Canvas to draw on. Unfortunately the thumb shape I want to use is rather complex. Drawing it manually would be rather tedious and building it with flutters Widgets would be much easier. Is there a way to paint a Widget to a Canvas?

like image 779
Ymi_Yugy Avatar asked Oct 31 '19 21:10

Ymi_Yugy


People also ask

Which widget is used to draw shapes?

The Canvas widget enables you to draw shapes like lines (in any orientation), circles, rectangle, etc. in your application. Canvas widget draws the shapes inside its view bounds, by using the shape configurations provided in the shapesData property.


1 Answers

You can use an image or vector drawable as a thumb image, and reduce the painting complexity.

For example to get a map slider as below

enter image description here

You can use below code, and use the image file as thumb icon

 class DistanceSlider extends StatefulWidget {
  const DistanceSlider({
    Key key,
    @required this.imageUrl,
    @required this.minDistance,
    @required this.maxDistance,
  }) : super(key: key);
  final String imageUrl;
  final double maxDistance;
  final double minDistance;

  @override
  _DistanceSliderState createState() => _DistanceSliderState();
}

class _DistanceSliderState extends State<DistanceSlider> {
  double distance = 20;

  ui.Image customImage;
  Future<ui.Image> loadImage(String imageUrl) async {
    ByteData data = await rootBundle.load(imageUrl);
    ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    ui.FrameInfo fi = await codec.getNextFrame();
    return fi.image;
  }

  @override
  void initState() {
    loadImage(widget.imageUrl).then((image) {
      setState(() {
        customImage = image;
      });
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: SliderTheme(
        data: SliderThemeData(
          trackHeight: 8,
          thumbShape: SliderThumbImage(
              image: customImage,
              thumbRadius: 0,
              max: widget.maxDistance.toInt()),
          overlayColor: kSecondaryColorLight.withAlpha(32),
          overlayShape: RoundSliderOverlayShape(overlayRadius: 28.0),
          activeTrackColor: kSecondaryColorDark,
          inactiveTrackColor: kSecondaryColorLight.withOpacity(0.5),
          valueIndicatorShape: PaddleSliderValueIndicatorShape(),
          valueIndicatorTextStyle: TextStyle(
            color: Colors.white,
          ),
          valueIndicatorColor: Colors.white,
        ),
        child: Stack(
          children: <Widget>[
            Slider(
              label: distance.abs().toString(),
              value: distance,
              min: widget.minDistance,
              max: widget.maxDistance,
              onChanged: (value) {
                setState(() {
                  distance = value;
                });
              },
            ),
            Container(
              margin: EdgeInsets.only(left: 25, right: 25, top: 40),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Text(widget.minDistance.toInt().toString()),
                  Text(widget.maxDistance.toInt().toString())
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class SliderThumbImage extends SliderComponentShape {
  final ui.Image image;
  final double thumbRadius;
  final int max;
  SliderThumbImage({this.image, this.thumbRadius, this.max});

  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
    return Size.fromRadius(thumbRadius);
  }

  @override
  void paint(PaintingContext context, Offset center,
      {Animation<double> activationAnimation,
      Animation<double> enableAnimation,
      bool isDiscrete,
      TextPainter labelPainter,
      RenderBox parentBox,
      SliderThemeData sliderTheme,
      TextDirection textDirection,
      double value}) {
    final canvas = context.canvas;
    final imageWidth = image?.width ?? 10;
    final imageHeight = image?.height ?? 10;

    Offset imageOffset = Offset(
      center.dx - (imageWidth / 2),
      center.dy - (imageHeight / 0.935),
    );

    Paint paint = Paint()..filterQuality = FilterQuality.high;

    if (image != null) {
      canvas.drawImage(image, imageOffset, paint);
    }

    TextSpan span = new TextSpan(
        style: new TextStyle(
            fontSize: imageHeight * .3,
            fontWeight: FontWeight.w700,
            color: sliderTheme.valueIndicatorColor,
            height: 0.9),
        text: '${getValue(value)}');
    TextPainter tp = new TextPainter(
        text: span,
        textAlign: TextAlign.left,
        textDirection: TextDirection.ltr);
    tp.layout();
    Offset textCenter = Offset(
      center.dx - (tp.width / 2),
      center.dy - (tp.height / 0.32),
    );
    tp.paint(canvas, textCenter);
  }

  String getValue(double value) {
    return ((max * value).round()).toString();
  }
}
like image 138
zavora Avatar answered Oct 01 '22 15:10

zavora