Flutter實(shí)戰(zhàn) 組合實(shí)例:TurnBox

2021-03-08 18:02 更新

我們之前已經(jīng)介紹過(guò)RotatedBox,它可以旋轉(zhuǎn)子組件,但是它有兩個(gè)缺點(diǎn):一是只能將其子節(jié)點(diǎn)以90度的倍數(shù)旋轉(zhuǎn);二是當(dāng)旋轉(zhuǎn)的角度發(fā)生變化時(shí),旋轉(zhuǎn)角度更新過(guò)程沒(méi)有動(dòng)畫(huà)。

本節(jié)我們將實(shí)現(xiàn)一個(gè)TurnBox組件,它不僅可以以任意角度來(lái)旋轉(zhuǎn)其子節(jié)點(diǎn),而且可以在角度發(fā)生變化時(shí)執(zhí)行一個(gè)動(dòng)畫(huà)以過(guò)渡到新?tīng)顟B(tài),同時(shí),我們可以手動(dòng)指定動(dòng)畫(huà)速度。

TurnBox的完整代碼如下:

  1. import 'package:flutter/widgets.dart';
  2. class TurnBox extends StatefulWidget {
  3. const TurnBox({
  4. Key key,
  5. this.turns = .0, //旋轉(zhuǎn)的“圈”數(shù),一圈為360度,如0.25圈即90度
  6. this.speed = 200, //過(guò)渡動(dòng)畫(huà)執(zhí)行的總時(shí)長(zhǎng)
  7. this.child
  8. }) :super(key: key);
  9. final double turns;
  10. final int speed;
  11. final Widget child;
  12. @override
  13. _TurnBoxState createState() => new _TurnBoxState();
  14. }
  15. class _TurnBoxState extends State<TurnBox>
  16. with SingleTickerProviderStateMixin {
  17. AnimationController _controller;
  18. @override
  19. void initState() {
  20. super.initState();
  21. _controller = new AnimationController(
  22. vsync: this,
  23. lowerBound: -double.infinity,
  24. upperBound: double.infinity
  25. );
  26. _controller.value = widget.turns;
  27. }
  28. @override
  29. void dispose() {
  30. _controller.dispose();
  31. super.dispose();
  32. }
  33. @override
  34. Widget build(BuildContext context) {
  35. return RotationTransition(
  36. turns: _controller,
  37. child: widget.child,
  38. );
  39. }
  40. @override
  41. void didUpdateWidget(TurnBox oldWidget) {
  42. super.didUpdateWidget(oldWidget);
  43. //旋轉(zhuǎn)角度發(fā)生變化時(shí)執(zhí)行過(guò)渡動(dòng)畫(huà)
  44. if (oldWidget.turns != widget.turns) {
  45. _controller.animateTo(
  46. widget.turns,
  47. duration: Duration(milliseconds: widget.speed??200),
  48. curve: Curves.easeOut,
  49. );
  50. }
  51. }
  52. }

上面代碼中:

  1. 我們是通過(guò)組合RotationTransition和 child 來(lái)實(shí)現(xiàn)的旋轉(zhuǎn)效果。
  2. didUpdateWidget中,我們判斷要旋轉(zhuǎn)的角度是否發(fā)生了變化,如果變了,則執(zhí)行一個(gè)過(guò)渡動(dòng)畫(huà)。

下面我們測(cè)試一下TurnBox的功能,測(cè)試代碼如下:

  1. import 'package:flutter/material.dart';
  2. import '../widgets/index.dart';
  3. class TurnBoxRoute extends StatefulWidget {
  4. @override
  5. _TurnBoxRouteState createState() => new _TurnBoxRouteState();
  6. }
  7. class _TurnBoxRouteState extends State<TurnBoxRoute> {
  8. double _turns = .0;
  9. @override
  10. Widget build(BuildContext context) {
  11. return Center(
  12. child: Column(
  13. children: <Widget>[
  14. TurnBox(
  15. turns: _turns,
  16. speed: 500,
  17. child: Icon(Icons.refresh, size: 50,),
  18. ),
  19. TurnBox(
  20. turns: _turns,
  21. speed: 1000,
  22. child: Icon(Icons.refresh, size: 150.0,),
  23. ),
  24. RaisedButton(
  25. child: Text("順時(shí)針旋轉(zhuǎn)1/5圈"),
  26. onPressed: () {
  27. setState(() {
  28. _turns += .2;
  29. });
  30. },
  31. ),
  32. RaisedButton(
  33. child: Text("逆時(shí)針旋轉(zhuǎn)1/5圈"),
  34. onPressed: () {
  35. setState(() {
  36. _turns -= .2;
  37. });
  38. },
  39. )
  40. ],
  41. ),
  42. );
  43. }
  44. }

測(cè)試代碼運(yùn)行后效果如圖10-2所示:

圖10-2

當(dāng)我們點(diǎn)擊旋轉(zhuǎn)按鈕時(shí),兩個(gè)圖標(biāo)的旋轉(zhuǎn)都會(huì)旋轉(zhuǎn)1/5圈,但旋轉(zhuǎn)的速度是不同的,讀者可以自己運(yùn)行一下示例看看效果。

實(shí)際上本示例只組合了RotationTransition一個(gè)組件,它是一個(gè)最簡(jiǎn)的組合類(lèi)組件示例。另外,如果我們封裝的是StatefulWidget,那么一定要注意在組件更新時(shí)是否需要同步狀態(tài)。比如我們要封裝一個(gè)富文本展示組件MyRichText ,它可以自動(dòng)處理url鏈接,定義如下:

  1. class MyRichText extends StatefulWidget {
  2. MyRichText({
  3. Key key,
  4. this.text, // 文本字符串
  5. this.linkStyle, // url鏈接樣式
  6. }) : super(key: key);
  7. final String text;
  8. final TextStyle linkStyle;
  9. @override
  10. _MyRichTextState createState() => _MyRichTextState();
  11. }

接下來(lái)我們?cè)?code>_MyRichTextState中要實(shí)現(xiàn)的功能有兩個(gè):

  1. 解析文本字符串“text”,生成TextSpan緩存起來(lái);
  2. build中返回最終的富文本樣式;

_MyRichTextState 實(shí)現(xiàn)的代碼大致如下:

  1. class _MyRichTextState extends State<MyRichText> {
  2. TextSpan _textSpan;
  3. @override
  4. Widget build(BuildContext context) {
  5. return RichText(
  6. text: _textSpan,
  7. );
  8. }
  9. TextSpan parseText(String text) {
  10. // 耗時(shí)操作:解析文本字符串,構(gòu)建出TextSpan。
  11. // 省略具體實(shí)現(xiàn)。
  12. }
  13. @override
  14. void initState() {
  15. _textSpan = parseText(widget.text)
  16. super.initState();
  17. }
  18. }

由于解析文本字符串,構(gòu)建出TextSpan是一個(gè)耗時(shí)操作,為了不在每次 build 的時(shí)候都解析一次,所以我們?cè)?code>initState中對(duì)解析的結(jié)果進(jìn)行了緩存,然后再build中直接使用解析的結(jié)果_textSpan。這看起來(lái)很不錯(cuò),但是上面的代碼有一個(gè)嚴(yán)重的問(wèn)題,就是父組件傳入的text發(fā)生變化時(shí)(組件樹(shù)結(jié)構(gòu)不變),那么MyRichText顯示的內(nèi)容不會(huì)更新,原因就是initState只會(huì)在 State 創(chuàng)建時(shí)被調(diào)用,所以在text發(fā)生變化時(shí),parseText沒(méi)有重新執(zhí)行,導(dǎo)致_textSpan任然是舊的解析值。要解決這個(gè)問(wèn)題也很簡(jiǎn)單,我們只需添加一個(gè)didUpdateWidget回調(diào),然后再里面重新調(diào)用parseText即可:

  1. @override
  2. void didUpdateWidget(MyRichText oldWidget) {
  3. if (widget.text != oldWidget.text) {
  4. _textSpan = parseText(widget.text);
  5. }
  6. super.didUpdateWidget(oldWidget);
  7. }

有些讀者可能會(huì)覺(jué)得這個(gè)點(diǎn)也很簡(jiǎn)單,是的,的確很簡(jiǎn)單,之所以要在這里反復(fù)強(qiáng)調(diào)是因?yàn)檫@個(gè)點(diǎn)在實(shí)際開(kāi)發(fā)中很容易被忽略,它雖然簡(jiǎn)單,但卻很重要??傊?dāng)我們?cè)?State 中會(huì)緩存某些依賴(lài) Widget 參數(shù)的數(shù)據(jù)時(shí),一定要注意在組件更新時(shí)是否需要同步狀態(tài)。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)