Flutter實(shí)現(xiàn)文本滾動(dòng)高亮效果的示例講解
前言
最近有個(gè)需求是人工語(yǔ)音播放時(shí)文本能隨語(yǔ)音朗讀時(shí)像歌詞滾動(dòng)的效果.
原本第一考慮的時(shí)能隨時(shí)間字體漸變成更改后的顏色, 有比較流暢的走馬燈效果. 但最終實(shí)踐了幾次后發(fā)現(xiàn)要能夠逐字逐行漸變有一些麻煩, 不好實(shí)現(xiàn).
所以轉(zhuǎn)而變?yōu)閷⒆煮w直接將字體高亮, 一段文本區(qū)分成兩個(gè)部分, 一個(gè)部分是高亮文本, 也就是已朗讀的部分, 一個(gè)部分是剩下未朗讀的非高亮文本. 通過(guò)時(shí)時(shí)渲染頁(yè)面就能達(dá)成滾動(dòng)高亮的效果.
功能實(shí)現(xiàn)
因?yàn)樵?code>Text中會(huì)存在兩段文本, 所以就不能單只用Text組件, 而改用Text.rich. 通過(guò)textSpan生成一個(gè)數(shù)組然后放到text.rich中. 所以本文需要處理, 而不是自己一個(gè)個(gè)拼接, 所以需要先有一個(gè)解析的類來(lái)負(fù)責(zé)處理.
需要在項(xiàng)目中加入第三方插件 string_scanner 用于掃描文本.
class StringParser {
// 導(dǎo)入的文本
final String content;
// 高亮部分尾部索引, 也就是兩段的區(qū)分位置
final int endIndex;
StringParser({required this.content, required this.endIndex});
late StringScanner _scanner;
// 解析函數(shù)
InlineSpan parser() {
_scanner = StringScanner(content);
parseContent();
final List<InlineSpan> spans = [];
int currentPosition = 0;
// 需要高亮的部分
spans.add(TextSpan(style: _spans.style, text: _spans.text(content)));
currentPosition = _spans.end;
// 未高亮的部分
if (currentPosition != content.length) {
spans.add(
TextSpan(text: content.substring(currentPosition, content.length)));
}
return TextSpan(style: TextStyleSupport.defaultStyle, children: spans);
}
late SpanBean _spans;
// 解析需要變成高亮的字符
void parseContent() {
int startIndex = 0;
_spans = SpanBean(startIndex, endIndex);
if (!_scanner.isDone) {
_scanner.position++;
}
}
}之后需要定義一個(gè)高亮的數(shù)據(jù)類型, 用于方便修改之后想要高亮的文本樣式和默認(rèn)樣式.
class SpanBean {
SpanBean(this.start, this.end);
final int start;
final int end;
String text(String src) {
return src.substring(start, end);
}
TextStyle get style => TextStyleSupport.highLightStyle;
}
class TextStyleSupport {
static const defaultStyle = TextStyle(color: Colors.black, fontSize: 36);
static const highLightStyle = TextStyle(color: Colors.green, fontSize: 36);
}至此文本高亮和非高亮處理完成, 只需要在文件中導(dǎo)入后使用.
滾動(dòng)效果則需要實(shí)現(xiàn)一個(gè)play函數(shù)里通過(guò) Future.delayed來(lái)控制延時(shí)遞歸執(zhí)行.
_starPlay(flag) {
// flag用于判斷是 執(zhí)行還是暫停
if (this.endIndex == content.length + 1 || !flag) {
return;
}
parser = StringParser(content: content, endIndex: this.endIndex++);
span = parser.parser();
setState(() {});
Future.delayed(Duration(milliseconds: 100)).then((value) {
_starPlay(this.flag);
});
}最終在文件里的代碼則是
import 'package:flutter/material.dart';
import 'string_parser.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late InlineSpan span;
final String content =
"""一點(diǎn)不錯(cuò),”狐貍說(shuō)?!皩?duì)我來(lái)說(shuō),你還只是一個(gè)小男孩,就像其他千萬(wàn)個(gè)小男孩一樣。我不需要你。你也同樣用不著我。對(duì)你來(lái)說(shuō),我也不過(guò)是一只狐貍,和其他千萬(wàn)只狐貍一樣。但是,如果你馴服了我,我們就互相不可缺少了。對(duì)我來(lái)說(shuō),你就是世界上唯一的了;我對(duì)你來(lái)說(shuō),也是世界上唯一的了。""";
late StringParser parser;
int endIndex = 0;
bool flag = true;
@override
void initState() {
super.initState();
parser = StringParser(content: content, endIndex: endIndex);
span = parser.parser();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("滾動(dòng)高亮文本"),
actions: [
ElevatedButton(
onPressed: () {
this.flag = true;
_starPlay(flag);
print('開始');
},
child: Text("開始")),
ElevatedButton(
onPressed: () {
this.flag = false;
// _starPlay(flag);
print('暫停');
},
child: Text("暫停"))
],
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Text.rich(span),
));
}
_starPlay(flag) {
if (this.endIndex == content.length + 1 || !flag) {
return;
}
parser = StringParser(content: content, endIndex: this.endIndex++);
span = parser.parser();
setState(() {});
Future.delayed(Duration(milliseconds: 100)).then((value) {
_starPlay(this.flag);
});
}
}實(shí)現(xiàn)效果:

到此這篇關(guān)于Flutter實(shí)現(xiàn)文本滾動(dòng)高亮效果的示例講解的文章就介紹到這了,更多相關(guān)Flutter文本高亮內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android自定義View實(shí)現(xiàn)驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)驗(yàn)證碼的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
android利用消息機(jī)制獲取網(wǎng)絡(luò)圖片
這篇文章主要為大家詳細(xì)介紹了android利用消息機(jī)制獲取網(wǎng)絡(luò)圖片的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
Android DataBinding單向數(shù)據(jù)綁定深入探究
看了谷歌官方文章確實(shí)寫的太簡(jiǎn)略了,甚至看完之后有很多地方還不知道怎么回事兒或者怎么用,那么接下來(lái)我將通過(guò)文章全面介紹一下DataBinding單向數(shù)據(jù)綁定2022-11-11
AndroidStudio 使用過(guò)程中出現(xiàn)的異常(Gradle sync failed)處理辦法
本文主要介紹AndroidStudio 使用過(guò)程中出現(xiàn)的異常的解決辦法,這里幫大家舉例說(shuō)明,如何處理出現(xiàn)這種問(wèn)題,有需要的小伙伴可以參考下2016-09-09
Android自定義View實(shí)現(xiàn)左右滑動(dòng)選擇出生年份
這篇文章主要介紹了Android自定義View實(shí)現(xiàn)左右滑動(dòng)選擇出生年份,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06
Android四大組件之Service服務(wù)詳細(xì)講解
Android的服務(wù)是開發(fā)Android應(yīng)用程序的重要組成部分。不同于活動(dòng)Activity,服務(wù)是在后臺(tái)運(yùn)行,服務(wù)沒(méi)有接口,生命周期也與活動(dòng)Activity非常不同。通過(guò)使用服務(wù)我們可以實(shí)現(xiàn)一些后臺(tái)操作,比如想從遠(yuǎn)程服務(wù)器加載一個(gè)網(wǎng)頁(yè)等,下面來(lái)看看詳細(xì)內(nèi)容,需要的朋友可以參考下2022-07-07
Assert.assertEquals()方法參數(shù)詳解
本文詳細(xì)講解了Assert.assertEquals()方法參數(shù),文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12
Android實(shí)現(xiàn)讀取掃碼槍內(nèi)容(條形碼)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)讀取掃碼槍內(nèi)容、條形碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09

