Last active
January 10, 2023 12:36
-
-
Save nhancv/83bb7792d18a4da9cae22ec47256b9f4 to your computer and use it in GitHub Desktop.
Flutter highlight text in search
This file contains 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
// For display friends | |
List<Friend> _friendsDto = <Friend>[]; | |
// Save search value | |
String _searchValue; | |
/// On search input change listener to validate form | |
void onSearchValueChange(final String search) { | |
searchValue = search; | |
// Filter on _friends, and copy to display data | |
friendsDto = _friends | |
.where((Friend f) => | |
(f.name?.toLowerCase()?.contains(search?.toLowerCase() ?? '') ?? | |
true) || | |
(f.phone?.toLowerCase()?.contains(search?.toLowerCase() ?? '') ?? | |
true)) | |
.toList(); | |
} | |
// bold search text | |
// Usage: | |
// RichText( | |
// text: TextSpan( | |
// children: highlightOccurrences(source, query), | |
// style: const TextStyle(color: Colors.black), | |
// ), | |
// ), | |
List<TextSpan> highlightOccurrences(String source, String query) { | |
if (query == null || query.isEmpty) { | |
return <TextSpan>[TextSpan(text: source)]; | |
} | |
final List<Match> matches = <Match>[]; | |
for (final String token in query.trim().toLowerCase().split(' ')) { | |
matches.addAll(token.allMatches(source.toLowerCase())); | |
} | |
if (matches.isEmpty) { | |
return <TextSpan>[TextSpan(text: source)]; | |
} | |
matches.sort((Match a, Match b) => a.start.compareTo(b.start)); | |
int lastMatchEnd = 0; | |
final List<TextSpan> children = <TextSpan>[]; | |
const Color matchColor = Color(0xFF602885); | |
for (final Match match in matches) { | |
if (match.end <= lastMatchEnd) { | |
// already matched -> ignore | |
} else if (match.start <= lastMatchEnd) { | |
children.add(TextSpan( | |
text: source.substring(lastMatchEnd, match.end), | |
style: | |
const TextStyle(fontWeight: FontWeight.bold, color: matchColor), | |
)); | |
} else { | |
children.add(TextSpan( | |
text: source.substring(lastMatchEnd, match.start), | |
)); | |
children.add(TextSpan( | |
text: source.substring(match.start, match.end), | |
style: | |
const TextStyle(fontWeight: FontWeight.bold, color: matchColor), | |
)); | |
} | |
if (lastMatchEnd < match.end) { | |
lastMatchEnd = match.end; | |
} | |
} | |
if (lastMatchEnd < source.length) { | |
children.add(TextSpan( | |
text: source.substring(lastMatchEnd, source.length), | |
)); | |
} | |
return children; | |
} |
This file contains 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
// list | |
return ListView.separated( | |
physics: const AlwaysScrollableScrollPhysics(), | |
itemCount: friends.length, | |
itemBuilder: (BuildContext context, int index) { | |
final Friend item = friends[index]; | |
return _buildItem(context, index, item, provider); | |
}, | |
separatorBuilder: (_, int index) => const Divider(), | |
); | |
// Build friend view | |
Widget _buildItem(BuildContext context, int index, Friend item, ContactProvider provider) { | |
return ListTile( | |
leading: item.avatarUrl == null | |
? CircleAvatar( | |
child: Text(item.name?.substring(0, 1)?.toUpperCase() ?? '')) | |
: CircleAvatar( | |
backgroundColor: Colors.white, | |
backgroundImage: NetworkImage(item.avatarUrl)), | |
title: RichText( | |
text: TextSpan( | |
children: provider.highlightOccurrences(item.name, provider.searchValue), | |
style: const TextStyle(color: Colors.black), | |
), | |
), | |
subtitle: RichText( | |
text: TextSpan( | |
children: provider.highlightOccurrences(item.phone, provider.searchValue), | |
style: const TextStyle(color: Colors.black), | |
), | |
), | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment