是那种比较好看的那种,欢迎欣赏。 欢迎来到「技术刚刚好」作者,「技术刚刚好」是个人维护,每天至少更新一篇Flutter技术文章,实时为大家播报Flutter最新消息。如果你刚好也在关注Flutter这门技术,那就跟我一起学习进步吧,你的赞,收藏,转发是对我个人最大的支持,维护不易,欢迎关注。 近几年,移动端跨平台开发技术层出不穷,从Facebook家的ReactNative,到阿里家WEEX,前端技术在移动端跨平台开发中大展身手,技术刚刚好作为一名Android开发,经历了从Reactjs到Vuejs的不断学习。而在2018年,我们的主角变成了Flutter,这是Goolge开源的一个移动端跨平台解决方案,可以快速开发精美的移动App。希望跟大家一起学习,一起进步! 做Flutter开发这么久,要学习的东西还是挺多的,一个文本组件的实现应该很简单,但是配合一个圆形的组件应该怎么实现了?今天就带大家看看圆形文本组件怎么实行,而且还有动画的实现。 CircularText代码 CircularText是一个核心的实现代码,全部贴出来。 启动类main 代码实现比较简单,逻辑也比较清楚。没啥说的,点赞,转发,收藏吧。 谢谢观看技术刚刚好的文章,技术刚刚好是个人维护,每天至少更新一篇Flutter技术文章,实时为大家播报Flutter最新消息。如果你刚好也在关注Flutter这门技术,那就跟我一起学习进步吧,你的赞,收藏,转发是对我个人最大的支持,维护不易,欢迎关注。本头条核心宗旨
技术刚刚好经历
本文核心要点
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;
}
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);
},
)
],
),
);
}
}
总结