Search code examples
flutterdartanimationcube

Flutter 3D Cube Effect


I'm asking you how I can create this effect in a Flutter?

enter image description here


Solution

  • Probably you know that flutter doesn't have 3d engine. But you don't need it, you can use perspective transformations.

    I've made small example for you.

    enter image description here

    import 'dart:math';
    
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter 3D Cube Effect',
          theme: ThemeData(
            primarySwatch: Colors.green,
          ),
          home: Scaffold(
              body: Container(
            child: SafeArea(
              top: true,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Text('Flutter 3D Cube Effect'),
                  Expanded(
                    child: Pseudo3dSlider(),
                  ),
                ],
              ),
            ),
          )),
        );
      }
    }
    
    class Pseudo3dSlider extends StatefulWidget {
      @override
      _Pseudo3dSliderState createState() => _Pseudo3dSliderState();
    }
    
    class _Pseudo3dSliderState extends State<Pseudo3dSlider> {
      Map<String, Offset> offsets = {
        'start': Offset(70, 100),
        'finish': Offset(200, 100),
        'center': Offset(100, 200),
      };
    
      double originX = 0;
      double x = 0;
    
      void onDragStart(double originX) => setState(() {
            this.originX = originX;
          });
    
      void onDragUpdate(double x) => setState(() {
            this.x = originX - x;
          });
    
      double get turnRatio {
        const step = -150.0;
        var k = x / step;
        k = k > 1 ? 1 : (k < 0 ? 0 : k);
        return 1 - k;
      }
    
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          behavior: HitTestBehavior.opaque,
          onPanStart: (details) => onDragUpdate(details.globalPosition.dx),
          onPanUpdate: (details) => onDragUpdate(details.globalPosition.dx),
          child: Slider(
            children: [
              _Side(
                color: Colors.blueAccent,
                number: 1,
              ),
              _Side(
                color: Colors.redAccent.shade200,
                number: 2,
              ),
            ],
            k: turnRatio,
          ),
        );
      }
    }
    
    class _Side extends StatelessWidget {
      const _Side({Key key, this.color, this.number}) : super(key: key);
    
      final Color color;
      final int number;
    
      @override
      Widget build(BuildContext context) {
        return Container(
          width: 150,
          height: 150,
          color: color,
          child: Center(
            child: Text(
              number.toString(),
              style: TextStyle(fontSize: 14),
            ),
          ),
        );
      }
    }
    
    class Slider extends StatelessWidget {
      Slider({
        Key key,
        @required this.children,
        @required this.k,
      }) : super(key: key) {
        assert(children.length == 2, 'wronge nubmer of children');
      }
    
      final List<Widget> children;
      final double k;
    
      @override
      Widget build(BuildContext context) {
        var k1 = k;
        var k2 = 1 - k;
        print(k1);
        print(k2);
        return Row(
          children: <Widget>[
            Transform(
              transform: Matrix4.identity()
                ..setEntry(3, 2, 0.003)
                ..rotateY(pi / 2 * k1),
              alignment: FractionalOffset.centerRight,
              child: children[0],
            ),
            Transform(
              transform: Matrix4.identity()
                ..setEntry(3, 2, 0.003)
                ..rotateY(pi / 2 * -k2),
              alignment: FractionalOffset.centerLeft,
              child: children[1],
            )
          ],
        );
      }
    }