资讯专栏INFORMATION COLUMN

flutter使用剪裁制作评分控件

li21 / 1054人阅读

摘要:本文介绍的剪裁,也可以叫遮罩的常见使用场景,以及使用剪裁功能制作一款评分控件。矩形剪裁这个控件需要定义参数才能使用,不然没有效果。使用矩形剪裁对上一层的五角星进行宽度剪裁。

本文介绍flutter的剪裁(Clip,也可以叫遮罩)的常见使用场景,以及使用剪裁(Clip)功能制作一款评分控件(Rating Bar)。

前言

今天发现flutter居然连个评分控件都没有,项目中要使用,于是研究了一番,顺便把flutter中的Clip涉及到的几个控件也看了下。

Flutter中的剪裁 圆形剪裁(ClipOval)

这个可以用来剪裁圆形头像

 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,),
    ),
  ),
圆角矩形剪裁(ClipRRect)

这个控件的borderRadius参数用于控制圆角的位置大小。

 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,),
    ),

  )
矩形剪裁(ClipRect)

这个控件需要定义clipper参数才能使用,不然没有效果。

class _MyClipper extends CustomClipper{
  @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 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,),
            ) ,
          ),
路径剪裁(ClipPath)

这个就比较有意思了,可以剪裁任意形状,比如五角星、三角形


class _StarCliper extends CustomClipper{

  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,然后:

 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,),
    ) ,

  )
评分控件(Rating Bar)的制作

我们已经了解了Flutter中的剪裁,那么制作一个评分控件已经很简单了。
先准备两个版本的五角星,一个用于高亮展示分数,一般是实心的,另一个用于底图,一般是空心的。使用矩形剪裁(ClipRect)对上一层的五角星进行宽度剪裁。

五角星可以使用作图工具做出来,也可以采用自绘图形。

大概思路:

静态展示控件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: [
        buildHollowStar(),
        new ClipRect(
          clipper: new _RatingBarClipper(width: rate * size),
          child: buildStar(),
        )
      ],
    );
  }
}

class _RatingBarClipper extends CustomClipper {
  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 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 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 {
  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 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讨论

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/17072.html

相关文章

  • flutter笔记7:flutter页面布局基础,看完这篇就可以用flutter写APP了

    摘要:布局控件不会直接呈现内容,可看作承载可视控件的容器。布局控件也是可以模拟显示的,通常用于调试布局样式时用到的网格线标尺动画帧等。但是当页面内容需要超出屏幕尺寸时,就用和代替。 不知不觉已经到了第7篇,然而很多萌新玩家可能还是不知道如何堆砌控件,像用CSS一样搭出漂亮的APP界面,我也一样,红红火火恍恍惚惚,直到今天含泪读完Flutter布局基础,仿佛打开了一个全新的世界。 基本概念 在...

    Flink_China 评论0 收藏0
  • flutter实战3:解析HTTP请求数据和制作新闻分类列表

    摘要:当我们搭建好了整个的页面框架,现在我往页里加点东西各种分类的新闻列表。由于需要请求外部数据,因此引入一个较为方便的库。于是乎,在初始化对象时发起请求应该是个不错的办法具体是怎么初始化数据的,第三步会讲到,踩了不少坑。 当我们搭建好了整个APP的页面框架,现在我往Tab页里加点东西:各种分类的新闻列表。也可以参考我的Git,上面有要点注释。 showImg(https://segment...

    BicycleWarrior 评论0 收藏0
  • Android 基本控件的常用属性

    摘要:保持原图的大小,显示在的中心,原图超过的部分剪裁。当原图宽高或等于的宽高时,按原图大小居中显示反之将原图等比例缩放至的宽高并居中显示。按比例拉伸图片,拉伸后图片的高度为的高度,且显示在的上边。TextView //normal 默认 bold 粗体 italic斜体 可用| 多选 android:textStyle //设置限定可以输入哪些字符 android:digits //设置文...

    URLOS 评论0 收藏0
  • Android程序员完全没时间提升自己怎么办?

    摘要:昨天有个小学弟给我发来微信,说他现在有点后悔选择开发了,月月光不说,还加班特别严重,平时也没有属于自己的时间去学习,问我刚毕业的时候是不是这样。每天回到出租屋都是倒头就睡,非常累,也没有其他时间提升自己的技术。 昨天有个小学弟给我发来微信,说他现在有点后悔选择Android开发了,月月光不说...

    kohoh_ 评论0 收藏0
  • flutter笔记3:基础语法、框架、控件

    摘要:是啥是谷歌推出的一套视觉设计语言。比如有的可以换皮肤,而每一套皮肤就是一种设计语言,有古典风呀炫酷风呀极简风呀神马的,而就是谷歌风,有兴趣的同学可以学习了解一下官方原版和中文翻译版,这是每一个产品经理的必修教材。 flutter环境和运行环境搭建好之后,可以开始撸码了,然而当你打开VScode,在打开项目文件夹后,摆在你面前的是main.dart被打开的样子,里面七七八八的已经写好了一...

    draveness 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<