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

2021-03-08 18:02 更新

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

本節(jié)我們將實現(xiàn)一個TurnBox組件,它不僅可以以任意角度來旋轉(zhuǎn)其子節(jié)點,而且可以在角度發(fā)生變化時執(zhí)行一個動畫以過渡到新狀態(tài),同時,我們可以手動指定動畫速度。

TurnBox的完整代碼如下:

import 'package:flutter/widgets.dart';


class TurnBox extends StatefulWidget {
  const TurnBox({
    Key key,
    this.turns = .0, //旋轉(zhuǎn)的“圈”數(shù),一圈為360度,如0.25圈即90度
    this.speed = 200, //過渡動畫執(zhí)行的總時長
    this.child
  }) :super(key: key);


  final double turns;
  final int speed;
  final Widget child;


  @override
  _TurnBoxState createState() => new _TurnBoxState();
}


class _TurnBoxState extends State<TurnBox>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;


  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
        vsync: this,
        lowerBound: -double.infinity,
        upperBound: double.infinity
    );
    _controller.value = widget.turns;
  }


  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    return RotationTransition(
      turns: _controller,
      child: widget.child,
    );
  }


  @override
  void didUpdateWidget(TurnBox oldWidget) {
    super.didUpdateWidget(oldWidget);
    //旋轉(zhuǎn)角度發(fā)生變化時執(zhí)行過渡動畫  
    if (oldWidget.turns != widget.turns) {
      _controller.animateTo(
        widget.turns,
        duration: Duration(milliseconds: widget.speed??200),
        curve: Curves.easeOut,
      );
    }
  }
}

上面代碼中:

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

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

import 'package:flutter/material.dart';
import '../widgets/index.dart';


class TurnBoxRoute extends StatefulWidget {
  @override
  _TurnBoxRouteState createState() => new _TurnBoxRouteState();
}


class _TurnBoxRouteState extends State<TurnBoxRoute> {
  double _turns = .0;


  @override
  Widget build(BuildContext context) {


    return Center(
      child: Column(
        children: <Widget>[
          TurnBox(
            turns: _turns,
            speed: 500,
            child: Icon(Icons.refresh, size: 50,),
          ),
          TurnBox(
            turns: _turns,
            speed: 1000,
            child: Icon(Icons.refresh, size: 150.0,),
          ),
          RaisedButton(
            child: Text("順時針旋轉(zhuǎn)1/5圈"),
            onPressed: () {
              setState(() {
                _turns += .2;
              });
            },
          ),
          RaisedButton(
            child: Text("逆時針旋轉(zhuǎn)1/5圈"),
            onPressed: () {
              setState(() {
                _turns -= .2;
              });
            },
          )
        ],
      ),
    );
  }
}

測試代碼運行后效果如圖10-2所示:

圖10-2

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

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

class MyRichText extends StatefulWidget {
  MyRichText({
    Key key,
    this.text, // 文本字符串
    this.linkStyle, // url鏈接樣式
  }) : super(key: key);


  final String text;
  final TextStyle linkStyle;


  @override
  _MyRichTextState createState() => _MyRichTextState();
}

接下來我們在_MyRichTextState中要實現(xiàn)的功能有兩個:

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

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

class _MyRichTextState extends State<MyRichText> {


  TextSpan _textSpan;


  @override
  Widget build(BuildContext context) {
    return RichText(
      text: _textSpan,
    );
  }


  TextSpan parseText(String text) {
    // 耗時操作:解析文本字符串,構(gòu)建出TextSpan。
    // 省略具體實現(xiàn)。
  }


  @override
  void initState() {
    _textSpan = parseText(widget.text)
    super.initState();
  }
}

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

@override
void didUpdateWidget(MyRichText oldWidget) {
  if (widget.text != oldWidget.text) {
    _textSpan = parseText(widget.text);
  }
  super.didUpdateWidget(oldWidget);
}

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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號