How to highlight a word in string in flutter programmatically - dart

Is there any way to change color on particular word in a string ?
Text("some long string")
now i want to give color to only long word.
can someone tell me how can i do this programatically ?
eg:-
I am long a really long and long string in some variable, a long one
now here i want to seperate long word.
I can seperate simple string to highlight one word but not sure how to find and highlight each of these words.

Wrap the word in a TextSpan and assign style properties to change the text appearance and use RichText instead of Text
RichText(
text: TextSpan(
text: 'Hello ',
style: DefaultTextStyle.of(context).style,
children: <TextSpan>[
TextSpan(text: 'bold', style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(text: ' world!'),
],
),
)
or use the Text.rich constructor https://docs.flutter.io/flutter/widgets/Text-class.html
const Text.rich(
TextSpan(
text: 'Hello', // default text style
children: <TextSpan>[
TextSpan(text: ' beautiful ', style: TextStyle(fontStyle: FontStyle.italic)),
TextSpan(text: 'world', style: TextStyle(fontWeight: FontWeight.bold)),
],
),
)

Here is my code.
import 'package:flutter/material.dart';
class HighlightText extends StatelessWidget {
final String text;
final String highlight;
final TextStyle style;
final TextStyle highlightStyle;
final Color highlightColor;
final bool ignoreCase;
HighlightText({
Key key,
this.text,
this.highlight,
this.style,
this.highlightColor,
TextStyle highlightStyle,
this.ignoreCase: false,
}) : assert(
highlightColor == null || highlightStyle == null,
'highlightColor and highlightStyle cannot be provided at same time.',
),
highlightStyle = highlightStyle ?? style?.copyWith(color: highlightColor) ?? TextStyle(color: highlightColor),
super(key: key);
#override
Widget build(BuildContext context) {
final text = this.text ?? '';
if ((highlight?.isEmpty ?? true) || text.isEmpty) {
return Text(text, style: style);
}
var sourceText = ignoreCase ? text.toLowerCase() : text;
var targetHighlight = ignoreCase ? highlight.toLowerCase() : highlight;
List<TextSpan> spans = [];
int start = 0;
int indexOfHighlight;
do {
indexOfHighlight = sourceText.indexOf(targetHighlight, start);
if (indexOfHighlight < 0) {
// no highlight
spans.add(_normalSpan(text.substring(start)));
break;
}
if (indexOfHighlight > start) {
// normal text before highlight
spans.add(_normalSpan(text.substring(start, indexOfHighlight)));
}
start = indexOfHighlight + highlight.length;
spans.add(_highlightSpan(text.substring(indexOfHighlight, start)));
} while (true);
return Text.rich(TextSpan(children: spans));
}
TextSpan _highlightSpan(String content) {
return TextSpan(text: content, style: highlightStyle);
}
TextSpan _normalSpan(String content) {
return TextSpan(text: content, style: style);
}
}

Extending on zhpoo's awesome answer, here's a widget snippet that would allow you to style/highlight anything in a string programmatically using regular expressions (dart's RegExp).
Link to dartpad demo: https://dartpad.dev/d7a0826ed1201f7247fafd9e65979953
class RegexTextHighlight extends StatelessWidget {
final String text;
final RegExp highlightRegex;
final TextStyle highlightStyle;
final TextStyle nonHighlightStyle;
const RegexTextHighlight({
#required this.text,
#required this.highlightRegex,
#required this.highlightStyle,
this.nonHighlightStyle,
});
#override
Widget build(BuildContext context) {
if (text == null || text.isEmpty) {
return Text("",
style: nonHighlightStyle ?? DefaultTextStyle.of(context).style);
}
List<TextSpan> spans = [];
int start = 0;
while (true) {
final String highlight =
highlightRegex.stringMatch(text.substring(start));
if (highlight == null) {
// no highlight
spans.add(_normalSpan(text.substring(start)));
break;
}
final int indexOfHighlight = text.indexOf(highlight, start);
if (indexOfHighlight == start) {
// starts with highlight
spans.add(_highlightSpan(highlight));
start += highlight.length;
} else {
// normal + highlight
spans.add(_normalSpan(text.substring(start, indexOfHighlight)));
spans.add(_highlightSpan(highlight));
start = indexOfHighlight + highlight.length;
}
}
return RichText(
text: TextSpan(
style: nonHighlightStyle ?? DefaultTextStyle.of(context).style,
children: spans,
),
);
}
TextSpan _highlightSpan(String content) {
return TextSpan(text: content, style: highlightStyle);
}
TextSpan _normalSpan(String content) {
return TextSpan(text: content);
}
}

I recently developed a package called Dynamic Text Highlighting. It let's you to highlight programatically some given words inside a given text.
Take a look at https://pub.dev/packages/dynamic_text_highlighting
Example
Widget buildDTH(String text, List<String> highlights) {
return DynamicTextHighlighting(
text: text,
highlights: highlights,
color: Colors.yellow,
style: TextStyle(
fontSize: 18.0,
fontStyle: FontStyle.italic,
),
caseSensitive: false,
);
}
It is a stateless widget, so for any changes just call setState(() {...}).
void applyChanges(List<String> newHighlights) {
setState(() {
highlights = newHighlights;
});
}

Use this code it would even highlight the query letters, check once
List<TextSpan> highlight(
String main, String query) {
List<TextSpan> children = [];
List<String> abc = query.toLowerCase().split("");
for (int i = 0; i < main.length; i++) {
if (abc.contains(main[i])) {
children.add(TextSpan(
text: main[i],
style: TextStyle(
backgroundColor: Colors.yellow[300],
color: Colors.black,
decoration: TextDecoration.none,
fontFamily: fontName,
fontWeight: FontWeight.bold,
fontSize: 16)));
} else {
children.add(TextSpan(
text: main[i],
style: TextStyle(
color: Colors.black,
decoration: TextDecoration.none,
fontFamily: fontName,
fontWeight: FontWeight.w300,
fontSize: 16)));
}
}
return children;
}

To achieve this, there are several possibilities :
1- Using the Text.rich constructor instead of the Text widget and then inside the constructor, you will use the TextSpan widgets that will allow you to add style through the style property. The first TextSpan directly in Text.rich and then the other TextSpan in the first TextSpan through its children property.
Text.rich(
TextSpan(
text: 'Some ',
children: <TextSpan>[
TextSpan(
text: ' Long ',
style: TextStyle(fontWeight: FontWeight.bold , background: Paint()..color = Colors.red)),
TextSpan(
text: ' world',
style: TextStyle(backgroundColor: Colors.yellow)),
],
),
)
Output :
2- The use of RichText widget . Same as Text.rich but this time the first TextSpan will be put on the text property of the RichText widget.
RichText(
text:TextSpan(
text: 'Some ',
style: TextStyle(color: Colors.blue),
children: <TextSpan>[
TextSpan(
text: ' Long ',
style: TextStyle(color:Colors.black ,fontWeight: FontWeight.bold , background: Paint()..color = Colors.red)),
TextSpan(
text: ' world',
style: TextStyle(backgroundColor: Colors.yellow)),
],
),
)
Output :
3- The use of external packages .
I propose you highlight_text or substring_highlight

You can use this flutter plugin Highlight Text plugin. It is quite a good option to try

that's Good Answer #Gauter Zochbauer. if You want to change dynamically then Follow following answer.
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(
title: 'Forms in Flutter',
home: new LoginPage(),
));
class LoginPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => new _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
Color color =Colors.yellowAccent;
#override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
return new Scaffold(
appBar: new AppBar(
title: new Text('Login'),
),
body: new Container(
padding: new EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Text.rich(
TextSpan(
text: 'Hello', // default text style
children: <TextSpan>[
TextSpan(text: ' beautiful ', style: TextStyle(fontStyle: FontStyle.italic,color: color)),
TextSpan(text: 'world', style: TextStyle(fontWeight: FontWeight.bold)),
],
),
),
new RaisedButton(
onPressed: (){
setState(() {
color == Colors.yellowAccent ? color = Colors.red : color = Colors.yellowAccent;
});
},
child: Text("Click Me!!!")
),
],
)
));
}
}

Related

How can i hide day from cupertino date picker

i need to pic only year and month from the date picker, so how can i hide day from date picker.
CupertinoDatePicker(
initialDateTime: DateTime.now(),
onDateTimeChanged: (DateTime newdate) {
print(newdate);
widget.card.expDateTime = newdate.toString();
dateCnt.text = newdate.toString().split(" ")[0];
},
minimumYear: DateTime.now().year,
minimumDate: DateTime.now(),
mode: CupertinoDatePickerMode.date,
)
I had this problem, tried the package flutter_cupertino_date_picker but it looks like it don't have the hability to format only month and year excluding day, so you need to program on it to extend the capabilities. To me seemed more logical to change the build in CupertinoDatePicker that comes with Flutter, what I did was copy all the content of '/Users/your_user_name/developer/flutter/packages/flutter/lib/src/cupertino/date_picker.dart' in another file in my local envirronment, I called cupertino_picker_extended.dart, then (because I wanted a quick way) on line 1182 I changed: Text(localizations.datePickerDayOfMonth(day),... for Text('',...
Then where you need to use the customed Picker call it like:
import 'package:local_app/ui/widgets/cupertino_picker_extended.dart' as CupertinoExtended;
and use it:
CupertinoExtended.CupertinoDatePicker(
onDateTimeChanged: (DateTime value) {
setDate('${value.month}/${value.year}', setDateFunction,
section, arrayPos);
},
initialDateTime: DateTime.now(),
mode: CupertinoExtended.CupertinoDatePickerMode.date,
),
This is the result:
Hope it help someone and save the time I been looking for a easy and quick solution for my problem.
you have to look at flutter_cupertino_date_picker package this to do so. you can avoid picking date from user.
like below example i edited as you wish. i hope that it help you to achieve what you want.
import 'package:flutter/material.dart';
import 'package:flutter_cupertino_date_picker/flutter_cupertino_date_picker.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Date Picker Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _datetime = '';
int _year = 2018;
int _month = 11;
String _lang = 'en';
String _format = 'yyyy-mm';
bool _showTitleActions = true;
TextEditingController _langCtrl = TextEditingController();
TextEditingController _formatCtrl = TextEditingController();
#override
void initState() {
super.initState();
_langCtrl.text = 'zh';
_formatCtrl.text = 'yyyy-mm';
DateTime now = DateTime.now();
_year = now.year;
_month = now.month;
}
/// Display date picker.
void _showDatePicker() {
final bool showTitleActions = false;
DatePicker.showDatePicker(
context,
showTitleActions: _showTitleActions,
minYear: 1970,
maxYear: 2020,
initialYear: _year,
initialMonth: _month,
confirm: Text(
'custom ok',
style: TextStyle(color: Colors.red),
),
cancel: Text(
'custom cancel',
style: TextStyle(color: Colors.cyan),
),
locale: _lang,
dateFormat: _format,
onChanged: (year, month,date) {
debugPrint('onChanged date: $year-$month');
if (!showTitleActions) {
_changeDatetime(year, month);
}
},
onConfirm: (year, month,date) {
_changeDatetime(year, month);
},
);
}
void _changeDatetime(int year, int month) {
setState(() {
_year = year;
_month = month;
_datetime = '$year-$month';
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Date Picker Demo'),
),
body: Container(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: <Widget>[
// show title actions checkbox
Row(
children: <Widget>[
Text(
'Show title actions',
style: TextStyle(fontSize: 16.0),
),
Checkbox(
value: _showTitleActions,
onChanged: (value) {
setState(() {
_showTitleActions = value;
});
},
)
],
),
// Language input field
TextField(
controller: _langCtrl,
keyboardType: TextInputType.url,
decoration: InputDecoration(
labelText: 'Language',
hintText: 'en',
hintStyle: TextStyle(color: Colors.black26),
),
onChanged: (value) {
_lang = value;
},
),
// Formatter input field
TextField(
controller: _formatCtrl,
keyboardType: TextInputType.url,
decoration: InputDecoration(
labelText: 'Formatter',
hintText: 'yyyy-mm-dd / yyyy-mmm-dd / yyyy-mmmm-dd',
hintStyle: TextStyle(color: Colors.black26),
),
onChanged: (value) {
_format = value;
},
),
// Selected date
Container(
margin: EdgeInsets.only(top: 40.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'Selected Date:',
style: Theme.of(context).textTheme.subhead,
),
Container(
padding: EdgeInsets.only(left: 12.0),
child: Text(
'$_datetime',
style: Theme.of(context).textTheme.title,
),
),
],
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _showDatePicker,
tooltip: 'Show DatePicker',
child: Icon(Icons.date_range),
),
);
}
}
There has been updates on the flutter_cupertino_date_picker package. You can now specify a date format
DatePicker.showDatePicker(context,
maxDateTime: DateTime.now(),
dateFormat:'MMMM-yyyy'
);
This is the way to go
Widget datetime() {
return CupertinoDatePicker(
initialDateTime: DateTime.now(),
onDateTimeChanged: (DateTime newdate) {
print(newdate);
},
minimumYear: 2010,
maximumYear: 2030,
mode: CupertinoDatePickerMode.date,
);
}

Flutter : Refresh same screen with different data and back button

I have recently started exploring flutter few days back. I have created a list which has some rows.
Some rows has the Child data.
Right now screen has customised button on the top.
final topAppBar = AppBar(
elevation: 0.1,
backgroundColor: Color.fromRGBO(0, 113, 188, 1.0),
title: Text("RESOURCES", style: TextStyle(
color: Colors.white,
fontFamily: 'Raleway-ExtraBold',
fontWeight: FontWeight.w900,
fontSize: 20.0,
),),
leading: IconButton(
icon: new Image.asset('assets/images/settings.png'),
),
);
When user clicks on those rows I want to just refresh the list with child data and push effect with updating “back button” on the top.
The below code is able to navigate the screen with push effect but how can we maintain the state of application with data as well as back button.
ListTile makeResourcesListTile(Resources resources) => ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0.0),
title: Text(
resources.title,
style: TextStyle(
color: Colors.white,
fontSize: 14.0,
fontWeight: FontWeight.bold,
fontFamily: "Raleway-Bold",
),
),
trailing:
Icon(Icons.keyboard_arrow_right, color: Colors.white, size: 30.0),
onTap: () {
Navigator.pushNamed(context, ‘/listScreen’);
},
);
Please suggest. Thank you in advance
I think you should have a look at: Passing data between screens in Flutter
Is this what you are looking for?
LE:
If you just want to change data source for the list and add a back button, please try this code:
class MyHomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
bool showDetails = false;
String title = 'Resources';
List<Resource> resources = [
new Resource('1', 'one', null),
new Resource('2', 'two', [new Resource('Child', 'Child', null)]),
new Resource('3', 'three', null),
new Resource('4', 'four', [
new Resource('Child', 'Child', null),
new Resource('Child', 'Child', null)
]),
new Resource('5', 'five', null)
];
List<Resource> currentSource;
#override
Widget build(BuildContext context) {
if (!showDetails) {
currentSource = resources;
}
Widget showResourcesList() {
return new ListView.builder(
itemCount: currentSource.length,
itemBuilder: (BuildContext context, int index) {
return new ListTile(
title: Center(
child: Text(currentSource[index].name),
),
onTap: () {
setState(() {
if (currentSource[index].children != null) {
title = 'Children for ' + currentSource[index].name;
currentSource = resources[index].children;
showDetails = true;
}
});
});
});
}
Widget showBackButton() {
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
setState(() {
showDetails = false;
currentSource = resources;
title = 'Resources';
});
},
);
}
Widget showSettingsButton() {
return IconButton(
icon: Icon(Icons.settings),
onPressed: () {},
);
}
return Scaffold(
appBar: AppBar(
elevation: 0.1,
backgroundColor: Color.fromRGBO(0, 113, 188, 1.0),
title: Text(
title,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 20.0,
),
),
leading: showDetails ? showBackButton() : showSettingsButton(),
),
body: showResourcesList(),
);
}
}
class Resource {
String name;
String description;
List<Resource> children;
Resource(this.name, this.description, this.children);
}
I used a bool variable (showDetails) which represents the app state and I change the data source when tapping on a listTile.

How to check if Flutter Text widget was overflowed

I have a Text widget which can be truncated if it exceeds a certain size:
ConstrainedBox(
constraints: BoxConstraints(maxHeight: 50.0),
child: Text(
widget.review,
overflow: TextOverflow.ellipsis,
)
);
Or max number of lines:
RichText(
maxLines: 2,
overflow: TextOverflow.ellipsis,
text: TextSpan(
style: TextStyle(color: Colors.black),
text: widget.review,
));
My goal is to have the text expandable only if an overflow occurred. Is there a proper way of checking if the text overflowed?
What I've tried
I have found that in RichText, there is a RenderParagraph renderObject , which has a private property TextPainter _textPainter which has a bool didExceedMaxLines.
In short, I just need to access richText.renderObject._textPainter.didExceedMaxLines but as you can see, it is made private with the underscore.
I found a way to do it. Full code below, but in short:
Use a LayoutBuilder to determine how much space we have.
Use a TextPainter to simulate the render of the text within the space.
Here's the full demo app:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Text Overflow Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(title: Text("DEMO")),
body: TextOverflowDemo(),
),
);
}
}
class TextOverflowDemo extends StatefulWidget {
#override
_EditorState createState() => _EditorState();
}
class _EditorState extends State<TextOverflowDemo> {
var controller = TextEditingController();
#override
void initState() {
controller.addListener(() {
setState(() {
mytext = controller.text;
});
});
controller.text = "This is a long overflowing text!!!";
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
String mytext = "";
#override
Widget build(BuildContext context) {
int maxLines = 1;
double fontSize = 30.0;
return Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: <Widget>[
LayoutBuilder(builder: (context, size) {
// Build the textspan
var span = TextSpan(
text: mytext,
style: TextStyle(fontSize: fontSize),
);
// Use a textpainter to determine if it will exceed max lines
var tp = TextPainter(
maxLines: maxLines,
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
text: span,
);
// trigger it to layout
tp.layout(maxWidth: size.maxWidth);
// whether the text overflowed or not
var exceeded = tp.didExceedMaxLines;
return Column(children: <Widget>[
Text.rich(
span,
overflow: TextOverflow.ellipsis,
maxLines: maxLines,
),
Text(exceeded ? "Overflowed!" : "Not overflowed yet.")
]);
}),
TextField(
controller: controller,
),
],
),
);
}
}
There is a shorter way to get an answer if text is overflowed or not. You just need to define textStyle and get the answer from this method
bool hasTextOverflow(
String text,
TextStyle style,
{double minWidth = 0,
double maxWidth = double.infinity,
int maxLines = 2
}) {
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: style),
maxLines: maxLines,
textDirection: TextDirection.ltr,
)..layout(minWidth: minWidth, maxWidth: maxWidth);
return textPainter.didExceedMaxLines;
}
You can use a flutter plug-in auto_size_text at pub.dev.
When the text is get overflowed, you can set some widget to be appeared.
int maxLines = 3;
String caption = 'Your caption is here';
return AutoSizeText(
caption,
maxLines: maxLines,
style: TextStyle(fontSize: 20),
minFontSize: 15,
overflowReplacement: Column( // This widget will be replaced.
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
caption,
maxLines: maxLines,
overflow: TextOverflow.ellipsis,
),
Text(
"Show more",
style: TextStyle(color: PrimaryColor.kGrey),
)
],
),
);
Made my own Widget i use it cross the project, it take Text widget in the constructor and reads the properties of it.
import 'package:flutter/material.dart';
class OverflowDetectorText extends StatelessWidget {
final Text child;
OverflowDetectorText({
Key? key,
required this.child,
}) : super(key: key);
#override
Widget build(BuildContext context) {
var tp = TextPainter(
maxLines: child.maxLines,
textAlign: child.textAlign ?? TextAlign.start,
textDirection: child.textDirection ?? TextDirection.ltr,
text: child.textSpan ?? TextSpan(
text: child.data,
style: child.style,
),
);
return LayoutBuilder(
builder: (context, constrains) {
tp.layout(maxWidth: constrains.maxWidth);
final overflowed = tp.didExceedMaxLines;
if (overflowed) {
//You can wrap your Text `child` with anything
}
return child;
},
);
}
}

How do I make a TextSpan ripple when I tap it?

Imagine I have a long piece of text like this: HELLO THIS IS MY LONG SENTENCE . I want the word LONG to ripple (ink splash) when I tap it.
Let's say I have this code:
new RichText(
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'),
],
),
)
Thanks!
If you want a generic solution to place widgets over portions of text, see this gist.
You can use the following code to have the ripple constrained to a specific section of the text:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:ui' show TextBox;
import 'dart:math';
void main() {
runApp(new MaterialApp(
home: new Material(
child: new Center(
child: new Demo(),
),
),
));
}
class Demo extends StatelessWidget {
final TextSelection textSelection =
const TextSelection(baseOffset: 17, extentOffset: 21);
final GlobalKey _textKey = new GlobalKey();
#override
Widget build(context) => new Stack(
children: <Widget>[
new RichText(
key: _textKey,
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'),
],
),
),
new Positioned.fill(
child: new LayoutBuilder(
builder: (context, _) => new Stack(
children: <Widget>[
new Positioned.fromRect(
rect: _getSelectionRect(),
child: new InkWell(
onTap: () => {}, // needed to show the ripple
),
),
],
),
),
),
],
);
Rect _getSelectionRect() =>
(_textKey.currentContext.findRenderObject() as RenderParagraph)
.getBoxesForSelection(textSelection)
.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;
}
You can accomplish this effect by adapting the code in ink_well.dart.
In this example I configured the rectCallback to expand to the containing card, but you could provide a smaller rectangle with the splash be centered around the point of the tap.
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'dart:collection';
void main() {
runApp(new MaterialApp(home: new DemoApp()));
}
class DemoText extends StatefulWidget {
#override
DemoTextState createState() => new DemoTextState();
}
class DemoTextState<T extends InkResponse> extends State<T>
with AutomaticKeepAliveClientMixin {
Set<InkSplash> _splashes;
InkSplash _currentSplash;
#override
bool get wantKeepAlive => (_splashes != null && _splashes.isNotEmpty);
void _handleTapDown(TapDownDetails details) {
final RenderBox referenceBox = context.findRenderObject();
InkSplash splash;
splash = new InkSplash(
controller: Material.of(context),
referenceBox: referenceBox,
containedInkWell: true,
rectCallback: () => referenceBox.paintBounds,
position: referenceBox.globalToLocal(details.globalPosition),
color: Theme.of(context).splashColor,
onRemoved: () {
if (_splashes != null) {
assert(_splashes.contains(splash));
_splashes.remove(splash);
if (_currentSplash == splash) _currentSplash = null;
updateKeepAlive();
} // else we're probably in deactivate()
});
_splashes ??= new HashSet<InkSplash>();
_splashes.add(splash);
_currentSplash = splash;
updateKeepAlive();
}
void _handleTap(BuildContext context) {
_currentSplash?.confirm();
_currentSplash = null;
Feedback.forTap(context);
}
void _handleTapCancel() {
_currentSplash?.cancel();
_currentSplash = null;
}
#override
void deactivate() {
if (_splashes != null) {
final Set<InkSplash> splashes = _splashes;
_splashes = null;
for (InkSplash splash in splashes) splash.dispose();
_currentSplash = null;
}
assert(_currentSplash == null);
super.deactivate();
}
Widget build(BuildContext context) {
return new Padding(
padding: new EdgeInsets.all(20.0),
child: new RichText(
text: new TextSpan(
text: 'HELLO THIS IS MY ',
style: DefaultTextStyle.of(context).style,
children: <TextSpan>[
new TextSpan(
recognizer: new TapGestureRecognizer()
..onTapCancel = _handleTapCancel
..onTapDown = _handleTapDown
..onTap = () => _handleTap(context),
text: 'LONG',
style: new TextStyle(fontWeight: FontWeight.bold),
),
new TextSpan(text: ' SENTENCE'),
],
),
),
);
}
}
class DemoApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Container(
height: 150.0,
width: 150.0,
child: new Card(
child: new DemoText(),
),
),
),
);
}
}
By 2019, we can use this:
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: textTheme.bodyText2,
text: "HELLO THIS IS MY",
children: [
WidgetSpan(
child: InkWell(
onTap: () {},
child: Text(
"SENTENCE",
style: TextStyle(color: colorScheme.primary),
),
),
),
],
),
),

How do I bold (or format) a piece of text within a paragraph?

How can I have a line of text with different formatting?
e.g.:
Hello World
You should use the RichText widget.
A RichText widget will take in a TextSpan widget that can also have a list of children TextSpans.
Each TextSpan widget can have a different TextStyle.
Here is the example code to render:
Hello World
var text = RichText(
text: TextSpan(
// Note: Styles for TextSpans must be explicitly defined.
// Child text spans will inherit styles from parent
style: const TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
TextSpan(text: 'Hello'),
TextSpan(text: 'World', style: const TextStyle(fontWeight: FontWeight.bold)),
],
),
);
[UPDATE]
The below answer fits best for couple of words and not for a paragraph,If you have a long sentence or a paragraph where you need to format a particular text prefer using RichText as suggested by #DvdWasibi in the above answer
[OLD ANSWER]
I like keeping my code short and clean this is How I Would do it add two text fields in a row one with Normal font and another bold,
Note: This may not look good for a long paragraph looks good for Headlines etc.
Row(children: [
Text("Hello"),
Text("World", style: const TextStyle(fontWeight: FontWeight.bold))
]);
and you should get a desired output as "Hello World"
return RichText(
text: TextSpan(
text: 'Can you ',
style: TextStyle(color: Colors.black),
children: <TextSpan>[
TextSpan(
text: 'find the',
style: TextStyle(
color: Colors.green,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.wavy,
),
recognizer: _longPressRecognizer,
),
TextSpan(text: 'secret?'),
],
),
);
You should use the Text.rich constructor from Text class here.
By using the rich constructor you can display a paragraph with differently styled TextSpans.
Why I recommended it instead of RichText is because of by using RichText you will required to define the parent TextStyle in RichText but using the rich constructor of Text you don't need explicitly defined the parent TextStyle in Text.rich
Here is the example how to use it with same result
Using RichText
const text = RichText(
text: TextSpan(
// Here is the explicit parent TextStyle
style: new TextStyle(
fontSize: 16.0,
color: Colors.black,
fontFamily: 'Montserrat',
),
children: <TextSpan>[
new TextSpan(text: 'Hello'),
new TextSpan(text: 'World', style: new TextStyle(fontWeight: FontWeight.bold)),
],
),
);
Using rich constructor of Text
const text = Text.rich(
TextSpan(
// with no TextStyle it will have default text style
text: 'Hello',
children: <TextSpan>[
TextSpan(text: 'World', style: TextStyle(fontWeight: FontWeight.bold)),
],
),
)
I've solved a similar problem by using flutter_html widget with custom styles for different tags.
Actually, I've got the strings in different languages and some parts of them should be bold, so it wasn't easy to determine which part of the string I should make bold since strings was in l10n locale files. Here is example:
Container(
child: Html(
data: "<p>My normal text <b>with bold part</b> in any place</p>",
style: {
"p": Style(
fontSize: FontSize.large,
fontWeight: FontWeight.normal),
"b": Style(
fontWeight: FontWeight.bold,
),
)
);
I think this approach is useful in case you have a lot of differently styled text inside your regular text.
Regex
You can use this widget. The example below always make numbers bold.
import 'package:flutter/material.dart';
class TextBold extends StatelessWidget{
final String text;
final String regex;
static const _separator = " ";
const TextBold({Key key, this.text, this.regex = r'\d+'}) : super(key: key);
#override
Widget build(BuildContext context) {
final parts = splitJoin();
return Text.rich(TextSpan(
children: parts.map((e) => TextSpan(
text: e.text,
style: (e.isBold)
? const TextStyle(fontFamily: 'bold')
: const TextStyle(fontFamily: 'light')))
.toList()));
}
// Splits text using separator, tag ones to be bold using regex
// and rejoin equal parts back when possible
List<TextPart> splitJoin(){
assert(text!=null);
final tmp = <TextPart>[];
final parts = text.split(_separator);
// Bold it
for (final p in parts){
tmp.add(TextPart(p + _separator,p.contains(RegExp(regex))));
}
final result = <TextPart>[tmp[0]];
// Fold it
if (tmp.length>1) {
int resultIdx = 0;
for (int i = 1; i < tmp.length; i++)
if (tmp[i - 1].isBold != tmp[i].isBold) {
result.add(tmp[i]);
resultIdx++;
}
else
result[resultIdx].text = result[resultIdx].text
+ tmp[i].text;
}
return result;
}
}
class TextPart{
String text;
bool isBold;
TextPart(this.text, this.isBold);
}
Not fully tested but you can try this helper function that uses Text.rich and takes in the fullText and the textToBold then returns a Text:
static Text boldTextPortion(
String fullText,
String textToBold,
) {
final texts = fullText.split(textToBold);
final textSpans = List.empty(growable: true);
texts.asMap().forEach((index, value) {
textSpans.add(TextSpan(text: value));
if (index < (texts.length - 1)) {
textSpans.add(TextSpan(
text: textToBold,
style: const TextStyle(fontWeight: FontWeight.bold),
));
}
});
return Text.rich(
TextSpan(
children: <TextSpan>[...textSpans],
),
);
}
RichText()
Or if you receiving text from for example 'someText'.tr,
so use styled_text pub package.

Resources