1

I would like to operate some perspective on a ClipPath overlay with Flutter. I reproduced inverted Clip Oval from Flutter: inverted ClipOval which works fine.

Then i would like to operate a perspective on this overlay:

For now i use a Transform widget but the "grey background" gets also rotated.I would like the background to expand on all screen left.

I think i should rotate only in InvertedRectClipper but i can't find a way to do something similar as alignment: FractionalOffset.center which tell where is the origin to rotate on.

Anyone have a tips for this ?

Actual screenshot :

Background is not entirely grey

Full code to test :

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: SafeArea(
      child: Stack(
        children: <Widget>[
          Container(
            color: Colors.white,
          ),
          Transform(
            alignment: FractionalOffset.center,
            transform: Matrix4.identity()
              ..setEntry(3, 2, 0.0005) // perspective
              ..rotateX(-0.9),
            child: ClipPath(
              clipper: InvertedRectClipper(),
              child: Container(
                color: Color.fromRGBO(0, 0, 0, 0.5),
              ),
            ),
          )
        ],
      ),
    ),
  ));
}

class InvertedRectClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    return Path()
      ..addRect(Rect.fromCenter(
          center: Offset(size.width / 2, size.height / 2),
          width: size.width / 2,
          height: size.height / 2))
      ..addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height))
      ..fillType = PathFillType.evenOdd;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}

1 Answers1

0

The key is to only transform the path you need to transform, not the whole widget. Here is a sample :

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: SafeArea(
      child: Stack(
        children: <Widget>[
          Container(
            color: Colors.blue,
          ),
          ClipPath(
            clipper: InvertedRectClipper(),
            child: Container(
              color: Colors.black.withOpacity(0.5),
            ),
          )
        ],
      ),
    ),
  ));
}

class InvertedRectClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    // First rectangle, to be transformed
    var path = Path()
      ..fillType = PathFillType.evenOdd
      ..addRect(Rect.fromCenter(
          center: Offset(size.width / 2, size.height / 2),
          width: size.width / 2,
          height: size.height / 2));

    // Transform matrix
    final matrix = Matrix4.identity()
      ..setEntry(3, 2, 0.0005)
      ..rotateX(-0.9);
    path = path.transform(matrix.storage);

    // Outer rectangle, straight
    path.addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height));

    // Return path
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}

Here is the result

Result

You'll 'just' have to adapt the transformation to your convenance.

** EDIT **

If you need the transformation to be centered relative to your rectangle, an easy way is to draw the rectangle centered on the origin, apply the transformation, then translate it to be centered to the screen :

  Path getClip(Size size) {
    // First rectangle, to be transformed, centered at the origin
    var path = Path()
      ..fillType = PathFillType.evenOdd
      ..addRect(Rect.fromCenter(
          center: Offset(0, 0),
          width: size.width / 2,
          height: size.height / 2));

    // Rotation X
    final matrix = Matrix4.identity()
      ..setEntry(3, 2, 0.001)
      ..rotateX(-0.9);
    path = path.transform(matrix.storage);
    
    // Translation to center back the rectangle
    path = path.transform(Matrix4.translationValues(size.width / 2, size.height / 2, 0).storage);

    // Outer rectangle, straight
    path.addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height));

    // Return path
    return path;
  }

Result 2

Nicolas Youpi
  • 178
  • 3
  • 11