import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'dart:async'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp(home: Scaffold(body: App())); } } class App extends StatefulWidget { @override AppState createState() => AppState(); } class AppState extends State with SingleTickerProviderStateMixin { AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 3), vsync: this, ); } @override void dispose() { _controller?.dispose(); super.dispose(); } Future _startAnim() async { try { await _controller.forward().orCancel; await _controller.reverse().orCancel; } on TickerCanceled { print('welp we fucked'); } } @override Widget build(BuildContext context) { return Material( child: GestureDetector( onTap: () { _startAnim(); }, child: Stack( fit: StackFit.expand, children: [ Image.network( 'https://wallpaperplay.com/walls/full/e/5/3/13586.jpg', fit: BoxFit.cover, ), ColorFiltered( colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.8), BlendMode.srcOut), // This one will create the magic child: Stack( fit: StackFit.expand, children: [ Container( decoration: BoxDecoration( color: Colors.black, backgroundBlendMode: BlendMode .dstOut), // This one will handle background + difference out ), MovingHole(controller: _controller) ], ), ), ], ), )); } } class MovingHole extends StatelessWidget { MovingHole({Key key, this.controller}) : width = Tween(begin: 50.0, end: 150.0).animate(CurvedAnimation( parent: controller, curve: Interval(0.0, 1, curve: Curves.fastOutSlowIn))), height = Tween(begin: 50.0, end: 150.0).animate(CurvedAnimation( parent: controller, curve: Interval(0.0, 1, curve: Curves.fastOutSlowIn))), radius = BorderRadiusTween( begin: BorderRadius.circular(0.0), end: BorderRadius.circular(100.0)) .animate(CurvedAnimation( parent: controller, curve: Interval( 0.0, 1, curve: Curves.ease, ))), alignment = AlignmentTween( begin: Alignment.bottomCenter, end: Alignment.topCenter) .animate(CurvedAnimation( parent: controller, curve: Interval(0.0, 1, curve: Curves.ease))), super(key: key); Animation controller; Animation width; Animation height; Animation radius; Animation alignment; Widget build(BuildContext context) { return AnimatedBuilder( animation: controller, builder: (BuildContext context, Widget child) { return Container( alignment: alignment.value, child: Container( width: width.value, height: height.value, decoration: BoxDecoration( color: Colors.red, borderRadius: radius.value), )); }); } }