Perspective on Flutter

The Transform in Flutter can achieve many cool animation effects. In this article, we will show how to use Transfrom to achieve 3D perspective rotation effects. The following example can be easily implemented with Flutter. However, it may be more difficult to achieve this effect with native components.

1. Use Transform to achieve 3D effect

Use the code generated by default to create the Flutter project as an example to show 3D perspective. The 3D effect is implemented using Transform first. The code is as follows:

// v1: move default app to separate function with fixed name
// Add transform widget, rotate and perspective
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Perspective',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key); // changed

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  Offset _offset = Offset(0.4, 0.7); // new

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    returnTransform( // Transform widget transform: Matrix4.identity() .. SetEntry (3, 2, 0.001) // perspective.. rotateX(_offset.dy) .. rotateY(_offset.dx), alignment: FractionalOffset.center, child: _defaultApp(context), ); } _defaultApp(BuildContext context) { // newreturn Scaffold(
      appBar: AppBar(
        title: Text('The Matrix 3D'), // changed
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment', child: Icon(Icons.add), ), ); }}Copy the code

Running the above code will give you a slightly rotated 3D effect.

For demonstration purposes, the default layout code is wrapped with the _defaultApp method, and then the 3D effect is implemented only through Transfrom.

2. Introduction to Transform Widget

In the above code, the perspective effect is implemented through Transfrom, which is implemented through matrix transformations performed by Matrix4.

Since modern smartphones all have GPU units for graphics computing, which are optimized for graphics computing and rendering, rendering even 3D graphics is very fast. So basically all the graphics you see on the phone are rendered in 3D, even 2D graphics.

By setting the transformation matrix, we can change the visual effect (even 3D effect) we see. Generally speaking, matrix transformations include: translation, rotation, scaling, and perspective. In the code above, we create a matrix from identity_matrix and apply it to Transform. It should be noted that matrix transformation does not meet the commutative law, so the position of parameters should be correct. After the matrix is passed in, the final matrix operation result will be transmitted to GPU, and then the image will be rendered.

Matrix computing is a very complex subject, and if you want to continue learning about it, please refer to other resources.

3, the realization of perspective effect

The above code implements the perspective effect, that is, the farther parts should look smaller. Therefore, the above parameters will be scaled by 0.001 based on distance.

So where does the 0.001 parameter come from? It’s arbitrary, you can make it bigger or smaller and see what it looks like, the bigger the number, the more it looks like we’re getting closer and closer to the object.

Flutter also provides a makePerspectiveMatrix method for perspective matrix transformation. However, this method requires extra parameters that are not needed. Therefore, the transformation can be done directly using matrix.

Also, the above code specifies the X-axis and the rotation of the axis by _offset.

4. Gesture interaction

Gesture interaction is implemented directly through the GestureDetector.

// v2: add Gesture detector
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Perspective',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key); // changed

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  Offset _offset = Offset.zero; // changed

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    returnTransform( // Transform widget transform: Matrix4.identity() .. SetEntry (3, 2, 0.001) // perspective.. RotateX (0.01 * _offset.dy) // changed.. RotateY (-0.01 * _offset. Dx), // Changed alignment: FractionalOffset. Center, child: GestureDetector(// new onPanUpdate: (details) =>setState(() => _offset += details.delta),
        onDoubleTap: () => setState(() => _offset = Offset.zero),
        child: _defaultApp(context),
      )
    );
  }

  _defaultApp(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('The Matrix 3D'), // changed
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment', child: Icon(Icons.add), ), ); }}Copy the code

There are only two types of gesture interaction above:

  • DoubleTap: Double click to reset
  • OnPanUpdate: Move your finger and rotate the image.

5, advanced actual combat – page turning effect

The next effect is a bit more complex, like a page-turning animation.

The preliminary design

When you first see this effect, you might think of it as a Stack, with each page divided into two sections, each of which can be rotated around the X-axis to see the next page.

So how do you do that in code? It can be done in two parts.

  • Divide a page into two parts
  • I’m going to rotate some of it around the X-axis.

So, what kind of Widget in Flutter is suitable for us to achieve this effect? ClipRect and Transform.

implementation

  • ClipRect The ClipRect component has one parameter, clipper, which defines the size and position of the clipped rectangular area, but the official documentation suggests another way to use ClipRect, combining it with Align.

Next, define a Widget to implement this functionality.

class FlipWidget extends StatelessWidget {
  Widget child;

  FlipWidget({Key key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    returnColumn( mainAxisSize: MainAxisSize.min, children: [ ClipRect( child: Align( alignment: Alignment. TopCenter, heightFactor: 0.5, child: child,)), Padding(Padding: EdgeInsets. Only (top: Bottom factor: 0.5),), (child: Align(Align: bottomCenter, heightFactor: 0.5, child: child,),]; }}Copy the code

The child argument can pass widgets of any type (text,image, etc.). Run the code above and you will see the following.

  • Achieve rollover effect

The Transform Widget component has a Transform parameter of type Matrix4, which determines what type of matrix transformation we will apply. Also, Matrix4 provides a constructor named rotationX(), which seems to be just what we need, so let’s try it out for the top half of the page.

@override
Widget build(BuildContext context) {
   returnColumn( mainAxisSize: MainAxisSize.min, children: [ Transform( transform: Matrix4.rotationX(pi / 4), alignment: BottomCenter, child: ClipRect(Child: Align(Alignment: Align. TopCenter, heightFactor: 0.5, child: child, )), ), ... ] ,); }Copy the code

Run the code above.

Obviously, this effect just shrinks the top half, not the desired effect. But if you specify additional parameters for Matrix4, set row to 3 and column to 2, test the effect.

Transform( transform: Matrix4.identity().. SetEntry (3, 2, 0.006).. rotateX(pi / 4), alignment: Alignment.bottomCenter, child: ClipRect( child: Align( alignment: Alignment. TopCenter, heightFactor: 0.5, Child: child,);Copy the code

It looks like this is the effect we need, with an argument, 0.006, where does this come from? It is to try to come out actually, choose the numerical value that oneself feels good to go 😂.

The next step is to animate the flip. But this one might be a little bit more complicated. First, each page has to be understood as having two sides (heads and tails), but this might not be easy to achieve in code because the images we see on our phones have only one side at any one time.

Let’s say we flip up, so our animation can be divided into two parts. The first part is when we flip the bottom half of the animation half way up. The effect of this process is that the current flipped page gradually disappears, and the next page gradually shows up. The second part is to continue flipping the current page up. The effect of this process is that the current page will gradually appear, and the current page in the upper half will gradually disappear.

This effect implementation, code is very much, more detailed code please refer to:

https://gist.github.com/hnvn/f1094fb4f6902078516cba78de9c868e
Copy the code

Final effect:


github


The last

Welcome to follow the wechat public account “Flutter Programming and Development”.