Update: Extension on the Canvas, compacted code, alignment
DartPad example
gist: rotated_text.dart
Example:
canvas.drawRotatedText(
pivot: pivot,
textPainter: mainTextPainter,
superTextPainter: superTextPainter,
subTextPainter: subTextPainter,
angle: angle, //radians
isInDegrees: false,
alignment: alignment);
The alignment of the main text can be adjusted with respect to the pivot point.
Obsolete: Rotate text, subscript and superscript
Image: Text with subscript and superscript
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double _counter = 0;
void _incrementCounter() {
setState(() {
_counter += 0.1;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CustomPaint(
painter: CustomText(multiplier: _counter),
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class CustomText extends CustomPainter {
CustomText({required this.multiplier});
double multiplier;
// Text centre about which the text should rotate
final textCentre = const Offset(0, 0);
// Angle of rotation
double get theta => -math.pi * multiplier;
// Text styles
double textFontSize = 24;
double get subTextFontSize => textFontSize / 2;
double get supTextFontSize => textFontSize / 2;
TextStyle get textStyle => TextStyle(
color: Colors.red, fontSize: textFontSize, fontStyle: FontStyle.italic);
TextStyle get subTextStyle => TextStyle(
color: Colors.green,
fontSize: subTextFontSize,
fontStyle: FontStyle.italic);
TextStyle get supTextStyle => TextStyle(
color: Colors.cyan,
fontSize: subTextFontSize,
fontStyle: FontStyle.italic);
@override
void paint(Canvas canvas, Size size) {
//
// MAIN TEXT //
final textPainter = TextPainter(
text: TextSpan(text: 'Example text', style: textStyle),
textDirection: TextDirection.ltr,
);
textPainter.layout();
// Calculate delta offset with reference to which any text should
// paint, such that the centre of the text will always be
// at the given textCentre
final delta = Offset(textCentre.dx - textPainter.size.width / 2,
textCentre.dy - textPainter.size.height / 2);
// Rotate the text about textCentrePoint
canvas.save();
canvas.translate(textCentre.dx, textCentre.dy);
canvas.rotate(theta);
canvas.translate(-textCentre.dx, -textCentre.dy);
textPainter.paint(canvas, delta);
canvas.restore();
//
// SUBSCRIPT TEXT //
final subTextPainter = TextPainter(
text: TextSpan(
text: 'subscript',
style: subTextStyle,
),
textDirection: TextDirection.ltr,
);
subTextPainter.layout();
// Position of top left point of the subscript text
final deltaSubtextDx = textCentre.dx +
// Cos
math.cos(theta) * (textPainter.size.width / 2) +
// Sine
math.sin(theta) *
(-textPainter.size.height / 2 + subTextPainter.size.height / 2);
final deltaSubtextDy = textCentre.dy +
// Cos
math.cos(theta) *
(textPainter.size.height / 2 - subTextPainter.size.height / 2) +
// Sine
math.sin(theta) * (textPainter.size.width / 2);
final deltaSubText = Offset(deltaSubtextDx, deltaSubtextDy);
// Rotate the text about textCentrePoint
canvas.save();
canvas.translate(deltaSubText.dx, deltaSubText.dy);
canvas.rotate(theta);
canvas.translate(-deltaSubText.dx, -deltaSubText.dy);
subTextPainter.paint(canvas, deltaSubText);
canvas.restore();
//
// SUPERSCRIPT TEXT //
final supTextPainter = TextPainter(
text: TextSpan(
text: 'superscript',
style: supTextStyle,
),
textDirection: TextDirection.ltr,
);
supTextPainter.layout();
// Position of top left point of the superscript text
final deltaSuptextDx = textCentre.dx +
// Cos
math.cos(theta) * (textPainter.size.width / 2) +
// Sine
math.sin(theta) *
(textPainter.size.height / 2 + supTextPainter.size.height / 2);
final deltaSuptextDy = textCentre.dy +
// Cos
math.cos(theta) *
(-textPainter.size.height / 2 - supTextPainter.size.height / 2) +
// Sine
math.sin(theta) * (textPainter.size.width / 2);
final deltaSupText = Offset(deltaSuptextDx, deltaSuptextDy);
// Rotate the text about textCentrePoint
canvas.save();
canvas.translate(deltaSupText.dx, deltaSupText.dy);
canvas.rotate(theta);
canvas.translate(-deltaSupText.dx, -deltaSupText.dy);
supTextPainter.paint(canvas, deltaSupText);
canvas.restore();
//
// Centre point marker
final pointPaint = Paint()..color = Colors.blue;
canvas.drawCircle(textCentre, 4, pointPaint);
// Subscript point marker
final pointPaint2 = Paint()..color = Colors.orange;
canvas.drawCircle(deltaSubText, 4, pointPaint2);
// Superscript point marker
final pointPaint3 = Paint()..color = Colors.brown;
canvas.drawCircle(deltaSupText, 4, pointPaint3);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Original answer
pskink's answer is modified to solve an issue that the text shifts in global X direction if the number of characters are changed.
Image: Long text
Image: Short text
So to position and then rotate a dynamic text I have slightly modified the code such that the text is rotated about a given center point.
Red dot is at the center point.
Image: Text rotated about given offset/center point
If you want to rotate text about it's bottom center point then just add new line character at the end in the text String.
Image: Rotated about bottom center of the text
class NewPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
var text = 'You have pushed the button this many times:\n'; //Remove \n if you don't want to rotate about bottom centre
// I want my text centred at this point
const textCentrePoint = Offset(200, 300);
const textStyle1 = TextStyle(color: Colors.black, fontSize: 20);
final pointPaint = Paint()..color = const Color.fromARGB(255, 255, 0, 0);
// Rotated text
final textPainter1 = TextPainter(
text: TextSpan(text: text, style: textStyle1),
textDirection: TextDirection.ltr,
);
textPainter1.layout();
// Calculate delta offset with reference to which any text should
// paint, such that the centre of the text will be
// at the given textCentrePoint
final delta = Offset(textCentrePoint.dx - textPainter1.size.width / 2,
textCentrePoint.dy - textPainter1.size.height / 2);
// Rotate the text about textCentrePoint
canvas.save();
canvas.translate(textCentrePoint.dx, textCentrePoint.dy);
canvas.rotate(-pi / 2);
canvas.translate(-textCentrePoint.dx, -textCentrePoint.dy);
textPainter1.paint(canvas, delta);
canvas.restore();
canvas.drawCircle(textCentrePoint, 4, pointPaint);
// Normal horizontal text
const textStyle2 =
TextStyle(color: Color.fromARGB(255, 139, 0, 0), fontSize: 20);
final textPainter2 = TextPainter(
text: TextSpan(text: text, style: textStyle2),
textDirection: TextDirection.ltr,
);
textPainter2.layout();
textPainter2.paint(canvas, delta);
}