是那种比较好看的那种,欢迎欣赏。

本头条核心宗旨

欢迎来到「技术刚刚好」作者,「技术刚刚好」是个人维护,每天至少更新一篇Flutter技术文章,实时为大家播报Flutter最新消息。如果你刚好也在关注Flutter这门技术,那就跟我一起学习进步吧,你的赞,收藏,转发是对我个人最大的支持,维护不易,欢迎关注。

技术刚刚好经历

近几年,移动端跨平台开发技术层出不穷,从Facebook家的ReactNative,到阿里家WEEX,前端技术在移动端跨平台开发中大展身手,技术刚刚好作为一名Android开发,经历了从Reactjs到Vuejs的不断学习。而在2018年,我们的主角变成了Flutter,这是Goolge开源的一个移动端跨平台解决方案,可以快速开发精美的移动App。希望跟大家一起学习,一起进步!

本文核心要点

做Flutter开发这么久,要学习的东西还是挺多的,一个文本组件的实现应该很简单,但是配合一个圆形的组件应该怎么实现了?今天就带大家看看圆形文本组件怎么实行,而且还有动画的实现。


flutter做悬浮窗口(Flutter实现圆形文本带动画的那种)(1)

圆形组件带文本

CircularText代码

CircularText是一个核心的实现代码,全部贴出来。

import 'dart:math'; import 'package:flutter/material.dart'; enum CircularTextDirection { clockwise, anticlockwise } enum CircularTextPosition { outside, inside } class CircularText extends StatelessWidget { //Text final String text; //Text style final TextStyle textStyle; //Circle radius final double radius; //Spacing between characters final double spacing; //Text starting position final double startAngle; //Background shape paint final Paint backgroundPaint; //Text position either outside or inside circle final CircularTextPosition position; //Text direction either clockwise or anticlockwise final CircularTextDirection direction; CircularText( {Key key, @required this.text, this.textStyle = const TextStyle(), this.radius = 125, this.spacing = 10, this.startAngle = 0, Paint backgroundPaint, this.position = CircularTextPosition.inside, this.direction = CircularTextDirection.clockwise}) : assert(text != null), assert(textStyle != null), assert(radius != null && radius >= 0), assert(spacing != null && spacing >= 0), assert(startAngle != null && startAngle >= 0), this.backgroundPaint = backgroundPaint ?? (backgroundPaint = Paint()..color = Colors.transparent), assert(position != null), assert(direction != null), super(key: key); @override Widget build(BuildContext context) { return FittedBox( child: SizedBox.fromSize( size: Size(2 * radius, 2 * radius), child: CustomPaint( painter: CircularTextPainter( text: text, textStyle: textStyle, spacing: spacing, startAngle: startAngle, backgroundPaint: backgroundPaint, position: position, direction: direction), ), ), ); } } //要实现自定义画图器,可以子类化或实现此接口来定义您的自定义画图委托。 //CustomPaint子类必须实现paint和shouldRepaint方法, //还可以选择实现hitTest和shouldRebuildSemantics方法以及 语义Builder获取器。 class CircularTextPainter extends CustomPainter { final String text; final TextStyle textStyle; final double spacing; final double startAngle; final Paint backgroundPaint; final CircularTextPosition position; final CircularTextDirection direction; double _radius = 0.0; List<TextPainter> _charPainters = []; CircularTextPainter( {this.text, this.textStyle, this.spacing, this.startAngle, this.backgroundPaint, this.position, this.direction}) { for (final char in text.toUpperCase().split("")) { final tp = TextPainter( text: TextSpan(text: char, style: textStyle), textDirection: TextDirection.ltr); tp.layout(); _charPainters.add(tp); } } @override void paint(canvas canvas, Size size) { _radius = min(size.width / 2, size.height / 2); canvas.translate(size.width / 2, size.height / 2); canvas.drawCircle(Offset.zero, _radius, backgroundPaint); if (direction == CircularTextDirection.clockwise) { _paintTextClockwise(canvas, size); } else { _paintTextAntiClockwise(canvas, size); } } void _paintTextClockwise(Canvas canvas, Size size) { bool hasStrokeStyle = backgroundPaint.style == PaintingStyle.stroke && backgroundPaint.strokeWidth > 0.0; canvas.rotate((startAngle - 90) * pi / 180); for (int i = 0; i < _charPainters.length; i ) { final tp = _charPainters[i]; final x = -tp.width / 2; final y = position == CircularTextPosition.outside ? (-_radius - tp.height) - (hasStrokeStyle ? backgroundPaint.strokeWidth / 2 : 0.0) : -_radius - (hasStrokeStyle ? tp.height / 2 : 0.0); tp.paint(canvas, Offset(x, y)); canvas.rotate(spacing * pi / 180); } } void _paintTextAntiClockwise(Canvas canvas, Size size) { bool hasStrokeStyle = backgroundPaint.style == PaintingStyle.stroke && backgroundPaint.strokeWidth > 0.0; canvas.rotate((-startAngle - 270) * pi / 180); for (int i = 0; i < _charPainters.length; i ) { final tp = _charPainters[i]; final x = -tp.width / 2; final y = position == CircularTextPosition.outside ? _radius (hasStrokeStyle ? backgroundPaint.strokeWidth / 2 : 0.0) : (_radius - tp.height) (hasStrokeStyle ? tp.height / 2 : 0.0); tp.paint(canvas, Offset(x, y)); canvas.rotate(-spacing * pi / 180); } } @override bool shouldRepaint(CircularTextPainter oldDelegate) => oldDelegate.text != text || oldDelegate.textStyle != textStyle || oldDelegate.spacing != spacing || oldDelegate.startAngle != startAngle || oldDelegate.backgroundPaint != backgroundPaint || oldDelegate.position != position || oldDelegate.direction != direction; }

启动类main

import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_circular_text/circular_text.dart'; void main() { runApp(MaterialApp(home: CircularTextDemo())); SystemChrome.setEnabledSystemUIOverlays([]); } class CircularTextDemo extends StatefulWidget { @override _CircularTextDemoState createState() => _CircularTextDemoState(); } class _CircularTextDemoState extends State<CircularTextDemo> { double _spacing = 10; double _startAngle = 0; double _strokeWidth = 0.0; bool _showStroke = false; bool _showBackground = true; CircularTextPosition _position = CircularTextPosition.inside; CircularTextDirection _direction = CircularTextDirection.clockwise; @override Widget build(BuildContext context) { return Material( child: Container( color: Colors.white, alignment: Alignment.center, padding: EdgeInsets.only(left: 10, right: 10, top: 50, bottom: 10), child: SizedBox.fromSize( size: Size(360, double.infinity), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ buildCircularTextWidget(), SizedBox.fromSize(size: Size(40, 40)), Expanded( flex: 1, child: CustomScrollView( slivers: <Widget>[ buildSpacingPanel(), buildStartAnglePanel(), buildStrokeWidthPanel(), buildShowBackgroundShapePanel(), buildCircularTextPositionPanel(), buildCircularTextDirectionPanel(), ], ), ), ], ), ), ), ); } Widget buildCircularTextWidget() { final backgroundPaint = Paint(); if (_showBackground) { backgroundPaint..color = Colors.grey.shade200; if (_showStroke) { backgroundPaint ..color = Colors.grey.shade200 ..style = PaintingStyle.stroke ..strokeWidth = _strokeWidth; } } else { backgroundPaint.color = Colors.transparent; } return CircularText( text: "circular text widget", textStyle: TextStyle( fontSize: 25, color: Colors.blue, fontWeight: FontWeight.bold), radius: 125, spacing: _spacing, startAngle: _startAngle, backgroundPaint: backgroundPaint, position: _position, direction: _direction, ); } Widget buildSpacingPanel() { return SliverToBoxAdapter( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text("SPACING", style: TextStyle(fontWeight: FontWeight.bold)), Slider( value: _spacing, min: 0, max: 30, onChanged: (value) { setState(() => _spacing = value); }, ) ], ), ); } Widget buildStartAnglePanel() { return SliverToBoxAdapter( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text("START ANGLE", style: TextStyle(fontWeight: FontWeight.bold)), Slider( value: _startAngle, min: 0, max: 360, onChanged: (value) { setState(() => _startAngle = value); }, ) ], ), ); } Widget buildStrokeWidthPanel() { return SliverToBoxAdapter( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text("STROKE WIDTH", style: TextStyle(fontWeight: FontWeight.bold)), Slider( value: _strokeWidth, min: 0, max: 100, onChanged: _showStroke ? (value) { setState(() => _strokeWidth = value); } : null, ), Checkbox( value: _showStroke, onChanged: (value) { setState(() => _showStroke = value); }, ) ], ), ); } Widget buildShowBackgroundShapePanel() { return SliverToBoxAdapter( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text("SHOW BACKGROUND", style: TextStyle(fontWeight: FontWeight.bold)), Checkbox( value: _showBackground, onChanged: (value) { setState(() => _showBackground = value); }, ) ], ), ); } Widget buildCircularTextPositionPanel() { return SliverToBoxAdapter( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text("POSITION", style: TextStyle(fontWeight: FontWeight.bold)), DropdownButton<CircularTextPosition>( value: _position, items: [ DropdownMenuItem( child: Text("INSIDE"), value: CircularTextPosition.inside, ), DropdownMenuItem( child: Text("OUTSIDE"), value: CircularTextPosition.outside, ) ], onChanged: (value) { setState(() => _position = value); }, ) ], ), ); } Widget buildCircularTextDirectionPanel() { return SliverToBoxAdapter( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text("DIRECTION", style: TextStyle(fontWeight: FontWeight.bold)), DropdownButton<CircularTextDirection>( value: _direction, items: [ DropdownMenuItem( child: Text("CLOCKWISE"), value: CircularTextDirection.clockwise, ), DropdownMenuItem( child: Text("ANTI CLOCKWISE"), value: CircularTextDirection.anticlockwise, ) ], onChanged: (value) { setState(() => _direction = value); }, ) ], ), ); } }

总结

代码实现比较简单,逻辑也比较清楚。没啥说的,点赞,转发,收藏吧。

谢谢观看技术刚刚好的文章技术刚刚好是个人维护,每天至少更新一篇Flutter技术文章,实时为大家播报Flutter最新消息。如果你刚好也在关注Flutter这门技术,那就跟我一起学习进步吧,你的赞,收藏,转发是对我个人最大的支持,维护不易,欢迎关注。

,