Last active
August 11, 2022 11:09
-
-
Save Takhion/e48938fd865fc9be64ec28ad2f7063d0 to your computer and use it in GitHub Desktop.
[Flutter] TextSelectionLayout (see https://stackoverflow.com/a/46244263/1306903)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// animated GIF: https://i.stack.imgur.com/mHOIc.gif | |
// see https://stackoverflow.com/a/46244263/1306903 | |
import 'package:flutter/material.dart'; | |
import 'ink_text_selection_layout.dart'; | |
class Demo extends StatelessWidget { | |
@override | |
Widget build(context) => inkTextSelectionLayout( | |
richTextBuilder: (context) => new RichText( | |
key: key, | |
text: new TextSpan( | |
text: 'HELLO THIS IS MY ', | |
style: DefaultTextStyle.of(context).style, | |
children: <TextSpan>[ | |
new TextSpan( | |
text: 'LONG', | |
style: new TextStyle(fontWeight: FontWeight.bold)), | |
new TextSpan(text: ' SENTENCE'), | |
], | |
), | |
), | |
selections: <TextSelection>[ | |
const TextSelection( | |
baseOffset: 17, | |
extentOffset: 21, | |
), | |
], | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:flutter/foundation.dart'; | |
import 'text_selection_layout.dart'; | |
export 'text_selection_layout.dart'; | |
TextSelectionLayout inkTextSelectionLayout({ | |
Key key, | |
TextBuilder textBuilder, | |
RichTextBuilder richTextBuilder, | |
@required List<TextSelection> selections, | |
Rect rippleRectPadding, | |
BorderRadius rippleBorderRadius, | |
}) { | |
final padding = rippleRectPadding ?? new Rect.fromLTRB(8.0, 8.0, 8.0, 8.0); | |
final radius = rippleBorderRadius ?? kMaterialEdges[MaterialType.button]; | |
final WidgetBuilder inkBuilder = (_) => new InkWell( | |
borderRadius: radius, | |
onTap: () => {}, | |
); | |
final MergeTextBoxes mergeTextBoxes = padding == Rect.zero | |
? null | |
: (textBoxes) { | |
Rect rect = TextSelectionLayout.defaultMergeTextBoxes(textBoxes); | |
if (rect != Rect.zero) { | |
rect = new Rect.fromLTRB( | |
rect.left - padding.left, | |
rect.top - padding.top, | |
rect.right + padding.right, | |
rect.bottom + padding.bottom, | |
); | |
} | |
return rect; | |
}; | |
final data = selections | |
.map((selection) => new TextSelectionLayoutData( | |
selection: selection, | |
builder: inkBuilder, | |
mergeTextBoxes: mergeTextBoxes, | |
)) | |
.toList(growable: false); | |
return new TextSelectionLayout( | |
key: key, | |
textBuilder: textBuilder, | |
richTextBuilder: richTextBuilder, | |
data: data, | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
class KeyWrapper extends StatelessWidget { | |
const KeyWrapper({Key key, this.child}) : super(key: key); | |
final Widget child; | |
@override | |
Widget build(BuildContext context) => child; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
import 'package:flutter/foundation.dart'; | |
import 'dart:ui' show TextBox; | |
import 'dart:math'; | |
import 'key_wrapper.dart'; | |
typedef Text TextBuilder(BuildContext context); | |
typedef RichText RichTextBuilder(BuildContext context); | |
typedef Rect MergeTextBoxes(List<TextBox> textBoxes); | |
@immutable | |
class TextSelectionLayoutData { | |
/// See [TextSelectionLayout.defaultMergeTextBoxes] for the default | |
/// implementation of [mergeTextBoxes] (used in case it's `null`). | |
const TextSelectionLayoutData({ | |
@required this.selection, | |
@required this.builder, | |
this.mergeTextBoxes, | |
}); | |
final TextSelection selection; | |
final WidgetBuilder builder; | |
final MergeTextBoxes mergeTextBoxes; | |
} | |
/// Provides a convenient way to place widgets in front of sections of text. | |
class TextSelectionLayout extends StatelessWidget { | |
/// Must provide [data] and either [textBuilder] or [richTextBuilder]. | |
TextSelectionLayout({ | |
Key key, | |
TextBuilder textBuilder, | |
RichTextBuilder richTextBuilder, | |
@required this.data, | |
}) | |
: assert((textBuilder != null) != (richTextBuilder != null)), | |
textBuilder = textBuilder ?? richTextBuilder, | |
super(key: key); | |
final WidgetBuilder textBuilder; | |
final List<TextSelectionLayoutData> data; | |
final GlobalKey _textKey = new GlobalKey(); | |
/// Returns a [Rect] that wraps all the [textBoxes]. | |
static Rect defaultMergeTextBoxes(List<TextBox> textBoxes) => | |
textBoxes?.fold( | |
null, | |
(Rect previous, TextBox textBox) => new Rect.fromLTRB( | |
min(previous?.left ?? textBox.left, textBox.left), | |
min(previous?.top ?? textBox.top, textBox.top), | |
max(previous?.right ?? textBox.right, textBox.right), | |
max(previous?.bottom ?? textBox.bottom, textBox.bottom), | |
), | |
) ?? | |
Rect.zero; | |
@override | |
Widget build(context) => new Stack( | |
children: <Widget>[ | |
new KeyWrapper( | |
key: _textKey, | |
child: textBuilder(context), | |
), | |
new Positioned.fill( | |
child: new LayoutBuilder( | |
builder: (context, _) => new Stack( | |
children: _getChildren(context), | |
), | |
), | |
), | |
], | |
); | |
List<Positioned> _getChildren(BuildContext context) { | |
if (data == null) return null; | |
final RenderParagraph renderParagraph = | |
_textKey.currentContext.findRenderObject() as RenderParagraph; | |
return data.map((datum) { | |
final List<TextBox> textBoxes = | |
renderParagraph.getBoxesForSelection(datum.selection); | |
final textBoxesToRect = datum.mergeTextBoxes ?? defaultMergeTextBoxes; | |
final rect = textBoxesToRect(textBoxes) ?? Rect.zero; | |
return new Positioned.fromRect( | |
rect: rect, | |
child: datum.builder(context), | |
); | |
}).toList(growable: false); | |
} | |
} |
@Takhion did your code work for this issue also [https://github.com/flutter/flutter/issues/26581]
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@Takhion What license is this under? Thxs