😗
【Flutter】ClipPathを使って楕円のContainerを作成した
はじめに
dribbbleで見ていいなと思ったUIをFlutterでClipPathを使って実現しました。
環境
flutter --version
Flutter 3.10.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision f92f44110e (9 weeks ago) • 2023-06-01 18:17:33 -0500
Engine • revision 2a3401c9bb
Tools • Dart 3.0.3 • DevTools 2.23.1
ContainerをClipPathを使って上部が楕円になるようにくり抜く
ClipPath
はWidgetを自由にくり抜くWidgetです。
CustomClipper<Path>
を継承した別クラスで描画の設定をし、clipper
で呼び出すと親Widgetをくり抜いてくれます。
描画の設定
CustomClipper<Path>
を継承したMyClipper
を用意しました。
class MyClipper extends CustomClipper<Path> {
Path getClip(Size size) {
double w = size.width; //親Widgetのwidth
double h = size.height; //親Widetのheight
double curveHeight = 20;
final path = Path();
path.moveTo(0, curveHeight);
path.lineTo(0, h);
path.lineTo(w, h);
path.lineTo(w, curveHeight);
path.quadraticBezierTo(w / 2, -curveHeight, 0, curveHeight);
path.close();
return path;
}
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
getClip
の引数である、size
は親Widgetのサイズになります。
初期設定では、左上が描画の原点です。
原点はmoveTo
で移動できます。
以降は順番にlineTo
で描画する点を指定していきます。
カーブの描画はquadraticBezierTo
でします。
最後にclose
で描画を終了します。
ClipPathにclipperを追加
ClipPathに、先ほど作成したMyClipperを指定する。これで楕円のContainerの完成です。
//省略
ClipPath(
clipper: MyClipper(), //指定
child: Container(
width: width,
height: 120,
color: Colors.blue,
),
//省略
利用例
私はこれを下付きのボタンにしたかったので、以下のようなWidgetを作成しました。
round_container_button.dart
import 'package:flutter/material.dart';
import '../round_container.dart';
class RoundContainerButton extends StatelessWidget {
const RoundContainerButton({
required this.onTap,
super.key,
});
final Function onTap;
Widget build(BuildContext context) {
return InkWell(
onTap: () {
onTap;
},
child: const RoundContainer(
title: "円形のボタン",
),
);
}
}
コード全体
view_screen.dart
import 'package:flutter/material.dart';
import 'ui/widget/button/round_container_button.dart';
class ViewScreen extends StatelessWidget {
const ViewScreen({Key? key}) : super(key: key);
static Route<void> route() {
return MaterialPageRoute<dynamic>(
builder: (_) => const ViewScreen(),
);
}
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
RoundContainerButton(
onTap: () {
print("onTap!!");
},
),
],
),
);
}
}
round_container.dart
import 'package:flutter/material.dart';
class RoundContainer extends StatelessWidget {
const RoundContainer({
super.key,
required this.title,
});
final String title;
Widget build(BuildContext context) {
final screen = MediaQuery.of(context).size;
final double width = screen.width;
return ClipPath(
clipper: MyClipper(),
child: Container(
width: width,
height: 120,
color: Colors.blue,
child: Center(
child: Text(
title,
style: const TextStyle(color: Colors.white),
)),
),
);
}
}
class MyClipper extends CustomClipper<Path> {
Path getClip(Size size) {
double w = size.width;
double h = size.height;
double curveHeight = 20;
final path = Path();
path.moveTo(0, curveHeight);
path.lineTo(0, h);
path.lineTo(w, h);
path.lineTo(w, curveHeight);
path.quadraticBezierTo(w / 2, -curveHeight, 0, curveHeight);
path.close();
return path;
}
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
round_container_button.dart
import 'package:flutter/material.dart';
import '../round_container.dart';
class RoundContainerButton extends StatelessWidget {
const RoundContainerButton({
required this.onTap,
super.key,
});
final Function onTap;
Widget build(BuildContext context) {
return InkWell(
onTap: () {
onTap;
},
child: const RoundContainer(
title: "円形のボタン",
),
);
}
}
参考にしたもの
以下の動画をもとに今回のWidgetを作成しました。
この動画で紹介されていた、https://shapemaker.web.app/#/ を利用すれば簡単に理想の描画ができそうです。(Flutter Webでお馴染みの#が入ってますね)
最後に
ClipPath
を使用すればかなり自由度が高いWidgetを作成できそうだと感じました。
次は今回のWidgetを応用したものの作成や、描画に処理にどれぐらい負荷がかかるか検証したいと思います。
ここまでご覧いただきありがとうございました。
Discussion