掌握 Flutter 中的形状和裁剪(flutter)

发布于 1周前 作者 eggper 来自 Flutter

掌握 Flutter 中的形状和裁剪

在 Flutter 中,形状和裁剪是创建复杂 UI 设计的重要工具。以下是一个详细的指南,介绍如何掌握这些技术,包括自定义形状和裁剪器的实现。

1. 什么是形状和盒子?

  • 形状:形状可以是任何由路径定义的形式。
  • 盒子:盒子是一个矩形形状,由 4 个点组成,可以具有额外的属性,如边框半径。

在 Flutter 中,盒子可能出现在不同的上下文中,例如 RenderBoxBoxDecorationBoxBorder。我们经常使用 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 实现

为了实现自定义形状,我们需要创建 ShapeBorderOutlinedBorder 的一个子类,并实现以下方法:

  • getInnerPathgetOuterPath:返回相应路径的方法。
  • paint:绘制形状的方法。
  • scalecopyWith

让我们使用圆锥形段来创建一个消息气泡形状,并使权重(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

1 回复

更多关于掌握 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组件,创建复杂而美观的用户界面。

回到顶部