Flutter : 一個簡單的九宮格抽獎實現

寫在前面

用Flutter實現一個簡單的九宮格抽獎
在這裏插入圖片描述

步驟

  • 實現一個九宮格的頁面
  • 實現一個控制器用於控制抽獎開始
  • 實現抽獎的動畫

過程

定義數據類型

class SimpleLotteryValue {
  SimpleLotteryValue(
      {this.target = 0, this.isFinish = false, this.isPlaying = false});

  /// 中獎目標
  int target = 0;

  bool isPlaying = false;
  bool isFinish = false;

  SimpleLotteryValue copyWith({
    int target = 0,
    bool isPlaying = false,
    bool isFinish = false,
  }) {
    return SimpleLotteryValue(
        target: target, isFinish: isFinish, isPlaying: isPlaying);
  }

  @override
  String toString() {
    return "target : $target , isPlaying : $isPlaying , isFinish : $isFinish";
  }
}

定義控制器

class SimpleLotteryController extends ValueNotifier<SimpleLotteryValue> {
  SimpleLotteryController() : super(SimpleLotteryValue());

  /// 開啓抽獎
  ///
  /// [target] 中獎目標
  void start(int target) {
    // 九宮格抽獎裏範圍爲0~8
    assert(target >= 0 && target <= 8);
    if (value.isPlaying) {
      return;
    }
    value = value.copyWith(target: target, isPlaying: true);
  }

  void finish() {
    value = value.copyWith(isFinish: true);
  }
}

實現九宮格頁面

class SimpleLotteryWidget extends StatefulWidget {
  final SimpleLotteryController controller;
  final Function onPress;

  SimpleLotteryWidget({Key key, @required this.controller, this.onPress});

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

class _SimpleLotteryWidgetState extends State<SimpleLotteryWidget> {
  
  @override
  Widget build(BuildContext context) {
    return Container(
        width: 220,
        height: 220,
        child: GridView.builder(
            itemCount: 9,
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3, crossAxisSpacing: 4, mainAxisSpacing: 4),
            itemBuilder: (context, index) {
              if (index != 4) {
                return Container(
                  child: Stack(
                    alignment: Alignment.center,
                    children: <Widget>[
                      Container(
                        width: 50,
                        height: 50,
                        color:  Colors.amber,
                      ),
                    ],
                  ),
                );
              }
              return GestureDetector(
                onTap: () => widget.onPress(),
                child: Container(
                  color: Colors.red,
                ),
              );
            }));
  }
}

實現抽獎動畫


 Animation<int> _selectedIndexTween;
  AnimationController _startAnimateController;
  int _currentSelect = -1;
  int _target = 0;

  /// 旋轉的圈數
  final int repeatRound = 3;
  VoidCallback _listener;

  _SimpleLotteryWidgetState() {
    _listener = () {
      // 開啓抽獎動畫
      if (widget.controller.value.isPlaying) {
        _startAnimateController.reset();
        _target = widget.controller.value.target;
        _selectedIndexTween = _initSelectIndexTween(_target);

        _startAnimateController.forward();
      }
    };
  }

  /// 選中下標的映射 
  /// 因爲動畫的數值爲0~8的增長,而宮格的實際下標順序是0,3,6,7,8,5,2,1,因此做了映射處理
  final Map<int, int> _selectMap = {
    0: 0,
    1: 3,
    2: 6,
    3: 7,
    4: 8,
    5: 5,
    6: 2,
    7: 1
  };

  @override
  void initState() {
    _startAnimateController =
        AnimationController(vsync: this, duration: Duration(seconds: 5));
    _selectedIndexTween = _initSelectIndexTween(_target);

    // 動畫監聽
    _startAnimateController.addListener(() {
      // 更新選中的下標
      _currentSelect = _selectMap[_selectedIndexTween.value % 8];

      if (_startAnimateController.isCompleted) {
        widget.controller.finish();
      }
      setState(() {});
    });

    // 控制監聽
    widget.controller.addListener(_listener);

    super.initState();
  }

完整代碼

class SimpleLotteryWidget extends StatefulWidget {
  final SimpleLotteryController controller;
  final Function onPress;

  SimpleLotteryWidget({Key key, @required this.controller, this.onPress});

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

class _SimpleLotteryWidgetState extends State<SimpleLotteryWidget>
    with TickerProviderStateMixin {
  Animation<int> _selectedIndexTween;
  AnimationController _startAnimateController;
  int _currentSelect = -1;
  int _target = 0;

  /// 旋轉的圈數
  final int repeatRound = 3;
  VoidCallback _listener;

  /// 選中下標的映射
  final Map<int, int> _selectMap = {
    0: 0,
    1: 3,
    2: 6,
    3: 7,
    4: 8,
    5: 5,
    6: 2,
    7: 1
  };

  _SimpleLotteryWidgetState() {
    _listener = () {
      // 開啓抽獎動畫
      if (widget.controller.value.isPlaying) {
        _startAnimateController.reset();
        _target = widget.controller.value.target;
        _selectedIndexTween = _initSelectIndexTween(_target);

        _startAnimateController.forward();
      }
    };
  }

  /// 初始化tween
  ///
  /// [target] 中獎的目標
  Animation _initSelectIndexTween(int target) =>
      StepTween(begin: 0, end: repeatRound * 8 + target).animate(
          CurvedAnimation(
              parent: _startAnimateController, curve: Curves.easeOutQuart));

  @override
  void initState() {
    _startAnimateController =
        AnimationController(vsync: this, duration: Duration(seconds: 5));
    _selectedIndexTween = _initSelectIndexTween(_target);

    // 動畫監聽
    _startAnimateController.addListener(() {
      // 更新選中的下標
      _currentSelect = _selectMap[_selectedIndexTween.value % 8];

      if (_startAnimateController.isCompleted) {
        widget.controller.finish();
      }
      setState(() {});
    });

    // 控制監聽
    widget.controller.addListener(_listener);

    super.initState();
  }

  @override
  void deactivate() {
    widget.controller.removeListener(_listener);
    super.deactivate();
  }

  @override
  void dispose() {
    _startAnimateController.dispose();
    widget.controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        width: 220,
        height: 220,
        child: GridView.builder(
            itemCount: 9,
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3, crossAxisSpacing: 4, mainAxisSpacing: 4),
            itemBuilder: (context, index) {
              if (index != 4) {
                return Container(
                  child: Stack(
                    alignment: Alignment.center,
                    children: <Widget>[
                      Container(
                        width: 50,
                        height: 50,
                        color: index == _currentSelect
                            ? Colors.blue
                            : Colors.amber,
                      ),
                    ],
                  ),
                );
              }
              return GestureDetector(
                onTap: () => widget.onPress(),
                child: Container(
                  color: Colors.red,
                ),
              );
            }));
  }
}

代碼地址

代碼地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章