emvaized
emvaized

Reputation: 1125

3D Carousel Animation in Flutter

Example gif

How to create similar effect using Flutter?

Upvotes: 3

Views: 4531

Answers (2)

user18423210
user18423210

Reputation:

Well, I use github for some help with my CSS animations. I also am using it for my website's loading screen (this is just an example, ok?):

<!DOCTYPE html>
<html lang="en">
<style>
* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
  }
  
  body {
    height: 100vh;
    display: flex;
    background: #ffffff;
  }
  
  .circular-slider {
    width: 250px;
    height: 150px;
    margin: auto;
    perspective: 1000px;
  }
  
  .slider-content {
    width: 100%;
    height: 100%;
    position: relative;
    transform-style: preserve-3d;
    animation: rotate 20s infinite;
  }
  
  .slider-item {
    width: 220px;
    height: 120px;
    position: absolute;
    background: rgba(255,255,255,.5);
    -webkit-box-reflect: below 15px
    -webkit-gradient(linear, left top, left bottom, from(transparent),
                     color-stop(0.5, transparent), to(rgba(255,255,255,.5)));
  }
  
  .slider-item img {
    width: 100%;
    height: 100%;
  }
  
  .slider-item:nth-child(1) { transform: rotateY(0deg) translateZ(216px); }
  .slider-item:nth-child(2) { transform: rotateY(60deg) translateZ(216px); }
  .slider-item:nth-child(3) { transform: rotateY(120deg) translateZ(216px); }
  .slider-item:nth-child(4) { transform: rotateY(180deg) translateZ(216px); }
  .slider-item:nth-child(5) { transform: rotateY(240deg) translateZ(216px); }
  .slider-item:nth-child(6) { transform: rotateY(300deg) translateZ(216px); }
  
  @keyframes rotate {
    0%     { transform: translateZ(-216px) rotateY(0deg); }
    16.67% { transform: translateZ(-216px) rotateY(-60deg); }
    33.33% { transform: translateZ(-216px) rotateY(-120deg); }
    50%    { transform: translateZ(-216px) rotateY(-180deg); }
    66.67% { transform: translateZ(-216px) rotateY(-240deg); }
    83.34% { transform: translateZ(-216px) rotateY(-300deg); }
    100%   { transform: translateZ(-216px) rotateY(-360deg); }
  }
</style>
<head>
  <meta charset="UTF-8">
  <title>Testing CSS capabilities</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="circular-slider">
    <div class="slider-content">
      <div class="slider-item">
        <img src="https://peakvisor.com/img/news/Utah-mountains.jpg">
      </div>
      <div class="slider-item">
        <img src="https://passionpassport-1.s3.amazonaws.com/wp-content/uploads/2017/07/10002009/James-Udall-Utah-Notch-Lake-Uintas-Sunset.jpg">
      </div>
      <div class="slider-item">
        <img src="https://webneel.com/wallpaper/sites/default/files/images/08-2018/3-nature-wallpaper-mountain.jpg">
      </div>
      <div class="slider-item">
        <img src="https://www.teahub.io/photos/full/186-1868092_1080p-galaxy-wallpaper-hd.jpg">
      </div>
      <div class="slider-item">
        <img src="https://www.teahub.io/photos/full/13-137126_1920x1152-free-download-nature-hd-wallpapers-nature-nature.jpg">
      </div>
      <div class="slider-item">
        <img src="https://thumbs.gfycat.com/PlayfulSoreGroundbeetle-max-1mb.gif">
      </div>
    </div>
  </div>
</body>
</html>

Code from https://github.com/marina-ferreira/circular-slider

Upvotes: 3

roipeker
roipeker

Reputation: 1243

I made a quick demo for you, tweak the Matrix4 params. (blur/depth of field doesn't work well on browser).

import 'dart:math';
import 'dart:ui';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      darkTheme:
          ThemeData(platform: TargetPlatform.iOS, brightness: Brightness.dark),
      home: RotationScene(),
    );
  }
}

class RotationScene extends StatefulWidget {
  @override
  _RotationSceneState createState() => _RotationSceneState();
}

class _RotationSceneState extends State<RotationScene> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'carrousel',
          style: TextStyle(fontSize: 13),
        ),
        centerTitle: false,
        elevation: 12,
        backgroundColor: Colors.transparent,
      ),
      body: Container(
        decoration: BoxDecoration(
            gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [Color(0xff74ABE4), Color(0xffA892ED)],
          stops: [0, 1],
        )),
        child: Center(child: MyScener()),
      ),
    );
  }
}

class CardData {
  Color color;
  double x, y, z, angle;
  final int idx;
  double alpha = 0;

  Color get lightColor {
    var val = HSVColor.fromColor(color);
    return val.withSaturation(.5).withValue(.8).toColor();
  }

  CardData(this.idx) {
    color = Colors.primaries[idx % Colors.primaries.length];
    x = 0;
    y = 0;
    z = 0;
  }
}

class MyScener extends StatefulWidget {
  @override
  _MyScenerState createState() => _MyScenerState();
}

class _MyScenerState extends State<MyScener>
    with SingleTickerProviderStateMixin {
  AnimationController _animationController;

  List<CardData> cardData = [];
  int numItems = 9;
  double radio = 200.0;
  double radioStep;
  int centerIdx = 1;

  @override
  void initState() {
    cardData = List.generate(numItems, (index) => CardData(index)).toList();
    radioStep = (pi * 2) / numItems;

    _animationController =
        AnimationController(vsync: this, duration: Duration(seconds: 1));

    _animationController.addListener(() => setState(() {}));
    _animationController.addStatusListener((status) async {
      if (status == AnimationStatus.completed) {
        _animationController.value = 0;
        _animationController.animateTo(1);
        ++centerIdx;
      }
    });
    _animationController.forward();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    var ratio = _animationController.value;
    double animValue = centerIdx + ratio;
    // process positions.
    for (var i = 0; i < cardData.length; ++i) {
      var c = cardData[i];
      double ang = c.idx * radioStep + animValue;
      c.angle = ang + pi / 2;
      c.x = cos(ang) * radio;
//      c.y = sin(ang) * 10;
      c.z = sin(ang) * radio;
    }

    // sort in Z axis.
    cardData.sort((a, b) => a.z.compareTo(b.z));

    var list = cardData.map((vo) {
      var c = addCard(vo);
      var mt2 = Matrix4.identity();
      mt2.setEntry(3, 2, 0.001);
      mt2.translate(vo.x, vo.y, -vo.z);
      mt2.rotateY(vo.angle + pi);
      c = Transform(
        alignment: Alignment.center,
        origin: Offset(0.0, -0.0),
        transform: mt2,
        child: c,
      );

      // depth of field... doesnt work on web.
//      var blur = .4 + ((1 - vo.z / radio) / 2) * 2;
//      c = BackdropFilter(
//        filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur),
//        child: c,
//      );

      return c;
    }).toList();

    return Container(
      alignment: Alignment.center,
      child: Stack(
        alignment: Alignment.center,
        children: list,
      ),
    );
  }

  Widget addCard(CardData vo) {
    var alpha = ((1 - vo.z / radio) / 2) * .6;
    Widget c;
    c = Container(
      margin: EdgeInsets.all(12),
      width: 120,
      height: 80,
      alignment: Alignment.center,
      foregroundDecoration: BoxDecoration(
        borderRadius: BorderRadius.circular(12),
        color: Colors.black.withOpacity(alpha),
      ),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          stops: [0.1, .9],
          colors: [vo.lightColor, vo.color],
        ),
        borderRadius: BorderRadius.circular(12),
        boxShadow: [
          BoxShadow(
              color: Colors.black.withOpacity(.2 + alpha * .2),
              spreadRadius: 1,
              blurRadius: 12,
              offset: Offset(0, 2))
        ],
      ),
      child: Text('ITEM ${vo.idx}'),
    );
    return c;
  }
}

Upvotes: 13

Related Questions