• Make shimmer effect in Flutter: Learn Flutter from UI challenges
  • Author: Hung HD
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: geniusq1981

Learn Flutter by challenging UI creation

introduce

I’m an avid mobile developer, working on both Android and iOS. In the past I didn’t believe in any cross-platform frameworks (Ionic, Xamarin, ReactNative), but now I want to tell the story of my encounter with Flutter, a cross-platform framework.

inspiration

As a native app developer, I am acutely aware of how painful UI customization can be, and how it can be even worse with cross-platform frameworks. But the advent of Flutter gave me hope that this pain could be ameliorate.

Flutter builds all UI elements (called widgets) from scratch. No native views are encapsulated and no Web-based UI elements are used. Just like the way the game frame constructs the game world in the game (characters, enemies, palaces…) As such, Flutter draws its OWN UI based on the Skia graphics rendering engine. This really makes sense because you have complete control over what you draw on the screen. Does that make you think about something? To me, this sounds like an indication that I can do UI customization more easily. I tried to challenge some UI effect implementations to prove this.

One challenge THAT comes to mind is the shimmer effect. This is a very common effect, and if you’re not familiar with the name, consider the “slide to unlock” animation that appears when you wake up your phone.

How to do

The basic idea is simple. The animation effect consists of a gradient moving from left to right.

The point is THAT I don’t want to do this effect just for text content. This effect is very popular as a loading animation in modern mobile applications.

The first initial idea was to draw an opaque gradient area at the top of the content layout. While this can be done, it’s not a good way to do it. We don’t want the animation to mess up our entire white background. Effects need to apply only to a given content layout.

Now it is time to refer to the Flutter documentation and sample code to see how this effect can be achieved.

After research I found a group called SingleChildRenderObjectWidget base class, the base class to show a Canvas object. Canvas is an object that is responsible for drawing content on the screen, and it has an interesting method called saveLayer that is used to “save a copy of the current transform and fragment on the save stack, and then create a new group for subsequent calls” (from the official documentation). This is exactly the feature I need, and it allows me to shimmer on certain content layouts.

implementation

There is a good little exercise to follow in Flutter. A widget usually contains a parameter named child or children, which helps us apply transformations to descendant widgets. Our Shimmer Widget also has a child, which lets us create any layout we want and then pass it as a Shimmer child, and the Shimmer widget in turn only works on that child.

import 'package:flutter/material.dart';

class Shimmer extends StatefulWidget {
  final Widget child;
  final Duration period;
  final Gradient gradient;
  
  Shimmer({Key key, this.child, this.period, this.gradient}): super(key: key);
  
  @override
  _ShimmerState createState() => _ShimmerState();
}

class _ShimmerState extends State<Shimmer> {
  @override
  Widget build(BuildContext context) {
    return_Shimmer(); }}Copy the code

_Shimmer is the inner class responsible for effect painting. It comes from SingleChildRenderObjectWidget extension and wrote a paint draw method to fulfill. We use the Canvas object’s saveLayer and paintChild methods to capture our Child as a layer and paint a gradient effect on it (with a bit of BlendMode magic).

import 'package:flutter/rendering.dart';

class _Shimmer extends SingleChildRenderObjectWidget {
  final Gradient gradient;

  _Shimmer({Widget child, this.gradient})
      : super(child: child);

  @override
  _ShimmerFilter createRenderObject(BuildContext context) {
    return_ShimmerFilter(gradient); } } class _ShimmerFilter extends RenderProxyBox { final _clearPaint = Paint(); final Paint _gradientPaint; final Gradient _gradient; _ShimmerFilter(this._gradient) : _gradientPaint = Paint().. blendMode = BlendMode.srcIn; @override bool get alwaysNeedsCompositing => child ! = null; @override void paint(PaintingContext context, Offset offset) {if(child ! = null) { assert(needsCompositing); final rect = offset & child.size; _gradientPaint.shader = _gradient.createShader(rect); context.canvas.saveLayer(rect, _clearPaint); context.paintChild(child, offset); context.canvas.drawRect(rect, _gradientPaint); context.canvas.restore(); }}}Copy the code

All that’s left is to add a kinetic effect to get our effect moving. Nothing special here, we will create a dynamic effect to move the Canvas from left to right before drawing the gradient. This will give the effect of gradient movement.

We create a new AnimationController for the action effects in _ShimmerState. Our _Shimmer and _ShimmerFilter classes also need a new variable (called Percent) to store the progress results of the animation execution, And call markNeedsPaint each time the AnimationController emits a new value (this causes the widget to be redrawn). The Canvas displacement can be calculated according to the value of percent.

class _ShimmerState extends State<Shimmer> with TickerProviderStateMixin {
  AnimationController controller;

  @override
  void initState() { super.initState(); controller = AnimationController(vsync: this, duration: widget.period) .. addListener(() {setState(() {});
      })
      ..addStatusListener((status) {
        if(status == AnimationStatus.completed) { controller.repeat(); }}); controller.forward(); } @override Widget build(BuildContext context) {return _Shimmer(
      child: widget.child,
      gradient: widget.gradient,
      percent: controller.value,
    );
  }

  @override
  void dispose() { controller.dispose(); super.dispose(); }}Copy the code

flutter_shimmer3_1.dart

class _Shimmer extends SingleChildRenderObjectWidget {
  ...
  final double percent;

  _Shimmer({Widget child, this.gradient, this.percent})
      : super(child: child);

  @override
  _ShimmerFilter createRenderObject(BuildContext context) {
    return_ShimmerFilter(percent, gradient); } @override void updateRenderObject(BuildContext context, _ShimmerFilter shimmer) { shimmer.percent = percent; } } class _ShimmerFilter extends RenderProxyBox { ... double _percent; _ShimmerFilter(this._percent, this._gradient) : _gradientPaint = Paint().. blendMode = BlendMode.srcIn; .set percent(double newValue) {
    if(newValue ! = _percent) { _percent = newValue; markNeedsPaint(); } } @override void paint(PaintingContext context, Offset offset) {if(child ! = null) { assert(needsCompositing); final width = child.size.width; final height = child.size.height; Rect rect; double dx, dy; dx = _offset(-width, width, _percent); Dy = 0.0; rect = Rect.fromLTWH(offset.dx - width, offset.dy, 3 * width, height); _gradientPaint.shader = _gradient.createShader(rect); context.canvas.saveLayer(offset & child.size, _clearPaint); context.paintChild(child, offset); context.canvas.translate(dx, dy); context.canvas.drawRect(rect, _gradientPaint); context.canvas.restore(); } } double _offset(double start, double end, double percent) {returnstart + (end - start) * percent; }}Copy the code

flutter_shimmer3_2.dart

Not bad. We just implemented the shimmer effect in about 100 lines of code. This is the beauty of Flutter.

This is just the beginning. Next I will use Flutter to challenge more complex UI effects. I’ll share my results in the next article. Thanks for reading!

Note: I have published my code as a package called Shimmer.

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.