Flutterで Widgetをくり抜く 方法

Flutter では [ClipRect](https://docs.flutter.io/flutter/widgets/ClipRect-class.html), [ClipOval](https://docs.flutter.io/flutter/widgets/ClipOval-class.html)　を使用して Widget を Clip ことができます。

**矩形切り取り**

```dart
new ClipRect(
  child: new Align(
    alignment: Alignment.topCenter,
    heightFactor: 0.5,
    child: new Image.network(userAvatarUrl),
  ),
)
```

**オーバル切り取り**

```dart
new ClipOval(
  child: new Align(
    color: Colors.black,
    alignment: Alignment.topCenter,
    child: new Image.network(userAvatarUrl),
  ),
)
```

くり抜くには、[ClipPath](https://docs.flutter.io/flutter/widgets/ClipPath-class.html) と [CustomClipper](https://docs.flutter.io/flutter/rendering/CustomClipper-class.html) を使用することで可能です。


```dart
ClipPath(
  clipper: InvertedCircleClipper(),
    child: Container(
    color: Color.fromRGBO(0, 0, 0, 0.8),
  ),
),
```

```dart
class InvertedCircleClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    return Path()
      // くり抜き
      ..addOval(
        Rect.fromCircle(
          center: Offset(size.width / 2, size.height / 2),
          radius: size.width * 0.25,
        ),
      )
      // 全体描画
      ..addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height))
      ..fillType = PathFillType.evenOdd;
  }

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

*こんな感じのことができます。*

![device-2018-10-01-180722.png](https://qiita-image-store.s3.amazonaws.com/0/12177/25217a31-f1fe-cb00-7c7e-79be677eaa30.png)


**全体ソースコード**

```dart
import 'package:flutter/material.dart';

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

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

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: SafeArea(
        child: Container(
          constraints: BoxConstraints.expand(),
          color: Colors.amber,
          child: Column(
            children: <Widget>[
              Expanded(
                child: Stack(
                  children: <Widget>[
                    Container(
                      color: Colors.green,
                      alignment: Alignment.center,
                      child: Image.asset(
                        'images/lake.jpg',
                        fit: BoxFit.cover,
                      ),
                    ),
                    new ClipOval(
                      child: new Align(
                        alignment: Alignment.topCenter,
                        heightFactor: 0.5,
                        child: new Image.network(userAvatarUrl),
                      ),
                    ),
                    ClipPath(
                      clipper: InvertedCircleClipper(),
                      child: Container(
                        color: Color.fromRGBO(0, 0, 0, 0.8),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class InvertedCircleClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    return Path()
      // くり抜き
      ..addOval(
        Rect.fromCircle(
          center: Offset(size.width / 2, size.height / 2),
          radius: size.width * 0.25,
        ),
      )
      // 全体描画
      ..addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height))
      ..fillType = PathFillType.evenOdd;
  }

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

*参考)*

- [Flutter: inverted ClipOval \- Stack Overflow](https://stackoverflow.com/questions/49374893/flutter-inverted-clipoval)