Search code examples
flutterdartcanvas

Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan


I'm new to flutter trying to implment gesture control on a canvas. I have created a half pie chart indicating the contributions of various sectors and have created a semicircle smaller than chart and have implemented a text inside it. My goal is to get to know when the semicircle inside the chart is tapped So I'm looking out for a solution. Its fine even with some other functionality wherein I could make a function call when someone touches the arc

import 'package:flutter/material.dart';
import 'dart:math';
import 'package:touchable/touchable.dart';

class canavChart extends StatefulWidget {
  Color? midcolor;
  PaintingStyle? style;
  Color? gapcolor;

  canavChart({this.midcolor, this.style, this.gapcolor}) {
    style = PaintingStyle.fill;
    midcolor = Colors.white;
    gapcolor = Colors.blue;
  }

  final List<dataItem> _dataset = [
    dataItem(25, 'Comedy', Colors.red),
    dataItem(55, "Action", Colors.blue),
    dataItem(75, 'Drama', Colors.pink),
    dataItem(85, 'Horror', Colors.green),
  ];

  @override
  // ignore: no_logic_in_create_state
  State<canavChart> createState() => _canavChartState(
      styling: style, midcoloring: midcolor, gapcoloring: gapcolor);
}

class _canavChartState extends State<canavChart> {
  Color? midcoloring;
  Color? gapcoloring;
  PaintingStyle? styling;

  _canavChartState(
      {required this.midcoloring,
      required this.styling,
      required this.gapcoloring});

  @override
  Widget build(BuildContext context) {
    double _deviceheight, _devicewidth;
    _deviceheight = MediaQuery.of(context).size.height;
    String? s;
    _devicewidth = MediaQuery.of(context).size.width;
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          "donut chart",
          style: TextStyle(fontSize: 25),
        ),
      ),
      body: Column(
        mainAxisSize: MainAxisSize.max,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          MaterialButton(
              color: Colors.blue,
              onPressed: () {
                setState(() {
                  if (midcoloring == Colors.white)
                    midcoloring = Colors.black;
                  else
                    midcoloring = Colors.white;
                });
              }),
          MaterialButton(
              color: Colors.yellow,
              onPressed: () {
                setState(() {
                  if (gapcoloring == Colors.white)
                    gapcoloring = Colors.black;
                  else
                    gapcoloring = Colors.white;
                });
              }),
          MaterialButton(
            child: Text('Show Top Snackbar'),
            color: Colors.green,
            onPressed: () {
              returnSnackBar("Pressed a button");
            },
          ),
          Container(
            height: 0.45 * _devicewidth,
            color: Colors.white,
            width: _devicewidth,
            // color: Colors.white,
            child: CanvasTouchDetector(builder: (context) {
              return CustomPaint(
                child: Container(),
                painter: DonutChartPainter(context:context,
                    dataset: widget._dataset,
                    midcolor: midcoloring,
                    style: styling,
                    gapcolor: gapcoloring),
              );
            }),
          ),
        ],
      ),
    );
  }

  ScaffoldFeatureController<SnackBar, SnackBarClosedReason> returnSnackBar(
      String s) {
    return ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Text(s!),
      behavior: SnackBarBehavior.floating,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(24),
      ),
      margin: EdgeInsets.only(
          bottom: MediaQuery.of(context).size.height - 100,
          right: 20,
          left: 20),
    ));
  }
}

const textFieldTextBigStyle =
    TextStyle(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 30.0);

class DonutChartPainter extends CustomPainter {
  final List<dataItem> dataset;
  Color? midcolor, gapcolor;
  final BuildContext context;
  PaintingStyle? style;
  Paint? linepaint;
  Paint? midpaint;

  DonutChartPainter(
      {required this.dataset,
      required this.midcolor,
        required this.context,
      this.style,
      required this.gapcolor}) {
    midpaint = Paint()
      ..color = midcolor!
      ..style = style!;
    linepaint = Paint()
      ..color = gapcolor!
      ..strokeWidth = 2.0
      ..style = PaintingStyle.fill;
  } ScaffoldFeatureController<SnackBar, SnackBarClosedReason> returnSnackBar(
      String s) {
    return ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Text(s!),
      behavior: SnackBarBehavior.floating,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(24),
      ),
      margin: EdgeInsets.only(
          bottom: MediaQuery.of(context).size.height - 100,
          right: 20,
          left: 20),
    ));
  }

  @override
  void paint(Canvas canvas, Size size) {
    var myCanvas = TouchyCanvas(context, canvas);
    final c = Offset(size.width / 2.0, size.height);
    final textpositon = Offset(size.width / 2.0, size.height / 2.0);
    final radius = size.width * 0.9;
    var startAngle = 0 * pi / 180.0;
    int count = dataset.length, index = 0;
    final rect = Rect.fromCenter(center: c, width: radius, height: radius);
    final smallrect =
        Rect.fromCenter(center: c, width: radius * 0.9, height: radius * 0.9);
    double sum = 0;
    int len = dataset.length;
    double gap = 5 * pi / 180.0;

    for (var i in dataset) {
      sum += i.value;
    }
    double leftoutangle = pi - gap * (len - 1);
    for (var i in dataset) {
      i.value = i.value / sum * leftoutangle;
    }
    dataset.forEach((element) {

      double sweepAngle = drawSectors(element, myCanvas, rect, startAngle);
      startAngle = startAngle + sweepAngle;
      if (index < count - 1) {
        myCanvas.drawArc(rect, startAngle, -5 * pi / 180.0, true, linepaint!);
      }
      startAngle -= 5 * pi / 180;
      index++;
    });

    myCanvas.drawArc(smallrect, 0.0, -pi, true, midpaint!,onTapDown: (tapdetail){
      returnSnackBar("tapped");
    });

    drawTextCentered(canvas, textpositon, "favourite \n Movie ",
        textFieldTextBigStyle, radius * 0.6);
  }

  double drawSectors(dataItem di, TouchyCanvas myCanvas, Rect rect, double startAngle) {
    final sweepAngle = -di.value;
    final paint = Paint()
      ..style = PaintingStyle.fill
      ..color = di.color;
    myCanvas.drawArc(rect, startAngle, sweepAngle, true, paint);
    return sweepAngle;
  }

  TextPainter measureText(
      String s, TextStyle style, double maxWidth, TextAlign align) {
    final span = TextSpan(text: s, style: style);
    final tp = TextPainter(
        text: span, textAlign: align, textDirection: TextDirection.ltr);
    tp.layout(minWidth: 0, maxWidth: maxWidth);
    return tp;
  }

  Size drawTextCentered(Canvas canvas, Offset position, String text,
      TextStyle style, double maxWidth) {
    final tp = measureText(text, style, maxWidth, TextAlign.center);
    final pos = position + Offset(-tp.width / 2.0, -tp.height / 2.0);
    tp.paint(canvas, pos);
    return tp.size;
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

class dataItem {
  double value;
  final String label;
  final Color color;

  dataItem(this.value, this.label, this.color);
}

In the line 192, i'm trying to test the command of what happens when the arc is touched. But its saying mae to use scale gesture recognizer. I dont understand the issue where did I use both scalea and pan gesture


Solution

  • It looks like you're trying to implement gesture control on a canvas in Flutter, specifically detecting when a user taps on a semicircle within a chart. One solution to achieve this would be to use the GestureDetector widget provided by Flutter.

    You can wrap the semicircle you want to detect the tap on with a GestureDetector widget, and provide a onTap callback function that gets called when the semicircle is tapped.

    Here is an example:

    GestureDetector(
        onTap: () {
            // callback function when semicircle is tapped
        },
        child: CustomPaint(
            // your semicircle painter
        ),
    )
    

    It's also worth noting that you're already importing 'package:touchable/touchable.dart' which provides a CanvasTouchDetector. It can be used instead of the GestureDetector.

    CanvasTouchDetector(
        onLongPress: () {
            // callback function when semicircle is tapped
        },
        child: CustomPaint(
            // your semicircle painter
        ),
    ),
    

    You can also use the InkWell widget to achieve the same, which provides more features like ripple effect.

    InkWell(
        onTap: () {
            // callback function when semicircle is tapped
        },
        child: CustomPaint(
            // your semicircle painter
        ),
    ),
    

    It is also worth noting that you might need to adjust the position of the semicircle inside the chart according to the position of the gesture detector or InkWell.

    You can also use the Listenable class to update the state of your chart and call setState function when the semicircle is tapped.