Flutter 通過Clipper實現各種自定義形狀

藍色微笑ing發表於2019-12-03

ClipOval 圓形裁剪

Flutter 通過Clipper實現各種自定義形狀

ClipOval(
    child: SizedBox(
        width: 120.0,
        height: 120.0,
        child: Image.asset(
            Config.assets_avatar_1,
        ),
    ),
);
複製程式碼

CircleAvatar 圓形頭像

Flutter 通過Clipper實現各種自定義形狀

CircleAvatar(
    radius: 60.0,
    backgroundImage: AssetImage(
        Config.assets_avatar_1,
    ),
);
複製程式碼

Container Decoration 裝飾形狀

Flutter 通過Clipper實現各種自定義形狀

通過BoxShape.circle實現圓形圖片

Container(
    width: 120.0,
    height: 120.0,
    decoration: BoxDecoration(
        shape: BoxShape.circle,
        image: DecorationImage(
            image: AssetImage(
                Config.assets_avatar_1,
            ),
        ),
    )
);
複製程式碼

通過BorderRadius實現圓形圖片

Container(
    width: 120.0,
    height: 120.0,
    decoration: BoxDecoration(
        borderRadius: BorderRadius.all(Radius.circular(60.0)),
            image: DecorationImage(
                image: AssetImage(
                    Config.assets_avatar_1,
                ),
            ),
    ),
)
複製程式碼

ClipPath 路徑剪裁

Flutter 通過Clipper實現各種自定義形狀

ClipPath(
    clipper: TriangleClipper(ClipperPosition.LeftTop),
    child: Container(
        width: 16.0,
        height: 16.0,
        decoration: BoxDecoration(
            color: Colors.blue,
        ),
    ),
);

enum ClipperPosition {
    LeftTop,
    RightTop,
}

class TriangleClipper extends CustomClipper<Path> {
    final ClipperPosition position;
    TriangleClipper(this.position);

    @override
    Path getClip(Size size) {
        final path = Path();
        path.lineTo(0.0, 0.0);
        if (position == ClipperPosition.LeftTop) {
            path.lineTo(size.width, 0.0);
            path.lineTo(size.width, size.height);
        } else if (position == ClipperPosition.RightTop) {
            path.lineTo(size.width, 0.0);
            path.lineTo(0.0, size.height);
        }
        path.close();
        return path;
    }

    @override
    bool shouldReclip(CustomClipper oldClipper) {
        return false;
    }
}
複製程式碼

ClipRect 矩形剪裁

Flutter 通過Clipper實現各種自定義形狀

Container(
    alignment: Alignment.topCenter,
    color: Colors.transparent,
    child: Container(
        color: Colors.green,
        child: ClipRect(
            clipper: _RectClipper(20.0),
            child: Image.asset(
                Config.assets_avatar_1,
                width: 160.0,
                height: 160.0,
                fit: BoxFit.fill,
            ),
        ),
    ),
);

class _RectClipper extends CustomClipper<Rect> {
    /// Remove side of size
    final double removeSize;
    
    _RectClipper(this.removeSize);
    
    @override
    Rect getClip(Size size) {
        return new Rect.fromLTRB(
            removeSize,
            removeSize,
            size.width - removeSize,
            size.height - removeSize,
        );
    }
    
    @override
    bool shouldReclip(CustomClipper<Rect> oldClipper) {
        return false;
    }
}
複製程式碼

ClipRRect 圓角矩形剪裁

Flutter 通過Clipper實現各種自定義形狀

ClipRRect(
    borderRadius: BorderRadius.all(Radius.circular(16.0)),
    child: Image.asset(
        Config.assets_avatar_1,
        fit: BoxFit.fill,
        width: 120.0,
        height: 120.0,
    ),
);
複製程式碼

Star Rating(CustomPaint) 評分控制元件

評分控制元件 UI圖

Flutter 通過Clipper實現各種自定義形狀

實現方案

  1. 使用CustomPaint結合ClipPath畫出單個五角星;
  2. 使用Stack渲染兩層畫面
    • 背景層,一排灰色五角星
    • 前景層,一排亮色五角星,並使用ClipRect擷取一定Width

實現程式碼

class StarRatingDemo extends StatefulWidget {
    @override
    _StarRatingDemoState createState() => _StarRatingDemoState();
}

class _StarRatingDemoState extends State<StarRatingDemo> {
    /// ClipPath Star Rating
    _buildClipPathStarRating(double rate, int count) {
        return Container(
            padding: EdgeInsets.fromLTRB(24.0, 16.0, 24.0, 0.0),
            child: StaticRatingBar(
                size: 50.0,
                rate: rate,
                count: count,
            ),
        );
    }
    
    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                centerTitle: true,
                title: Text('Star Rating'),
            ),
            body: ListView(
                physics: BouncingScrollPhysics(),
                children: <Widget>[
                    // _buildClipPathStarRating(1.0, 1),
                    _buildClipPathStarRating(0.5, 5),
                    _buildClipPathStarRating(2.0, 5),
                    _buildClipPathStarRating(3.0, 5),
                    _buildClipPathStarRating(4.0, 5),
                    _buildClipPathStarRating(5.0, 5),
                    _buildClipPathStarRating(5.5, 6),
                    SizedBox(height: 16.0),
                ],
            ),
        );
    }
}

class StaticRatingBar extends StatelessWidget {
    /// Number of stars
    final int count;
    
    /// Init rate
    final double rate;
    
    /// Size of the starts
    final double size;
    
    final Color colorLight;
    
    final Color colorDark;
    
    StaticRatingBar({
        this.rate = 5,
        this.colorLight = const Color(0xFF1E88E5),
        this.colorDark = const Color(0xFFEEEEEE),
        this.count = 5,
        this.size = 60,
    });
    
    Widget buildDarkStar() {
        return SizedBox(
            width: size * count,
            height: size,
            child: CustomPaint(
                painter: _PainterStars(
                    count: count,
                    color: colorDark,
                    strokeWidth: 0.0,
                    size: this.size / 2,
                    style: PaintingStyle.fill,
                ),
            ),
        );
    }
    
    Widget buildLightStar() {
        return ClipRect(
            clipper: _RatingBarClipper(rate * size),
            child: SizedBox(
                height: size,
                width: size * count,
                child: CustomPaint(
                    painter: _PainterStars(
                        count: count,
                        strokeWidth: 0.0,
                        color: colorLight,
                        size: this.size / 2,
                        style: PaintingStyle.fill,
                    ),
                ),
            ),
        );
    }
    
    @override
    Widget build(BuildContext context) {
        return Stack(
            children: <Widget>[
                buildDarkStar(),
                buildLightStar(),
            ],
        );
    }
}

class _RatingBarClipper extends CustomClipper<Rect> {
    final double width;
    
    _RatingBarClipper(this.width);
    
    @override
    Rect getClip(Size size) {
        return Rect.fromLTRB(0.0, 0.0, width, size.height);
    }
    
    @override
    bool shouldReclip(_RatingBarClipper oldClipper) {
        return false;
    }
}

class _PainterStars extends CustomPainter {
    final double size;
    final int count;
    final Color color;
    final PaintingStyle style;
    final double strokeWidth;
    
    _PainterStars({
        this.size,
        this.count,
        this.color,
        this.strokeWidth,
        this.style,
    });

    double degree2Radian(int degree) {
        return (pi * degree / 180);
    }

    Path createStarPath(double radius, Path path) {
        double radian = degree2Radian(36);
        double radiusIn = (radius * sin(radian / 2) / cos(radian)) * 1.1;
        path.moveTo((radius * cos(radian / 2)), 0.0);
        path.lineTo(
            (radius * cos(radian / 2) + radiusIn * sin(radian)),
            (radius - radius * sin(radian / 2)),
        );
        path.lineTo(
            (radius * cos(radian / 2) * 2),
            (radius - radius * sin(radian / 2)),
        );
        path.lineTo(
            (radius * cos(radian / 2) + radiusIn * cos(radian / 2)),
            (radius + radiusIn * sin(radian / 2)),
        );
        path.lineTo(
            (radius * cos(radian / 2) + radius * sin(radian)),
            (radius + radius * cos(radian)),
        );
        path.lineTo((radius * cos(radian / 2)), (radius + radiusIn));
        path.lineTo(
            (radius * cos(radian / 2) - radius * sin(radian)),
            (radius + radius * cos(radian)),
        );
        path.lineTo(
            (radius * cos(radian / 2) - radiusIn * cos(radian / 2)),
            (radius + radiusIn * sin(radian / 2)),
        );
        path.lineTo(0.0, (radius - radius * sin(radian / 2)));
        path.lineTo(
            (radius * cos(radian / 2) - radiusIn * sin(radian)),
            (radius - radius * sin(radian / 2)),
        );
        path.lineTo((radius * cos(radian / 2)), 0.0);
        return path;
    }

    @override
    void paint(Canvas canvas, Size size) {
        Paint paint = Paint();
        paint.strokeWidth = strokeWidth;
        paint.color = color;
        paint.style = style;
        Path path = Path();
        double offset = strokeWidth > 0 ? strokeWidth + 2 : 0.0;
        
        path = createStarPath(this.size - offset, path);
        for (int i = 0; i < count - 1; i++) {
            path = path.shift(Offset(this.size * 2, 0.0));
            path = createStarPath(this.size - offset, path);
        }
    
        if (offset > 0) {
            path = path.shift(Offset(offset, offset));
        }
        path.close();
        canvas.drawPath(path, paint);
    }
    
    @override
    bool shouldRepaint(_PainterStars oldDelegate) {
        return oldDelegate.size != this.size;
    }
}
複製程式碼

程式碼地址

github.com/smiling1990…

相關文章