本文介紹flutter的剪裁(Clip,也能夠叫遮罩)的常見使用場景,以及使用剪裁(Clip)功能製做一款評分控件(Rating Bar)。git
今天發現flutter竟然連個評分控件都沒有,項目中要使用,因而研究了一番,順便把flutter中的Clip涉及到的幾個控件也看了下。
github
這個能夠用來剪裁圓形頭像canvas
new ClipOval( child: new SizedBox( width: 100.0, height:100.0, child: new Image.network("https://sfault-avatar.b0.upaiyun.com/206/120/2061206110-5afe2c9d40fa3_huge256",fit: BoxFit.fill,), ), ),
這個控件的borderRadius參數用於控制圓角的位置大小。less
new ClipRRect( borderRadius: new BorderRadius.all( new Radius.circular(10.0)), child: new SizedBox( width: 100.0, height:100.0, child: new Image.network("https://sfault-avatar.b0.upaiyun.com/206/120/2061206110-5afe2c9d40fa3_huge256",fit: BoxFit.fill,), ), )
這個控件須要定義clipper參數才能使用,否則沒有效果。ide
class _MyClipper extends CustomClipper<Rect>{ @override Rect getClip(Size size) { return new Rect.fromLTRB(10.0, 10.0, size.width - 10.0, size.height- 10.0); } @override bool shouldReclip(CustomClipper<Rect> oldClipper) { return true; } }
這裏定義剪裁掉周邊10像素的大小工具
new ClipRect( clipper: new _MyClipper(), child:new SizedBox( width: 100.0, height:100.0, child: new Image.network("https://sfault-avatar.b0.upaiyun.com/206/120/2061206110-5afe2c9d40fa3_huge256",fit: BoxFit.fill,), ) , ),
這個就比較有意思了,能夠剪裁任意形狀,好比五角星、三角形ui
class _StarCliper extends CustomClipper<Path>{ final double radius; _StarCliper({this.radius}); /// 角度轉弧度公式 double degree2Radian(int degree) { return (Math.pi * degree / 180); } @override Path getClip(Size size) { double radius = this.radius; Path path = new Path(); double radian = degree2Radian(36);// 36爲五角星的角度 double radius_in = (radius * Math.sin(radian / 2) / Math .cos(radian)); // 中間五邊形的半徑 path.moveTo((radius * Math.cos(radian / 2)), 0.0);// 此點爲多邊形的起點 path.lineTo((radius * Math.cos(radian / 2) + radius_in * Math.sin(radian)), (radius - radius * Math.sin(radian / 2))); path.lineTo((radius * Math.cos(radian / 2) * 2), (radius - radius * Math.sin(radian / 2))); path.lineTo((radius * Math.cos(radian / 2) + radius_in * Math.cos(radian / 2)), (radius + radius_in * Math.sin(radian / 2))); path.lineTo( (radius * Math.cos(radian / 2) + radius * Math.sin(radian)), (radius + radius * Math.cos(radian))); path.lineTo((radius * Math.cos(radian / 2)), (radius + radius_in)); path.lineTo( (radius * Math.cos(radian / 2) - radius * Math.sin(radian)), (radius + radius * Math.cos(radian))); path.lineTo((radius * Math.cos(radian / 2) - radius_in * Math.cos(radian / 2)), (radius + radius_in * Math.sin(radian / 2))); path.lineTo(0.0, (radius - radius * Math.sin(radian / 2))); path.lineTo((radius * Math.cos(radian / 2) - radius_in * Math.sin(radian)), (radius - radius * Math.sin(radian / 2))); path.close();// 使這些點構成封閉的多邊形 return path; } @override bool shouldReclip(_StarCliper oldClipper) { return this.radius != oldClipper.radius; } }
先定義好五角星的路徑ClipRect,而後:this
new ClipPath( clipper: new _StarCliper(radius: 50.0), child:new SizedBox( width: 100.0, height:100.0, child: new Image.network("https://sfault-avatar.b0.upaiyun.com/206/120/2061206110-5afe2c9d40fa3_huge256",fit: BoxFit.fill,), ) , )
咱們已經瞭解了Flutter中的剪裁,那麼製做一個評分控件已經很簡單了。
先準備兩個版本的五角星,一個用於高亮展現分數,通常是實心的,另外一個用於底圖,通常是空心的。使用矩形剪裁(ClipRect)對上一層的五角星進行寬度剪裁。spa
五角星能夠使用做圖工具作出來,也能夠採用自繪圖形。 .net
大概思路:
靜態展現控件StaticRatingBar
import 'package:flutter/widgets.dart'; import 'dart:math' as Math; const double kMaxRate = 5.0; const int kNumberOfStarts = 5; const double kSpacing = 3.0; const double kSize = 50.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({ double rate, Color colorLight, Color colorDark, int count, this.size: kSize, }) : rate = rate ?? kMaxRate, count = count ?? kNumberOfStarts, colorDark = colorDark ?? new Color(0xffeeeeee), colorLight = colorLight ?? new Color(0xffFF962E); Widget buildStar() { return new SizedBox( width: size * count, height: size, child: new CustomPaint( painter: new _PainterStars( size: this.size / 2, color: colorLight, style: PaintingStyle.fill, strokeWidth: 0.0), )); } Widget buildHollowStar() { return new SizedBox( width: size * count, height: size, child: new CustomPaint( painter: new _PainterStars( size: this.size / 2, color: colorDark, style: PaintingStyle.fill, strokeWidth: 0.0), )); } @override Widget build(BuildContext context) { return new Stack( children: <Widget>[ buildHollowStar(), new ClipRect( clipper: new _RatingBarClipper(width: rate * size), child: buildStar(), ) ], ); } } class _RatingBarClipper extends CustomClipper<Rect> { final double width; _RatingBarClipper({this.width}) : assert(width != null); @override Rect getClip(Size size) { return new Rect.fromLTRB(0.0, 0.0, width, size.height); } @override bool shouldReclip(_RatingBarClipper oldClipper) { return width != oldClipper.width; } } class _PainterStars extends CustomPainter { final double size; final Color color; final PaintingStyle style; final double strokeWidth; _PainterStars({this.size, this.color, this.strokeWidth, this.style}); /// 角度轉弧度公式 double degree2Radian(int degree) { return (Math.pi * degree / 180); } Path createStarPath(double radius, Path path) { double radian = degree2Radian(36); // 36爲五角星的角度 double radius_in = (radius * Math.sin(radian / 2) / Math.cos(radian)) * 1.1; // 中間五邊形的半徑,太正不是很好看,擴大一點點 path.moveTo((radius * Math.cos(radian / 2)), 0.0); // 此點爲多邊形的起點 path.lineTo((radius * Math.cos(radian / 2) + radius_in * Math.sin(radian)), (radius - radius * Math.sin(radian / 2))); path.lineTo((radius * Math.cos(radian / 2) * 2), (radius - radius * Math.sin(radian / 2))); path.lineTo( (radius * Math.cos(radian / 2) + radius_in * Math.cos(radian / 2)), (radius + radius_in * Math.sin(radian / 2))); path.lineTo((radius * Math.cos(radian / 2) + radius * Math.sin(radian)), (radius + radius * Math.cos(radian))); path.lineTo((radius * Math.cos(radian / 2)), (radius + radius_in)); path.lineTo((radius * Math.cos(radian / 2) - radius * Math.sin(radian)), (radius + radius * Math.cos(radian))); path.lineTo( (radius * Math.cos(radian / 2) - radius_in * Math.cos(radian / 2)), (radius + radius_in * Math.sin(radian / 2))); path.lineTo(0.0, (radius - radius * Math.sin(radian / 2))); path.lineTo((radius * Math.cos(radian / 2) - radius_in * Math.sin(radian)), (radius - radius * Math.sin(radian / 2))); path.lineTo((radius * Math.cos(radian / 2)), 0.0); return path; } @override void paint(Canvas canvas, Size size) { Paint paint = new Paint(); // paint.color = Colors.redAccent; paint.strokeWidth = strokeWidth; paint.color = color; paint.style = style; Path path = new Path(); double offset = strokeWidth > 0 ? strokeWidth + 2 : 0.0; path = createStarPath(this.size - offset, path); path = path.shift(new Offset(this.size * 2, 0.0)); path = createStarPath(this.size - offset, path); path = path.shift(new Offset(this.size * 2, 0.0)); path = createStarPath(this.size - offset, path); path = path.shift(new Offset(this.size * 2, 0.0)); path = createStarPath(this.size - offset, path); path = path.shift(new Offset(this.size * 2, 0.0)); path = createStarPath(this.size - offset, path); if (offset > 0) { path = path.shift(new Offset(offset, offset)); } path.close(); canvas.drawPath(path, paint); } @override bool shouldRepaint(_PainterStars oldDelegate) { return oldDelegate.size != this.size; } }
使用靜態顯示評分:
new StaticRatingBar( size: 20.0, rate: 4.5, )
效果:
動態評分控件:
class RatingBar extends StatefulWidget { /// 回調 final ValueChanged<int> onChange; /// 大小, 默認 50 final double size; /// 值 1-5 final int value; /// 數量 5 個默認 final int count; /// 高亮 final Color colorLight; /// 底色 final Color colorDark; /// 若是有值,那麼就是空心的 final double strokeWidth; /// 越大,五角星越圓 final double radiusRatio; RatingBar( {this.onChange, this.value, this.size: kSize, this.count: kNumberOfStarts, this.strokeWidth, this.radiusRatio: 1.1, Color colorDark, Color colorLight}) : colorDark = colorDark ?? new Color(0xffDADBDF), colorLight = colorLight ?? new Color(0xffFF962E); @override State<StatefulWidget> createState() { return new _RatingBarState(); } } class _PainterStar extends CustomPainter { final double size; final Color color; final PaintingStyle style; final double strokeWidth; final double radiusRatio; _PainterStar( {this.size, this.color, this.strokeWidth, this.style, this.radiusRatio}); @override void paint(Canvas canvas, Size size) { Paint paint = new Paint(); paint.strokeWidth = strokeWidth; paint.color = color; paint.style = style; Path path = new Path(); double offset = strokeWidth > 0 ? strokeWidth + 2 : 0.0; path = createStarPath(this.size - offset, radiusRatio, path); if (offset > 0) { path = path.shift(new Offset(offset, offset)); } path.close(); canvas.drawPath(path, paint); } @override bool shouldRepaint(_PainterStar oldDelegate) { return oldDelegate.size != this.size || oldDelegate.color != this.color || oldDelegate.strokeWidth != this.strokeWidth; } } class _RatingBarState extends State<RatingBar> { int _value; @override void initState() { _value = widget.value; super.initState(); } Widget buildItem(int index, double size, count) { bool selected = _value != null && _value > index; bool stroke = widget.strokeWidth != null && widget.strokeWidth > 0; return new GestureDetector( onTap: () { if (widget.onChange != null) { widget.onChange(index + 1); } setState(() { _value = index + 1; }); }, behavior: HitTestBehavior.opaque, child: new SizedBox( width: size, height: size, child: new CustomPaint( painter: new _PainterStar( radiusRatio: widget.radiusRatio, size: size / 2, color: selected ? widget.colorLight : widget.colorDark, style: !selected && stroke ? PaintingStyle.stroke : PaintingStyle.fill, strokeWidth: !selected && stroke ? widget.strokeWidth : 0.0), )), ); } @override Widget build(BuildContext context) { double size = widget.size; int count = widget.count; List<Widget> list = []; for (int i = 0; i < count; ++i) { list.add(buildItem(i, size, count)); } return new Row( children: list, ); } }
完整代碼這裏:
https://github.com/jzoom/flut...
若有疑問,請加qq羣854192563討論