F.NiX
F.NiX

Reputation: 1505

Flutter cursor wheel widget auto positioning with animation

I have a flutter code for cursor wheel widget.

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: RotateText(),
    );
  }
}

class RotateText extends StatefulWidget {
  RotateText({
    Key key
  }): super(key: key); // changed

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

class _RotateTextState extends State < RotateText > {
  double finalAngle = 0.0;
  double offsetAngle = 0.0;

  @override
  Widget build(BuildContext context) {
    return _defaultApp(context);
  }

  _defaultApp(BuildContext context) {
    var radius = 300.0;
    var left = MediaQuery.of(context).size.width / 2 - radius;
    var size = 1.6 * radius;
    var place = 60;
    var item = 100.0;
    var distance = 180;
    
    return Scaffold(
      appBar: AppBar(
        title: Text('Single finger Rotate text'), // changed
      ),
      body: new Stack(
        children: <Widget>[
          Positioned(
            left: left,
            bottom: place - radius,
            child: Container(
              width: 2 * radius,
              height: 2 * radius,
              decoration: new BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.grey,
              ),
              child: LayoutBuilder(
                builder: (context, constraints) {
                  return GestureDetector(
                    behavior: HitTestBehavior.translucent,
                    onPanStart: (details) {
                      Offset centerOfGestureDetector = Offset(constraints.maxWidth / 2, constraints.maxHeight / 2);
                      final touchPositionFromCenter = details.localPosition - centerOfGestureDetector;
                      offsetAngle = touchPositionFromCenter.direction - finalAngle;
                    },
                    onPanUpdate: (details) {
                      Offset centerOfGestureDetector = Offset(constraints.maxWidth / 2, constraints.maxHeight / 2);
                      final touchPositionFromCenter = details.localPosition - centerOfGestureDetector;
                      setState(() {
                        finalAngle = touchPositionFromCenter.direction - offsetAngle;
                      });
                    },
                    child: Transform.rotate(
                      angle: finalAngle,
                      child: Icon(
                        Icons.settings,
                        color: Colors.white,
                        size: size,
                      ),
                    ),
                  );
                },
              ),
            )
          ),
          Positioned(
            left: left + radius / 2 + item + distance * cos(finalAngle - pi / 2),
            bottom: place - item / 2 - distance * sin(finalAngle - pi / 2),
            child: Container(
              height: item,
              width: item,
              child: Image.network(
                'https://picsum.photos/250?image=9',
              ),
            )
          ),
        ]
      )
    );
  }
}

It works fine and spins by dragging but I doesn't stay and calibrate automatically on one menu item with animation.

How can I make it rotate (with finalAngle float number) to reposition at the center of the items when it's on the radial area range?

Thanks.

Upvotes: 2

Views: 755

Answers (1)

Taleb
Taleb

Reputation: 2249

You should use AnimationController to animate position of the items and you must set the correct position of menu item in the onPanEnd method, i completely review your code and finally fixed it as below:

import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: RotateText(),
    );
  }
}

class RotateText extends StatefulWidget {
  RotateText({Key key}) : super(key: key); // changed

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

class _RotateTextState extends State<RotateText>
    with SingleTickerProviderStateMixin {
  double finalAngle = 0.0;
  double offsetAngle = 0.0;

  Tween<double> _tween;
  Animation<double> _animation;
  AnimationController _animationController;

  @override
  void initState() {
    super.initState();
    _animationController =
        AnimationController(duration: Duration(milliseconds: 700), vsync: this);
    _tween = Tween(begin: 0.0, end: finalAngle);
    _animation = _tween.animate(CurvedAnimation(
        parent: _animationController, curve: Curves.easeOut))
      ..addListener(() {
        setState(() {});
      });
  }

  void updateFinalAngle(double angle, [bool withDuration = false]) {
    _tween.begin = _tween.end;
    _animationController.reset();
    _tween.end = angle;
    _animationController.forward();
  }

  @override
  Widget build(BuildContext context) {
    return _defaultApp(context);
  }

  _defaultApp(BuildContext context) {
    var radius = 300.0;
    var left = MediaQuery.of(context).size.width / 2 - radius;
    var size = 1.6 * radius;
    var place = 60;
    var item = 100.0;
    var distance = 180;

    return Scaffold(
        appBar: AppBar(
          title: Text('Single finger Rotate text'), // changed
        ),
        body: Stack(children: <Widget>[
          Positioned(
              left: left,
              bottom: place - radius,
              child: Container(
                width: 2 * radius,
                height: 2 * radius,
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: Colors.grey,
                ),
                child: LayoutBuilder(
                  builder: (context, constraints) {
                    return GestureDetector(
                      behavior: HitTestBehavior.translucent,
                      onPanStart: (details) {
                        Offset centerOfGestureDetector = Offset(
                            constraints.maxWidth / 2,
                            constraints.maxHeight / 2);
                        final touchPositionFromCenter =
                            details.localPosition - centerOfGestureDetector;
                        offsetAngle =
                            touchPositionFromCenter.direction - finalAngle;
                      },
                      onPanUpdate: (details) {
                        Offset centerOfGestureDetector = Offset(
                            constraints.maxWidth / 2,
                            constraints.maxHeight / 2);
                        final touchPositionFromCenter =
                            details.localPosition - centerOfGestureDetector;
                        setState(() {
                          finalAngle =
                              touchPositionFromCenter.direction - offsetAngle;
                          updateFinalAngle(finalAngle);
                        });
                      },
                      onPanEnd: (detail) {
                        double fixedAngle = _calcFinalAngle(finalAngle);
                        finalAngle = fixedAngle;
                        updateFinalAngle(finalAngle, true);
                      },
                      child: Transform.rotate(
                        angle: _animation.value,
                        child: Icon(
                          Icons.settings,
                          color: Colors.white,
                          size: size,
                        ),
                      ),
                    );
                  },
                ),
              )),
          Positioned(
              left: left +
                  radius / 2 +
                  item +
                  distance * cos(_animation.value - pi / 2),
              bottom:
                  place - item / 2 - distance * sin(_animation.value - pi / 2),
              child: Container(
                height: item,
                width: item,
                child: Image.network(
                  'https://picsum.photos/250?image=9',
                ),
              )),
        ]));
  }

  double _calcFinalAngle(double radianAngle) {
    int degreeAngle =
        double.parse((radianAngle * (180 / pi)).toStringAsFixed(2)).round();

    int criticalAngle = 60;
    int criticalOffset = 30;
    bool isNegative = false;
    if (degreeAngle < 0) isNegative = true;
    degreeAngle = degreeAngle.abs();
    int rem60 = degreeAngle % criticalAngle;

    if (rem60 >= criticalOffset) {
      degreeAngle += (60 - rem60);
    } else {
      degreeAngle -= rem60;
    }

    double resultAngleInRad = degreeAngle * (pi / 180);

    return isNegative ? -resultAngleInRad : resultAngleInRad;
  }
}

Upvotes: 1

Related Questions