Skip to content

Instantly share code, notes, and snippets.

@daohoangson
Created January 27, 2021 16:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daohoangson/17ab33dcc2c21b3e2b2864bccf3d4279 to your computer and use it in GitHub Desktop.
Save daohoangson/17ab33dcc2c21b3e2b2864bccf3d4279 to your computer and use it in GitHub Desktop.
Flutter demo with TextPainter and PageView
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'alice_in_wonderland.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Home(),
);
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pager Demo'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: SafeArea(
child: LayoutBuilder(
builder: (context, bc) => Pager(
size: bc.biggest,
// https://gist.githubusercontent.com/phillipj/4944029/raw/75ba2243dd5ec2875f629bf5d79f6c1e4b5a8b46/alice_in_wonderland.txt
text: kText
.replaceAll('\n\n ', 'xXx')
.replaceAll('\n', ' ')
.replaceAll('xXx', '\n\n'),
),
),
),
),
);
}
}
class Pager extends StatefulWidget {
final Size size;
final String text;
const Pager({Key key, this.size, this.text}) : super(key: key);
@override
State<Pager> createState() => _PagerState();
}
class _PagerState extends State<Pager> {
final currentPage = ValueNotifier(1);
final indeces = <int>[];
@override
void didChangeDependencies() {
super.didChangeDependencies();
_calculate();
}
@override
void didUpdateWidget(covariant Pager oldWidget) {
super.didUpdateWidget(oldWidget);
_calculate();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Stack(
children: [
PageView.builder(
itemBuilder: (context, index) {
final x2 = index * 2;
return Text(widget.text.substring(indeces[x2], indeces[x2 + 1]));
},
itemCount: indeces.length ~/ 2,
onPageChanged: (page) => currentPage.value = page + 1,
),
Positioned(
bottom: 16,
left: 0,
right: 0,
child: Center(
child: Container(
color: theme.disabledColor,
padding: const EdgeInsets.all(8.0),
child: AnimatedBuilder(
animation: currentPage,
builder: (_, __) => Text(
'${currentPage.value} / ${indeces.length ~/ 2}',
style: TextStyle(color: theme.colorScheme.onBackground),
),
),
),
),
),
],
);
}
void _calculate() {
final stopwatch = Stopwatch()..start();
indeces
..clear()
..addAll(Splitter(
style: DefaultTextStyle.of(context).style,
textFull: widget.text,
textDirection: Directionality.of(context),
textScaleFactor: MediaQuery.of(context).textScaleFactor,
viewport: Size(widget.size.width, widget.size.height),
).split(0));
print(stopwatch.elapsed);
}
}
class Splitter {
final TextStyle style;
final String textFull;
final TextDirection textDirection;
final double textScaleFactor;
final Size viewport;
Splitter({
this.style,
this.textFull,
this.textDirection,
this.textScaleFactor,
this.viewport,
});
List<int> split([int startIndex = 0]) {
final text = textFull.substring(startIndex);
final size0 = _layout(text);
if (size0.height <= viewport.height) {
// text fits viewport, no chopping required
return [startIndex, startIndex + text.length];
}
final newLineOffset = _fit(text, '\n');
final spaceOffset = _fit(text, ' ', newLineOffset ?? 0);
final offset = spaceOffset ?? newLineOffset;
if (offset != null) {
return [
startIndex,
startIndex + offset,
...split(startIndex + offset + 1),
];
} else {
// couldn't split in anyway... Just take the entire text
return [startIndex, startIndex + text.length];
}
}
int _fit(String text, Pattern pattern, [int offset = 0]) {
int goodOffset;
while (true) {
offset = text.indexOf(pattern, offset + 1);
if (offset == -1) {
// couldn't fit
return null;
}
final size = _layout(text.substring(0, offset));
if (size.height < viewport.height) {
goodOffset = offset;
} else {
return goodOffset;
}
}
}
Size _layout(String text) {
final painter = TextPainter(
text: TextSpan(text: text, style: style),
textDirection: textDirection,
textScaleFactor: textScaleFactor,
)..layout(
minWidth: viewport.width,
maxWidth: viewport.width,
);
return painter.size;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment