当前位置: 代码迷 >> 综合 >> Flutter 浅析之 自定义view 五 绘制标签
  详细解决方案

Flutter 浅析之 自定义view 五 绘制标签

热度:77   发布时间:2023-09-28 22:41:34.0

技术无止境,只怕不学习啊,Flutter 我们开始吧

有时候会遇到展示一些标签,最近项目中也遇到做一个标签标记,电商项目中多数都会用到,可能都是UI切的图,这里我们用自定义view 的方式来画一个标签
Flutter 浅析之 自定义view 五 绘制标签

Flutter 浅析之 自定义view 五 绘制标签
自定义LabelView

首先还是先建立类继承于CustomPainter

class LabelViewPainter extends CustomPainter {
    @overridevoid paint(Canvas canvas, Size size) {
    }@overridebool shouldRepaint(CustomPainter oldDelegate) {
    return false;}
}

由于这个LabelView是一个不算很规则的图形,所以用 canvas.drawPath()方法来实现比较合适。
接下来我们先来完成一个简单的绘制,先实现左上角的三角形效果
在这里我们先假设这个label的边长为100
Flutter 浅析之 自定义view 五 绘制标签

class LabelViewPainter extends CustomPainter {
    var _paint;LabelViewPainter() {
    _paint = new Paint()..color = Colors.redAccent..strokeCap = StrokeCap.round..isAntiAlias = true..style = PaintingStyle.fill..strokeWidth = 5.0;}@overridevoid paint(Canvas canvas, Size size) {
    Path path = new Path();path.lineTo(100, 0);path.lineTo(0, 100);path.close();canvas.drawPath(path, _paint);}@overridebool shouldRepaint(CustomPainter oldDelegate) {
    return false;}}

可以看到使用Path构建了一个三角形的区域,并设置画笔的绘制风格为fill
当然,也可以改变这个label显示位置

右上角:
Flutter 浅析之 自定义view 五 绘制标签

@overridevoid paint(Canvas canvas, Size size) {
    Path path = new Path();path.moveTo(450, 0);path.lineTo(450, 100);path.lineTo(300, 0);path.close();canvas.drawPath(path, _paint);}

左下角:

Flutter 浅析之 自定义view 五 绘制标签

 @overridevoid paint(Canvas canvas, Size size) {
    Path path = new Path();path.moveTo(0, 620);path.lineTo(100, 620);path.lineTo(0, 510);path.close();canvas.drawPath(path, _paint);}

右下角:
Flutter 浅析之 自定义view 五 绘制标签

@overridevoid paint(Canvas canvas, Size size) {
    Path path = new Path();path.moveTo(450, 620);path.lineTo(450, 520);path.lineTo(350, 620);path.close();canvas.drawPath(path, _paint);}

处理参数传入
上面绘制的label的颜色和大小都是写在自定义的View里面的,包括标签显示的位置也都是默认写在左上角的。
接下来尝试把这些属性由外部传入进来。
首先新建类LabelAlignment来标注Label的方向

常量名 作用
leftTop 显示在左上角
leftBottom 显示在左下角
rightTop 显示在右上角
rightBottom 显示在右下角
class LabelAlignment {
    int labelAlignment;LabelAlignment(this.labelAlignment);static const leftTop = 0;static const leftBottom = 1;static const rightTop = 2;static const rightBottom = 3;
}

然后让自定义的LabelViewPainter的构造方法传入这个参数。
同样的让LabelViewPainter的构造方法中传入label的颜色,方便绘制

@override
void paint(Canvas canvas, Size size) {
    }

使用paint传入的size属性来获取label的尺寸。
在这里取size的宽和高最小值的二分之一为label的边长。

var drawSize = size.height > size.width ? size.width / 2 : size.height / 2;

然后根据传入的LabelAlignment类型来绘制不同方向的label即可。
改过的代码:

void main() {
    runApp(MyApp());
}class MyApp extends StatelessWidget {
    // This widget is the root of your application.@overrideWidget build(BuildContext context) {
    return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.red, // 风格颜色visualDensity: VisualDensity.adaptivePlatformDensity,),routes:{
    "new_page":(context) => NewRoute(),},home: MyHomePage(title: '测试项目'),);}
}class MyHomePage extends StatefulWidget {
    final String title;MyHomePage({
    Key key, this.title}) : super(key: key);@override_MyHomePageState createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {
    @overrideWidget build(BuildContext context) {
    // TODO: implement buildvar paint = CustomPaint(size: Size(300,300),// 传入参数 颜色和方向painter: LabelViewPainter(Colors.redAccent,LabelAlignment.leftBottom),);return Scaffold(appBar: AppBar(title: Text( "Canvas的用法"),),body: Container(width: 600,height: 200,color: Colors.grey,child: paint,),);}
}

LabelViewPainter:


class LabelViewPainter extends CustomPainter {
    var _paint;var labelColor;var labelAlignment;LabelViewPainter(this.labelColor, this.labelAlignment) {
    _paint = new Paint()..color = Colors.redAccent..strokeCap = StrokeCap.round..isAntiAlias = true..style = PaintingStyle.fill..strokeWidth = 5.0;}@overridevoid paint(Canvas canvas, Size size) {
    var drawSize = size.height > size.width ? size.width / 2 : size.height / 2;Path path = new Path();switch (labelAlignment) {
    case LabelAlignment.leftTop:case LabelAlignment.leftTop:path.lineTo(0, drawSize);path.lineTo(drawSize, 0);break;case LabelAlignment.leftBottom:path.moveTo(0, size.height - drawSize);path.lineTo(drawSize, size.height);path.lineTo(0, size.height);break;case LabelAlignment.rightTop:path.moveTo(size.width - drawSize, 0);path.lineTo(size.width, 0);path.lineTo(size.width, drawSize);break;case LabelAlignment.rightBottom:path.moveTo(size.width, size.height);path.lineTo(size.width - drawSize, size.height);path.lineTo(size.width, size.height - drawSize);break;default:path.lineTo(0, drawSize);path.lineTo(drawSize, 0);break;}path.close();canvas.drawPath(path, _paint);}@overridebool shouldRepaint(CustomPainter oldDelegate) {
    return false;}
}class LabelAlignment {
    int labelAlignment;LabelAlignment(this.labelAlignment);static const leftTop = 0;static const leftBottom = 1;static const rightTop = 2;static const rightBottom = 3;
}

Flutter 浅析之 自定义view 五 绘制标签
可以看到,调用的地方传入color以及label方向即可调用LabelView,为了方便看出效果,给外层加了一个灰色的背景。

“去角”的Label
其实在上面代码的基础上实现起来就显得非常的简单,只需要根据显示的位置控制绘制的path即可。
为了方便使用,同样的给它新增了一个属性叫做“useAngle”默认让它是true,当它为false时显示去角的label效果。


class LabelViewPainter extends CustomPainter {
    var _paint; //画笔var labelColor; // 标签颜色var labelAlignment; // 标签方向类var useAngle;  // 是否去角LabelViewPainter(this.labelColor, this.labelAlignment,this.useAngle) {
    _paint = new Paint()..color = Colors.redAccent..strokeCap = StrokeCap.round..isAntiAlias = true..style = PaintingStyle.fill..strokeWidth = 5.0;}@overridevoid paint(Canvas canvas, Size size) {
    var drawSize = size.height > size.width ? size.width / 2 : size.height / 2;print(drawSize);Path path = new Path();switch (labelAlignment) {
    case LabelAlignment.leftTop:case LabelAlignment.leftTop:if(!useAngle){
    path.moveTo(drawSize/2, 0);path.lineTo(0, drawSize/2);}path.lineTo(0, drawSize);path.lineTo(drawSize, 0);break;case LabelAlignment.leftBottom:path.moveTo(0, size.height - drawSize);if(useAngle){
    path.lineTo(drawSize, size.height);path.lineTo(0, size.height);}else{
    path.lineTo(0, size.height-drawSize/2);path.lineTo(drawSize/2, size.height);path.lineTo(drawSize, size.height);}break;case LabelAlignment.rightTop:path.moveTo(size.width - drawSize, 0);if (useAngle) {
    path.lineTo(size.width, 0);}else{
    path.lineTo(size.width - drawSize / 2, 0);path.lineTo(size.width, drawSize / 2);}path.lineTo(size.width, drawSize);break;case LabelAlignment.rightBottom:if(useAngle){
    path.moveTo(size.width, size.height);path.lineTo(size.width - drawSize, size.height);path.lineTo(size.width, size.height - drawSize);}else{
    path.moveTo(size.width-drawSize, size.height);path.lineTo(size.width - drawSize/2, size.height);path.lineTo(size.width, size.height - drawSize/2);path.lineTo(size.width, size.height - drawSize);}break;default:if (!useAngle) {
    path.moveTo(drawSize/2, 0);path.lineTo(0, drawSize/2);}path.lineTo(0, drawSize);path.lineTo(drawSize, 0);break;}path.close();canvas.drawPath(path, _paint);}@overridebool shouldRepaint(CustomPainter oldDelegate) {
    return false;}
}

效果:
Flutter 浅析之 自定义view 五 绘制标签
给LabelView加上文字

绘制文字可以使用drawParagraph,要先获取到ParagraphBuilder文字相关的设置 ,通过ParagraphConstraints设置文本的宽度,然后设置build 和layout

 /// 绘制文字ParagraphBuilder pb = new ParagraphBuilder(ParagraphStyle(textAlign: TextAlign.left,fontWeight: FontWeight.w300,fontStyle: FontStyle.normal,fontSize: 15.0,));pb.pushStyle(ui.TextStyle(color: Colors.greenAccent));pb.addText("热销");// 设置文本的宽度约束ParagraphConstraints pc = ParagraphConstraints(width: 400);// 这里需要先layout,将宽度约束填入,否则无法绘制var  paragraph = pb.build()..layout(pc);

然后在canvas.drawParagraph绘制

 canvas.translate(drawSize/3-57, drawSize/2); // 位移canvas.rotate(-0.85);   // 旋转canvas.drawParagraph(paragraph, offset);

位移和旋转的角度,可以自行计算,这里是随便进行了绘制

Flutter 浅析之 自定义view 五 绘制标签
也可以使用Text添加文字,借助Transform.rotate()

Transform.rotate({
    Key key,@required double angle,//旋转角度this.origin,//起始位置this.alignment = Alignment.center,this.transformHitTests = true,Widget child,})

构造方法非常的简单,只需要计算出旋转的角度和起始位置的坐标即可,数学计算就不再具体讲了,由于这里没有计算文字的宽高所以可能会有一点的误差(原谅比较懒)
当然,这个计算肯定也是要根据你label显示的位置来算的。
所以,需要在LabelView的构造方法中再增加几个关于文字的属性,文字内容、文字风格
改动比较多,所以部分的代码都放了出来

class MyHomePage extends StatefulWidget {
    final Size size = Size(600,200);final String title;final labelAlignment = LabelAlignment.leftTop;MyHomePage({
    Key key, this.title}) : super(key: key);@override_MyHomePageState createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {
    ui.Image _image;static final double PI = 3.1415926;var textAngle;var textAlignment;var offset;@overridevoid initState() {
    super.initState();/*** 注意在初始化函数里面加加载图片* build 函数中把图片传给TestPainter对象*/load("images/timg.jpg").then((i) {
    setState(() {
    _image = i;});});}@overrideWidget build(BuildContext context) {
    // TODO: implement buildvar offsetX = widget.size.width > widget.size.height? widget.size.height / 4.5: widget.size.width / 4.5;switch (widget.labelAlignment) {
    case LabelAlignment.leftTop:offset = Offset(offsetX, -15);textAlignment = Alignment.topLeft;textAngle = -PI / 4;break;case LabelAlignment.rightTop:offset = Offset(-offsetX, 0);textAlignment = Alignment.topRight;textAngle = PI / 4;break;case LabelAlignment.leftBottom:offset = Offset(offsetX, 0);textAlignment = Alignment.bottomLeft;textAngle = PI / 4;break;case LabelAlignment.rightBottom:offset = Offset(-offsetX, 0);textAlignment = Alignment.bottomRight;textAngle = -PI / 4;break;}var paint = CustomPaint(size: Size(600,200),painter: LabelViewPainter(Colors.redAccent,LabelAlignment.leftTop,false),child: Align(   // 添加文字alignment: textAlignment,child: Transform.rotate(angle: textAngle,child: Text("TOP",style: TextStyle(color: Colors.greenAccent),),origin: offset,)),);return Scaffold(appBar: AppBar(title: Text( "Canvas的用法"),),body: Container(width: widget.size.width,height: widget.size.height,child: paint,color: Colors.grey,),);}
}

Flutter 浅析之 自定义view 五 绘制标签
包含其他child

LabView作为一个Widget只能显示不能组合其他Widget就不符合Flutter Widget设计的理念(组合大于继承)啊,所以我们的LabelView肯定也要支持组合其他的Widget的。
但是,看上面的代码可以发现CustomPaint的child已经被文字给占用了,但是现在还是需要在这个child里去放子Widget,怎么办?
肯定是要在这个child放置一个支持多child的Widget啊,想想也就那个几个

Widget 说明
ROW 横向显示
Colum 纵向显示
ListView 列表显示
Expanded 子Widget按照比例分布
Table 堆栈布局
IndexedStack 可控制堆栈布局

这里使用Stack是最好的,位于栈定的Widget会覆盖位于栈底的Widget。
所以,把用于组合的子child放在栈底(第一个位置,优先入栈),把自定义的LabelView放在第二个位置,这样就实现了LabelView覆盖在子child上的效果。

    return Scaffold(appBar: AppBar(title: Text( "Canvas的用法"),),body: Container(width: widget.size.width,height: widget.size.height,child:Stack(children: <Widget>[Image.asset("images/timg.jpg",height: 400,width: 500,fit: BoxFit.cover,alignment: Alignment.topLeft,),paint,],),),);

Flutter 浅析之 自定义view 五 绘制标签

本节就到这里 ,labelView实现的还是比较粗糙的,感兴趣的小伙伴可以继续去完善。

  相关解决方案