掌握 Flutter 中的形状和裁剪(flutter)
掌握 Flutter 中的形状和裁剪
在 Flutter 中,形状和裁剪是创建复杂 UI 设计的重要工具。以下是一个详细的指南,介绍如何掌握这些技术,包括自定义形状和裁剪器的实现。
1. 什么是形状和盒子?
- 形状:形状可以是任何由路径定义的形式。
- 盒子:盒子是一个矩形形状,由 4 个点组成,可以具有额外的属性,如边框半径。
在 Flutter 中,盒子可能出现在不同的上下文中,例如 RenderBox
、BoxDecoration
和 BoxBorder
。我们经常使用 BoxDecoration
来为 Container
或其他装饰盒子添加样式。
Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(16),
),
child: Text('Hello, World!'),
);
另外,我们还可以使用 ShapeDecoration
,它提供了类似的颜色、阴影和渐变的定制,主要的区别在于它的 shape
参数接受 ShapeBorder
,而不是 BoxShape
。
Container(
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
color: Colors.green,
),
child: Text('Hello, Shape!'),
);
2. 自定义 ShapeBorder 实现
为了实现自定义形状,我们需要创建 ShapeBorder
或 OutlinedBorder
的一个子类,并实现以下方法:
getInnerPath
和getOuterPath
:返回相应路径的方法。paint
:绘制形状的方法。scale
、copyWith
让我们使用圆锥形段来创建一个消息气泡形状,并使权重(w)参数可变。
class BubbleShapeBorder extends OutlinedBorder {
final double w;
BubbleShapeBorder(this.w);
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
final Path path = Path();
path.moveTo(rect.left, rect.bottom - w);
path.quadraticBezierTo(
rect.left,
rect.top,
rect.left + w,
rect.top
);
path.lineTo(rect.right - w, rect.top);
path.quadraticBezierTo(
rect.right,
rect.top,
rect.right,
rect.bottom - w
);
path.lineTo(rect.right, rect.bottom);
path.lineTo(rect.left, rect.bottom);
path.close();
return path;
}
@override
bool get isUniform => true;
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) => getOuterPath(rect, textDirection: textDirection);
@override
void paint(Canvas canvas, Rect rect, Paint paint) {}
@override
ShapeBorder scale(double t) => BubbleShapeBorder(w * t);
@override
ShapeBorder copyWith(TextDirection? textDirection) => BubbleShapeBorder(w);
}
// 使用自定义形状
Container(
decoration: ShapeDecoration(
shape: BubbleShapeBorder(30),
color: Colors.amber,
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text('Bubble Message'),
),
);
3. 裁剪器的应用
在 Flutter 中,有几个内置的裁剪器,例如:
ClipRect
:矩形裁剪ClipRRect
:圆角矩形裁剪ClipOval
:圆形和椭圆形裁剪ClipPath
:自定义路径裁剪
前三个非常简单,只需将你的 widget 包装在它们之一中即可进行裁剪。
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Container(
color: Colors.red,
child: Text('Clipped Rectangle'),
),
);
ClipPath
接受一个 CustomClipper
作为参数。在大多数情况下,只需将一个 ShapeBorder
传递给 ShapeBorderClipper
。
class CustomClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final Path path = Path();
path.moveTo(0, 0);
path.lineTo(size.width, 0);
path.lineTo(size.width - 50, size.height);
path.lineTo(50, size.height);
path.close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldDelegate) => false;
}
ClipPath(
clipper: CustomClipper(),
child: Container(
color: Colors.purple,
child: Text('Custom Clipped'),
),
);
4. 自定义裁剪器
在某些情况下,我们需要更多的裁剪控制,例如,当裁剪应该依赖于内容或某些兄弟 widget 时。
让我们创建一个票据形状的 widget,它的缺口将依赖于内容的大小。
class TicketClipper extends CustomClipper<Path> {
final GlobalKey topKey;
final GlobalKey bottomKey;
TicketClipper(this.topKey, this.bottomKey);
@override
Path getClip(Size size) {
final RenderBox? topBox = topKey.currentContext?.findRenderObject() as RenderBox?;
final RenderBox? bottomBox = bottomKey.currentContext?.findRenderObject() as RenderBox?;
final Rect? topRect = topBox?.paintBounds;
final Rect? bottomRect = bottomBox?.paintBounds;
final double topHeight = topRect?.height ?? 0;
final double bottomHeight = bottomRect?.height ?? 0;
final double middleHeight = size.height - topHeight - bottomHeight;
final Path path = Path();
path.moveTo(0, 0);
path.lineTo(size.width, 0);
path.lineTo(size.width - 20, topHeight + 20);
path.arcTo(
Rect.fromCircle(center: Offset(size.width - 40, topHeight + 40), radius: 20),
startAngle: -pi / 2,
sweepAngle: pi,
);
path.lineTo(size.width - middleHeight - 40, topHeight + 40 + middleHeight);
path.arcTo(
Rect.fromCircle(center: Offset(size.width - 40 - middleHeight, topHeight + 40 + middleHeight), radius: 20),
startAngle: pi,
sweepAngle: pi,
);
path.lineTo(20, size.height);
path.lineTo(0, size.height);
path.close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldDelegate) =>
oldDelegate.topKey != topKey || oldDelegate.bottomKey != bottomKey;
}
// 使用自定义裁剪器
Column(
children: <Widget>[
SizedBox(
height: 100,
key: GlobalKey(), // 顶部子项的 key
child: Container(color: Colors.blue,),
),
ClipPath(
clipper: TicketClipper(topKey, bottomKey),
child: Container(
color: Colors.yellow,
height: 200,
child: Column(
children: <Widget>[
SizedBox(
height: 50,
key: bottomKey, // 底部子项的 key
child: Container(color: Colors.red,),
),
Spacer(),
SizedBox(
height: 50,
child: Container(color: Colors.red,),
),
],
),
),
),
SizedBox(
height: 100,
child: Container(color: Colors.blue,),
),
],
);
以上代码展示了如何在 Flutter 中创建自定义形状和裁剪器,并通过示例展示了它们的应用。希望这些示例能帮助你更好地掌握 Flutter 中的形状和裁剪技术。
更多关于掌握 Flutter 中的形状和裁剪(flutter)的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于掌握 Flutter 中的形状和裁剪(flutter)的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter中,掌握形状和裁剪(Clipping)是创建复杂UI布局和动画效果的重要技能。Flutter提供了丰富的工具集,可以方便地绘制各种形状和裁剪路径。以下是一些关键概念和示例代码,帮助你理解和应用这些功能。
1. 绘制基本形状
Flutter通过Canvas
类绘制形状,但更常用的方式是通过CustomPaint
widget,它允许你自定义绘制逻辑。
圆形
import 'package:flutter/material.dart';
class CircleShape extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2;
canvas.drawCircle(center, radius, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
class CircleScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Circle Shape')),
body: Center(
child: CustomPaint(
size: Size(200, 200),
painter: CircleShape(),
),
),
);
}
}
矩形
class RectangleShape extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.green
..style = PaintingStyle.fill;
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.drawRect(rect, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
class RectangleScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Rectangle Shape')),
body: Center(
child: CustomPaint(
size: Size(200, 100),
painter: RectangleShape(),
),
),
);
}
}
2. 裁剪路径
Flutter通过ClipPath
widget来裁剪其子widget。
圆形裁剪
class ClipCircleScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Clip Circle')),
body: Center(
child: ClipPath(
clipper: CircleClipper(),
child: Container(
color: Colors.red,
width: 200,
height: 200,
),
),
),
);
}
}
class CircleClipper extends CustomClipper<Rect> {
@override
Rect getClip(Size size) {
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
return rect.deflate(size.width / 4); // 缩放裁剪区域
}
@override
bool shouldReclip(CustomClipper<Rect> oldClipper) => false;
}
注意,CircleClipper
类实际上返回一个矩形裁剪区域,但你可以通过自定义路径来实现圆形裁剪。为了简化,这里使用了Rect
,实际应用中应使用Path
类。
自定义路径裁剪
class CustomClipPathScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Custom Clip Path')),
body: Center(
child: ClipPath(
clipper: CustomClipperPath(),
child: Container(
color: Colors.purple,
width: 200,
height: 200,
),
),
),
);
}
}
class CustomClipperPath extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path();
path.moveTo(0, size.height / 2);
path.quadraticBezierTo(size.width / 4, 0, size.width, size.height / 2);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
通过这些示例代码,你可以了解如何在Flutter中绘制基本形状和进行裁剪。你可以进一步扩展这些示例,结合动画和其他UI组件,创建复杂而美观的用户界面。