After the release of Women’s Federation 4 last year, Google quickly rolled out an Easter egg to pay tribute to Thanos, the director of the family planning office of the Women’s Federation.

Due to the outbreak of COVID-19 abroad, the country has restricted most dangerous passages in order to ensure our safety, so I risked my life to move this Easter egg for you.See this cool egg, I can not help but a tight hair follicle!

In fact, this egg has long been played by everyone bad, look at the realization of various gods, the heart also have ideas. Let’s start with Flutter to achieve this effect.

Implementation approach

The egg is essentially an animation, and to achieve an animation effect, the first thing to do is to dismantle it, and then enrich the elements in a simple effect.

Q: How many steps will it take thanos to realize his plan?

A: Three steps. 1. Put on gloves 2. Snap your fingers 3

Q: What are the steps needed to achieve this effect in Flutter?

Answer: Also three steps. 1. Visualize 2. Separate pixels 3

To visualize is to convert a Widget in a scope to an Image object, which can be interpreted as a screenshot.

Separating the pixels is the most critical step, but also the more complicated one.

To understand this step, you need to calm down, and LET me help you get this straight.

Silently answer the following questions:

  • How old are you?

  • How long have you been working?

  • How much money do you have in hand?

  • Where does your money go when you don’t have a girlfriend?

Yeah, years of savings, you hear it, and it’s gone. Game top-up? Eat and drink? Digital devices? Tipping anchor? The membership fee?

Do you get it? You get the idea!

Years of savings, turned into a brushstroke of expenditure, into a variety of consumption types, as the elapse of time slowly forgotten, forgotten.

The image generated in the first step is compared to years of savings, where every expenditure in life corresponds to every pixel of the image, and consumption types are overlapping blank transparent layers. You randomly assign each pixel of the image to one of these layers, and then you slowly pull them away in different directions, fade them out, and they’re gone. They’re gone.

Use a picture to reinforce your understanding:

Look at the animationAnimations of displacement, rotation, and fading are added to the action of pulling off layers.

Begin to build

graphical

Flutter provides a component called the RepaintBoundary, which generates a UI.image object from the wrapped child screenshot using the toImage() method. But this Image object can’t get a pixel, so we’ll convert it to image.image.

Import 'package:image/image.dart' as image; Future< image.image > _getImageFromWidget() async {// _globalKey is the key of the Widget that needs to be visualized RenderRepaintBoundary boundary = _globalKey.currentContext.findRenderObject(); // ui.Image => image.Image var img = await boundary.toImage(); var byteData = await img.toByteData(format: ImageByteFormat.png); var pngBytes = byteData.buffer.asUint8List(); return image.decodeImage(pngBytes); }Copy the code

The separation of pixels

First we define a few of the most important parameters and initialization operations

Class Sandable extends StatefulWidget {class Sandable extends StatefulWidget {final Widget child; Final Duration Duration; Final int numberOfLayers; final int numberOfLayers; final int numberOfLayers; Sandable( {Key key, @required this.child, this.duration = const Duration(seconds: 3), this.numberOfLayers = 10}) : super(key: key); @override _SandableState createState() => _SandableState(); } class _SandableState extends State<Sandable> with TickerProviderStateMixin _mainController; // key of child GlobalKey _globalKey = GlobalKey(); List<Widget> layers = []; @override void initState() { super.initState(); _mainController = AnimationController(vsync: this, duration: widget.duration); } @override void dispose() { _mainController.dispose(); super.dispose(); }... }Copy the code

The layout in the Build method is very simple, requiring only a Stack layout with two parts: Child and layers

@override Widget build(BuildContext context) { return Stack( children: <Widget>[...layers, // sand layer // clickable child wrapped with RepaintBoundary to screenshot GestureDetector(onTap: () {blow ();}, / / when the animation begins Ontology hidden child: _mainController isAnimating? Container () : RepaintBoundary (key: _globalKey, child: widget.child, ), ) ], ); }Copy the code

The Blow method is the core method. No nonsense, directly on the code:

Future<void> blow() async {image.image fullImage = await _getImageFromWidget(); Int width = fullimage.width; int height = fullImage.height; List<image.Image> blankLayers = List. Generate (widget.numberOfLayers, (I) => image. height)); // Place the pixels of the original image in the layer separatePixels(blankLayers, fullImage, width, height); Map ((layer) => imageToWidget(layer)).tolist (); // Convert the layer to Widget layers = blankLayers.map((layer) => imageToWidget(layer)).tolist (); // Refresh the page setState(() {}); // Start animation _mainController.forward(); } void separatePixels(List<image.Image> blankLayers, image.Image fullImage, int width, Int height) {// For (int x = 0; x < width; x++) { for (int y = 0; y < height; Y ++) {// Get the current pixel point int pixel = fullimage.getPixel (x, y); If (0 == pixel) continue; // If (0 == pixel) continue; Int index = Random().nextint (widget.numberoflayers); // Place the pixel on layer blankLayers[index]. SetPixel (x, y, pixel); }}}Copy the code

It’s not complicated!

Look at the animation

Animation is the three axe: AnimationController+ animation process Curve+ interpolation Tween.

In this effect, the layer has a random shift animation and fade animation, and of course you can add a little rotation, but I’m lazy

// Convert image to Uint8List data = Uint8List Uint8List.fromList(image.encodePng(png)); CurvedAnimation animation = CurvedAnimation(parent: _mainController, curve: Interval(0, 1, curve: curve) Curves.easeOut)); Animation<Offset> offsetAnimation = Tween<Offset>(begin: Offset. Zero, // Base Offset + random Offset end: Offset (50, 20) + Offset (30, 30) scale ((Random () nextDouble () 0.5) * 2, (Random () nextDouble () 0.5) * 2), ).animate(animation); return AnimatedBuilder( animation: _mainController, child: Image.memory(data), builder: (context, child) {return transform. translate(offset: offsetanimation. value, // Fade animation child: Opacity(Opacity: cos(animation.value * pi / 2), // 1 => 0 child: child, ), ); }); }Copy the code

And then, and then there was no…

run

Write a demo and try it out! It’s very simple to use, just wrap the controls that need to be destroyed.

Sandable( duration: ... , numOfLayers: ... , child: ... .)Copy the code

As you can see, there is still a lot to improve. Things like flicker, custom displacement, tapering from left to right, callbacks after animation, etc.

conclusion

In general, this effect is relatively easy to achieve in Flutter, with clear thinking and without complex calculations.

Lose a hair, master this skill, it is sweet?