首頁 > 軟體

Flutter實現文字捲動高亮效果的範例講解

2022-03-18 16:00:18

前言

最近有個需求是人工語音播放時文字能隨語音朗讀時像歌詞捲動的效果.

原本第一考慮的時能隨時間字型漸變成更改後的顏色, 有比較流暢的走馬燈效果. 但最終實踐了幾次後發現要能夠逐字逐行漸變有一些麻煩, 不好實現.

所以轉而變為將字型直接將字型高亮, 一段文字區分成兩個部分, 一個部分是高亮文字, 也就是已朗讀的部分, 一個部分是剩下未朗讀的非高亮文字. 通過時時渲染頁面就能達成捲動高亮的效果.

功能實現

因為在Text中會存在兩段文字, 所以就不能單隻用Text元件, 而改用Text.rich. 通過textSpan生成一個陣列然後放到text.rich中. 所以本文需要處理, 而不是自己一個個拼接, 所以需要先有一個解析的類來負責處理.

需要在專案中加入第三方外掛 string_scanner 用於掃描文字.

class StringParser {
  // 匯入的文字
  final String content;
  // 高亮部分尾部索引, 也就是兩段的區分位置
  final int endIndex;

  StringParser({required this.content, required this.endIndex});

  late StringScanner _scanner;

  // 解析函數
  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++;
    }
  }
}

之後需要定義一個高亮的資料型別, 用於方便修改之後想要高亮的文字樣式和預設樣式.

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);
}

至此文字高亮和非高亮處理完成, 只需要在檔案中匯入後使用.

捲動效果則需要實現一個play函數裡通過 Future.delayed來控制延時遞迴執行.

_starPlay(flag) {
	// 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);
    });
  }

最終在檔案裡的程式碼則是

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 =
      """一點不錯,」狐狸說。「對我來說,你還只是一個小男孩,就像其他千萬個小男孩一樣。我不需要你。你也同樣用不著我。對你來說,我也不過是一隻狐狸,和其他千萬只狐狸一樣。但是,如果你馴服了我,我們就互相不可缺少了。對我來說,你就是世界上唯一的了;我對你來說,也是世界上唯一的了。""";

  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("捲動高亮文字"),
          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);
    });
  }
}

實現效果:

到此這篇關於Flutter實現文字捲動高亮效果的範例講解的文章就介紹到這了,更多相關Flutter文字高亮內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com