首页
Preview

Flutter CustomClipper 自定义裁剪的应用

什么是CustomClipper

CustomClipper并不是一个组件,而是一个abstract(抽象)类,使用CustomClipper可以绘制出任何我们想要的形状。

ClipPath 裁剪路径

ClipPath用于创建任何形状的非常自定义的小部件。

ClipPath(
    clipper: DiamondCurve(),
    child: Container(
      width: 300,
      height: 200,
      color: Colors.orange,
    ),
),

要创建自定义裁剪,需要创建一个扩展CustomClipper<Path>的类,并且必须覆盖两个方法。 自定义裁剪需要更新时,就会调用getClip方法,并且此方法有一个Size参数,它提供小部件的高度和宽度值。

当提供该类的新实例时,将调用shouldReclip方法。如果新实例的信息与旧实例不同,则该方法应返回 true,否则应返回 false。

class DiamondCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path()
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return true;
  }
  
}

lineTo

此方法用于绘制从当前点到给定点的线段。

1_d8b4V3eknmGGuTvH2NaAxw.webp 如上图(a)所示,路径默认从点p1(0,0)开始。现在向 p2(0, h) 添加一个新行,然后向 p3(w, h) 添加一行。

我们不需要添加一条从端点 p3 到起点 p1 的线,它会默认绘制。

@override
Path getClip(Size size) {

  Path path = Path()
    ..lineTo(0, size.height) //p1->p2
    ..lineTo(size.width, size.height) //p2->p3
    ..close();
  
  return path;
}

moveTo

此方法用于将路径移动到绘图中的特定点。

1_k6clLYOLL5dFNvkUt9bBTA.webp

如上图所示,起点从(0,0)移动到p1(w/2,0)。

@override
Path getClip(Size size) {
  Path path = Path()  
    ..moveTo(size.width/2, 0)   // 移到 (w/2,0)
    ..lineTo(0, size.width)
    ..lineTo(size.width, size.height)
    ..close();

  return path;
}

我们可以再画一个三角形:

///三角形
class TriangleCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path  = Path()
        ..moveTo(size.width / 2, 0)
        ..lineTo(0, size.height)
        ..lineTo(size.width,size.height);
        return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}

quadraticBezierTo

我们还可以创建二次贝塞尔曲线,运用quadraticBezierTo

1_wajlGhKWifQ0zDwFhlT9UA.webp 如上图(a)所示,使用控制点c(w/2,h/2)在点p2(0,h)到p3(w,h)绘制曲线。

class TriangleBezierCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path()
        ..moveTo(size.width / 2, 0)
        ..lineTo(0, size.height)
        ..quadraticBezierTo(
            Offset(size.width/2, size.height/2).dx,
            Offset(size.width/2, size.height/2).dy,
            Offset(size.width, size.height).dx,
          Offset(size.width, size.height).dy,
        );
    return path;

  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }
  
}

cubicTo

讲到二次贝塞尔曲线,那就缺少不了我们的三次贝塞尔曲线,我们需要通过指定 2 个控制点和端点来创建三次曲线,如下图: 1_POA2id6mjpD4G8hEI0hTXw.webp

class CubicToBezierCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path()
        ..moveTo(size.width / 2, 0)
        ..lineTo(0, size.height - 50)
        ..cubicTo(
            Offset(50, size.height - 100).dx,
            Offset(50, size.height - 100).dy,
            Offset(size.width - 50, size.height).dx,
            Offset(size.width - 50, size.height).dy,
            Offset(size.width, size.height - 50).dx,
            Offset(size.width, size.height - 50).dy,
        );
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}

弧度到点 arcToPoint

该方法用于绘制从起点到指定点的圆弧。我们可以通过设置半径、顺时针/逆时针方向来自定义圆弧。

1_RBV-v7qCsf_er40SNKAw8A.webp

有椭圆形和圆形类型的半径来绘制圆弧。如上图所示,椭圆半径使用(x,y)值绘制,圆半径使用radius(R)绘制。

1_II16sL7mRQGGHORSbnDQYQ.webp 如上图(a)所示,路径从p1点开始。第一条弧从起点 p2 绘制到终点 p3 并且未指定半径,因此默认情况下为零,所以它看起来像一条线。第二条圆弧使用圆半径从起点 p4 绘制到终点 p5,方向为顺时针方向(方向默认为顺时针方向)。第三条圆弧使用圆半径从起点 p6 绘制到终点 p7,方向为逆时针方向。第四个圆弧使用椭圆半径从起点 p8 绘制到终点 p1。

/// 弧度到点
class ArcToPointCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    double radius = 20.0;
    Path path = Path()
        ..moveTo(radius, 0)
        ..lineTo(size.width - radius, 0)
        ..arcToPoint(Offset(size.width, radius))
        ..lineTo(size.width, size.height - radius)
        ..arcToPoint(Offset(size.width-radius, size.height),radius: Radius.circular(radius))
        ..lineTo(radius, size.height)
        ..arcToPoint(Offset(0, size.height - radius),radius: Radius.circular(radius),clockwise: false)
        ..lineTo(0, radius)
        ..arcToPoint(Offset(radius, 0),radius: const Radius.elliptical(40, 20))
        ..close();
    return path;

  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}

arcTo

用于通过将Rect、起始角和扫描(结束)角指定为弧度来绘制弧。

1_hxWF1rcwjvz8m_J7D3fm8Q.webp

上图是有关弧度角的基本信息。它从 0 PI(PI 值为 3.14)开始到 2 PI。

1_ZUTBDSxUQZPYxylT12ZUwQ.webp

有几种绘制矩形的方法,比如从点、从圆、从 LTRB(左、上、右、下)和 LTWH(左、上、宽、高)。在上图(a)中,所有类型的圆弧都以不同的起始角度绘制。

class ArcToCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    double radius = 50.0;
    Path path = Path()
      ..lineTo(size.width-radius, 0)
      ..arcTo(Rect.fromPoints(
          Offset(size.width - radius,0), Offset(size.width,radius)),
          1.5 * pi,
          0.5 * pi,
          true
      )
      ..lineTo(size.width, size.height - radius)
      ..arcTo(
          Rect.fromCircle(
            center: Offset(size.width-radius,size.height-radius),
            radius: radius
          ),
          0,
          0.5 * pi,
          false
      )
      ..lineTo(radius, size.height)
      ..arcTo(
          Rect.fromLTRB(0, size.height - radius, radius, size.height),
          0.5 * pi,
          0.5 * pi,
          false
      )
      ..lineTo(0, radius)
      ..arcTo(
          Rect.fromLTWH(0, 0, 70, 100),
          1 * pi,
          0.5 * pi,
          false
      )
      ..close();

    return path;

  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}
Rect.fromPoints

通过两个坐标点来绘制矩形。

Rect.fromPoints(Offset a, Offset b)需要传递两个Offset对象:

  • a代表左上角点的坐标
  • b代表右下角的点的坐标
Rect.fromCircle

构造一个以给定圆为边界的矩形。 Rect.fromCircle({required Offset center, required double radius})需要传递2个对象:

  • center用来设置圆的圆心坐标
  • radius用来设置圆的半径
Rect.fromLTRB

从左、上、右和下边缘构造一个矩形。

Rect.fromLTRB(left,top,right,bottom)需要传递4个参数:

  • left代表左上角顶点的x坐标
  • top代表左上角顶点的y坐标
  • right代表右下角顶点的x坐标
  • bottom代表右下角顶点的y坐标
Rect.fromLTWH

通过左上角点的坐标和宽度、高度来构造一个矩形

Rect.fromLTWH(left,top,width,height)需要传递4个参数:

  • left用来设置左上角的x坐标
  • top用来设置左上角的y坐标
  • width用来设置矩形的宽
  • height用来设置矩形的高

addRect

用于添加带圆角的矩形。我们可以在所有边或特定边上做一个圆角。

1_OS9MvQpoiwuWr6hxIqdcQg.webp

//  画矩形addRRect
class AddRRectCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    double radius = 10.0;
    Path path = Path()
      ..addRRect(
          RRect.fromLTRBR(
              0,
              0,
              60,
              60,
              Radius.circular(radius)
          )
      )
      ..addRRect(
        RRect.fromRectAndRadius(
          Rect.fromLTWH(0, size.height - 50, 50, 50),
          Radius.circular(radius)
        ),
        
      )
      ..addRRect(
        RRect.fromRectAndCorners(
          Rect.fromCircle(
              center: Offset(size.width/2,size.height/2),
              radius: 30
          ),
          topLeft: Radius.circular(radius),
          bottomRight: Radius.circular(radius)
        )
      )
      ..close();

    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}
RRect.fromLTRBR

RRect.fromLTRBR(left,top,right,bottom,radius)需要传递5个参数,前4个参数和Rect.fromLTRB一样:

  • left代表左上角顶点的x坐标
  • top代表左上角顶点的y坐标
  • right代表右下角顶点的x坐标
  • bottom代表右下角顶点的y坐标
  • radius用来设置矩形的圆角半径
RRect.fromRectAndRadius

通过绘制一个矩形再设置圆角半径来绘制圆角矩形

RRect RRect.fromRectAndRadius(Rect rect, Radius radius)需要传递2个参数:

  • rect是一个矩形对象
  • radius为矩形设置圆角
RRect.fromRectAndCorners
RRect.fromRectAndCorners( 
    Rect rect,
    { Radius topLeft = Radius.zero, 
    Radius topRight = Radius.zero,
    Radius bottomRight = Radius.zero,
    Radius bottomLeft = Radius.zero}
)

可以传递5个参数,1个必要的,4个可选的:

  • rect用来绘制一个矩形
  • topLeft用来设置左上角的圆角半径
  • topRight用来设置右上角的圆角半径
  • bottomRight用来设置右下角角的圆角半径
  • bottomLeft用来设置左下角的圆角半径

addOval 画椭圆

用于在路径中添加椭圆。与addRect相同,此方法需要Rect参数。

1_NA5ffa9wKCDORPSas2yjWg.webp

/// `addOval` 画椭圆
class AddOvalCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path()
        ..addOval(Rect.fromPoints(Offset(0,0), Offset(60,60)))
        ..addOval(Rect.fromCircle(center: Offset(size.width/2,size.height/2), radius: 20))
        ..addOval(Rect.fromLTWH(0, size.height - 50, 100, 50))
        ..close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
   return false;
  }

}

addPolygon

用于通过指定多个点将多边形添加到路径中。

1_xLBW5vFjFFm-rFpVEMdSPw.webp

class AddPolygonCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var points = [
      Offset(size.width/2, 0),
      Offset(0, size.height/2),
      Offset(size.width / 2, size.height),
      Offset(size.width, size.height/2)
    ];

    Path path = Path()
      ..addPolygon(points, false)
      ..close();

    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}

addPath

用于通过指定偏移量在主路径中添加另一个子路径。偏移量是计算坐标的其他路径的原点。

1_7nvgLsfyxEBMkExrLClB9w.webp

如上图(a)所示,绘制了两条路径,路径1为主路径,路径2是在路径1中添加的。路径2分别绘制偏移量(w/2),所以以( 0,0) 和所有其他点分别绘制到偏移点。

class AddPathCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path1 = Path()
        ..lineTo(0, size.height)
        ..lineTo(size.width/2, size.height)
        ..lineTo(0, 0);
    Path path2 = Path()
        ..lineTo(size.width/2, size.height)
        ..lineTo(size.width/2, 0)
        ..lineTo(0, 0);
    path1.addPath(path2, Offset(size.width/2, 0));

    return path1;

  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }
  
}

relativeLineTo

与 lineTo 相同,但线点是从路径的最后一个点计算的。

1_hRJKaAvgjGtDOXHrACsR4Q.webp 如图 (a) 所示,线 p1p2 是使用relativeLineTo绘制的,因此是相对于最后一个点 p1 绘制的。很简单,如果你想绘制关于原点 (0,0) 的点,那么你可以这样做,最终点p2(x,y) = (p1.x + p2.x, p1.y +p2.y)

class RelativeLineToCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path()
        ..moveTo(size.width/2, 0)
        ..relativeLineTo(50, size.height)
        ..lineTo(size.width, size.height)
        ..close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}

全部源码:

import 'dart:math';

import 'package:flutter/material.dart';

class CustomClipperCurve extends StatefulWidget {
  const CustomClipperCurve({Key? key}) : super(key: key);

  @override
  State<CustomClipperCurve> createState() => _CustomClipperCurveState();
}

class _CustomClipperCurveState extends State<CustomClipperCurve> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('CustomClipper')),
      body: SingleChildScrollView(
        child: Padding(
          padding: EdgeInsets.all(15.0),
          child: Column(
            children: [
              ClipPath(
                clipper: DiamondCurve(),
                child: Container(
                  width: 200,
                  height: 100,
                  color: Colors.orange,
                ),
              ),
              const SizedBox(height: 20,),
              ClipPath(
                clipper: TriangleCurve(),
                child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.red,
                ),
              ),
              const SizedBox(height: 20,),
              ClipPath(
                clipper: QuadraticBezierCurve(),
                child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.purple,
                ),
              ),
              const SizedBox(height: 20,),
              ClipPath(
                clipper: CubicToBezierCurve(),
                child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.green,
                ),
              ),

              const SizedBox(height: 20,),
              ClipPath(
                clipper: ArcToPointCurve(),
                child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.pink,
                ),
              ),
              const SizedBox(height: 20,),
              ClipPath(
                clipper: ArcToCurve(),
                child: Container(
                  width: 200,
                  height: 200,
                  color: Colors.pink,
                ),
              ),
              const SizedBox(height: 20,),
              ClipPath(
                clipper: AddRRectCurve(),
                child: Container(
                  width: 200,
                  height: 200,
                  color: Colors.pink,
                ),
              ),

              const SizedBox(height: 40,),
              ClipPath(
                clipper: AddOvalCurve(),
                child: Container(
                  width: 200,
                  height: 200,
                  color: Colors.pink,
                ),
              ),
              const SizedBox(height: 40,),
              ClipPath(
                clipper: AddPolygonCurve(),
                child: Container(
                  width: 200,
                  height: 200,
                  color: Colors.pink,
                ),
              ),

              const SizedBox(height: 40,),
             Container(
               color: Colors.grey[200],
               child:  ClipPath(
                 clipper: AddPathCurve(),
                 child: Container(
                   width: 200,
                   height: 200,
                   color: Colors.pink,
                 ),
               ),
             ),
              const SizedBox(height: 40,),

              ClipPath(
                clipper: RelativeLineToCurve(),
                child: Container(
                  width: 200,
                  height: 200,
                  color: Colors.pink,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

///三角形
class TriangleCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path  = Path()
        ..moveTo(size.width / 2, 0)
        ..lineTo(0, size.height)
        ..lineTo(size.width,size.height);
        return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}
///菱形
class DiamondCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path()
        ..moveTo(size.width / 2, 0)
        ..lineTo(0, size.height / 2)
        ..lineTo(size.width / 2, size.height)
        ..lineTo(size.width, size.height / 2);
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return true;
  }
  
}

// 二次贝塞尔曲线
class QuadraticBezierCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path()
        ..moveTo(size.width / 2, 0)
        ..lineTo(0, size.height)
        ..quadraticBezierTo(
            Offset(size.width/2, size.height/2).dx,
            Offset(size.width/2, size.height/2).dy,
            Offset(size.width, size.height).dx,
          Offset(size.width, size.height).dy,
        );
    return path;

  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }
  
}

/// 三次贝塞尔曲线
class CubicToBezierCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path()
        ..moveTo(size.width / 2, 0)
        ..lineTo(0, size.height - 50)
        ..cubicTo(
            Offset(50, size.height - 100).dx,
            Offset(50, size.height - 100).dy,
            Offset(size.width - 50, size.height).dx,
            Offset(size.width - 50, size.height).dy,
            Offset(size.width, size.height - 50).dx,
            Offset(size.width, size.height - 50).dy,
        );
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}

/// 弧度到点
class ArcToPointCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    double radius = 20.0;
    Path path = Path()
        ..moveTo(radius, 0)
        ..lineTo(size.width - radius, 0)
        ..arcToPoint(Offset(size.width, radius))
        ..lineTo(size.width, size.height - radius)
        ..arcToPoint(Offset(size.width-radius, size.height),radius: Radius.circular(radius))
        ..lineTo(radius, size.height)
        ..arcToPoint(Offset(0, size.height - radius),radius: Radius.circular(radius),clockwise: false)
        ..lineTo(0, radius)
        ..arcToPoint(Offset(radius, 0),radius: const Radius.elliptical(40, 20))
        ..close();
    return path;

  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}

class ArcToCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    double radius = 50.0;
    Path path = Path()
      ..lineTo(size.width-radius, 0)
      ..arcTo(Rect.fromPoints(
          Offset(size.width - radius,0), Offset(size.width,radius)),
          1.5 * pi,
          0.5 * pi,
          true
      )
      ..lineTo(size.width, size.height - radius)
      ..arcTo(
          Rect.fromCircle(
            center: Offset(size.width-radius,size.height-radius),
            radius: radius
          ),
          0,
          0.5 * pi,
          false
      )
      ..lineTo(radius, size.height)
      ..arcTo(
          Rect.fromLTRB(0, size.height - radius, radius, size.height),
          0.5 * pi,
          0.5 * pi,
          false
      )
      ..lineTo(0, radius)
      ..arcTo(
          Rect.fromLTWH(0, 0, 70, 100),
          1 * pi,
          0.5 * pi,
          false
      )
      ..close();

    return path;

  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}

//  画矩形addRRect
class AddRRectCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    double radius = 10.0;
    Path path = Path()
      ..addRRect(
          RRect.fromLTRBR(
              0,
              0,
              60,
              60,
              Radius.circular(radius)
          )
      )
      ..addRRect(
        RRect.fromRectAndRadius(
          Rect.fromLTWH(0, size.height - 50, 50, 50),
          Radius.circular(radius)
        ),
        
      )
      ..addRRect(
        RRect.fromRectAndCorners(
          Rect.fromCircle(
              center: Offset(size.width/2,size.height/2),
              radius: 30
          ),
          topLeft: Radius.circular(radius),
          bottomRight: Radius.circular(radius)
        )
      )
      ..close();

    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}

/// `addOval` 画椭圆
class AddOvalCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path()
        ..addOval(Rect.fromPoints(Offset(0,0), Offset(60,60)))
        ..addOval(Rect.fromCircle(center: Offset(size.width/2,size.height/2), radius: 20))
        ..addOval(Rect.fromLTWH(0, size.height - 50, 100, 50))
        ..close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
   return false;
  }

}

/// 多边形
class AddPolygonCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var points = [
      Offset(size.width/2, 0),
      Offset(0, size.height/2),
      Offset(size.width / 2, size.height),
      Offset(size.width, size.height/2)
    ];

    Path path = Path()
      ..addPolygon(points, false)
      ..close();

    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}

/// 添加路径

class AddPathCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path1 = Path()
        ..lineTo(0, size.height)
        ..lineTo(size.width/2, size.height)
        ..lineTo(0, 0);
    Path path2 = Path()
        ..lineTo(size.width/2, size.height)
        ..lineTo(size.width/2, 0)
        ..lineTo(0, 0);
    path1.addPath(path2, Offset(size.width/2, 0));

    return path1;

  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }
  
}

class RelativeLineToCurve extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path()
        ..moveTo(size.width/2, 0)
        ..relativeLineTo(50, size.height)
        ..lineTo(size.width, size.height)
        ..close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }

}

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
励志猿
励志每天写一篇文章,有价值的文章,提升自我!

评论(0)

添加评论