79243038

Date: 2024-12-02 05:41:18
Score: 2
Natty:
Report link

I'm able to achieve the curve effect, thanks to @pskink suggestion. The only thing I did differently was using quadratic Bezier curve formula instead of using a circle formula.

enum ShiftMode {
 forward,
 backward,
}


class BezierCurveItems extends StatefulWidget {
 const BezierCurveItems({super.key});


 @override
 State<BezierCurveItems> createState() => _BezierCurveItemsState();
}


class _BezierCurveItemsState extends State<BezierCurveItems>
   with SingleTickerProviderStateMixin {
 late double _width;
 late double _height;
 late int _visibleItemCount;
 ShiftMode? _shiftMode;
 late double _itemWidth;


 late AnimationController _controller;
 late Animation<double> _animation;
 late Tween<double> _tween;


 List<Widget> _items = [
   Icon(Icons.ac_unit),
   Icon(Icons.access_time),
   Icon(Icons.account_box_sharp),
   Icon(Icons.account_balance_wallet),
   Icon(Icons.account_tree),
 ];


 @override
 void initState() {
   _width = 360.0;
   _height = 80.0;
   _visibleItemCount = 5;
   _itemWidth = _width / _visibleItemCount;


   _controller = AnimationController(
     vsync: this,
     duration: const Duration(milliseconds: 500),
   );


   _tween = Tween<double>(begin: 0.0, end: 1.0);


   _animation = _tween.animate(
     CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
   );


   super.initState();
 }


 _shiftRightForward() {
   _shiftMode = ShiftMode.forward;
   _controller.reset();
   _controller.forward();
 }


 _shiftLeftForward() {
   _shiftMode = ShiftMode.backward;
   _controller.reset();
   _controller.forward();
 }


 double _getX(int i, double av) {
   // Calculate x position for the item
   double xPos = (i * (_width / (_visibleItemCount)));


   if (_shiftMode == ShiftMode.forward) {
     double xPosNext = ((i + 1) * (_width / (_visibleItemCount)));
     double interpolateX = lerpDouble(xPos, xPosNext, av)!;
     return interpolateX;
   } else if (_shiftMode == ShiftMode.backward) {
     double xPosPrev = ((i - 1) * (_width / (_visibleItemCount)));
     double interpolateX = lerpDouble(xPos, xPosPrev, av)!;
     return interpolateX;
   } else {
     return xPos;
   }
 }


 double _getY(int i, double av) {
   double yPos = _calculateBezierY(i);


   if (_shiftMode == ShiftMode.forward) {
     double yPosNext = _calculateBezierY(i + 1);
     return lerpDouble(yPos, yPosNext, av)!;
   } else if (_shiftMode == ShiftMode.backward) {
     double yPosPrev = _calculateBezierY(i - 1);
     return lerpDouble(yPos, yPosPrev, av)!;
   } else {
     return yPos;
   }
 }


 double _calculateBezierY(int i) {
   // Bezier curve control points
   Offset p0 = Offset(0, _height / 2);
   Offset p1 = Offset(_width / 2, 0); // Control point
   Offset p2 = Offset(_width, _height / 2);


   double t = i / (_visibleItemCount - 1);


   return (1 - t) * (1 - t) * p0.dy + 2 * (1 - t) * t * p1.dy + t * t * p2.dy;
 }


 @override
 Widget build(BuildContext context) {
   return Scaffold(
     floatingActionButton: Row(
       mainAxisAlignment: MainAxisAlignment.center,
       children: [
         FloatingActionButton(
           onPressed: () => _shiftLeftForward(),
           child: Icon(Icons.navigate_before),
         ),
         FloatingActionButton(
           onPressed: () async {
             _shiftRightForward();
             await Future.delayed(const Duration(seconds: 1));
             _shiftRightForward();
           },
           child: Icon(Icons.navigate_next),
         )
       ],
     ),
     appBar: AppBar(title: Text("Bezier Curve Items")),
     body: Container(
       width: 360.0,
       height: 80.0,
       color: Colors.grey.shade400,
       child: AnimatedBuilder(
         animation: _animation,
         builder: (BuildContext context, Widget? child) {
           return Stack(
             children: [
               for (int i = 0; i < _visibleItemCount; i++)
                 Transform.translate(
                   offset: Offset(
                     _getX(i, _animation.value),
                     _getY(i, _animation.value),
                   ),
                   child: Container(
                     decoration: BoxDecoration(
                       border: Border.all(color: Colors.black54),
                     ),
                     alignment: Alignment.center,
                     width: _itemWidth,
                     child: _items[i],
                   ),
                 ),
               if (_shiftMode == ShiftMode.forward)
                 Transform.translate(
                   offset: Offset(
                     _getX(-1, _animation.value),
                     _getY(-1, _animation.value),
                   ),
                   child: Container(
                     decoration: BoxDecoration(
                       border: Border.all(color: Colors.black54),
                     ),
                     alignment: Alignment.center,
                     width: _itemWidth,
                     child: _items[0],
                   ),
                 ),
               if (_shiftMode == ShiftMode.backward)
                 Transform.translate(
                   offset: Offset(
                     _getX(_visibleItemCount, _animation.value),
                     _getY(_visibleItemCount, _animation.value),
                   ),
                   child: Container(
                     decoration: BoxDecoration(
                       border: Border.all(color: Colors.black54),
                     ),
                     alignment: Alignment.center,
                     width: _itemWidth,
                     child: _items[0],
                   ),
                 ),
             ],
           );
         },
       ),
     ),
   );
 }
}

enter image description here

Reasons:
  • Blacklisted phrase (0.5): thanks
  • Probably link only (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • User mentioned (1): @pskink
  • Self-answer (0.5):
  • Low reputation (0.5):
Posted by: Roy Krueger