Flutter教程- Async(五) 通过stream做一个小游戏


发文时间:2022年04月18日 10:36:07     编辑:Aaron      标签:Flutter异步(Async) 图文详解系列 1019


结合streamBuilder一个键盘小游戏,通过小键盘发出的触摸时间做出对应的响应等,以及StreamTransformer的使用详解。

期间有写到关于StreamTransformer(数据流变形器)的使用方式,详细请看代码~image.png

import 'dart:async';
import 'dart:ffi';
import 'dart:math';

import 'package:flutter/material.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 Aaron',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    print('已热加载APP');
    super.initState();
  }

  @override
  void dispose() {
//销毁时关闭stream
    _InputController.close();
    super.dispose();
  }

//键盘输入的stream流 (广播)
  final _InputController = StreamController.broadcast();
  final _scoreController = StreamController.broadcast();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: StreamBuilder(
          /*
      todo计分的方式很多,如监听_InputController流的数据、
      或单独拉一个stf动态widget,或增加变量setState等
      以下用transform的方式去做,使其性能在低消耗的情况下 形成数据的监听 和值改变等
       this._scoreController.stream.transform(TallyTransformer())
        相当于把当前监听的stream转换成我们自己转后的数据
     */
          stream: this._scoreController.stream.transform(TallyTransformer()),
          builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
            if (snapshot.hasData) {
              return Text(
                'Score分数:${snapshot.data}',
                style: TextStyle(fontSize: 24),
              );
            }
            return Text(
              'Score分数:0',
              style: TextStyle(fontSize: 24),
            );
          },
        ),
      ),
      body: Stack(
        children: [
          //数字题目  ...把list拆分出来
          ...List.generate(
            10,
            (index) => Puzzle(
              inputStream: _InputController.stream,
              scoreStream: _scoreController,
            ),
          ),

          Align(
            //输入答案
            alignment: Alignment.bottomCenter,
            child: KeyPad(this._InputController),
          ),
        ],
      ),
    );
  }
}

//处理当前分数汇总  (处理数据流之间的变化 StreamTransformer)
class TallyTransformer implements StreamTransformer {
  int sum = 0;
  StreamController _controller = StreamController();

  @override
  Stream bind(Stream stream) {
/*
bind  数据流刚开始接入的时候 给的是初始时的数据流
目的是把初始的数据流转换成我们所需的数据流 后而返回
 */
//源头stream
    stream.listen((event) {
      //在这里可以做很多很多操作
      sum += event;
      _controller.add(sum);
    });

//返回的stream
    return _controller.stream;
  }

//cast 为stream类型检查
  @override
  StreamTransformer<RS, RT> cast<RS, RT>() =>
      StreamTransformer.castFrom(this); //让其自动做检查
}

//题目
class Puzzle extends StatefulWidget {
//inputStream键盘输入的值
  final inputStream;
  final scoreStream;
  const Puzzle({Key key, this.inputStream, this.scoreStream}) : super(key: key);
  @override
  _PuzzleState createState() => _PuzzleState();
}

//with 动画SingleTickerProviderStateMixin
class _PuzzleState extends State<Puzzle> with SingleTickerProviderStateMixin {
  int a, b;
  Color color;
  double x;
  AnimationController _controller;

//重新初始化
  reset([from = 0.0]) {
    a = Random().nextInt(5) + 1;
    b = Random().nextInt(5);
    x = Random().nextDouble() * 320;
    color = Colors.primaries[Random().nextInt(Colors.primaries.length)][200];
    _controller.duration =
        Duration(milliseconds: Random().nextInt(5000) + 5000);

//让动画循环,from
    _controller.forward(from: from);
  }

  @override
  void initState() {
//初始化动画
    _controller = AnimationController(
      vsync: this,
    );

//重置动画    0.0-1.0 让其随机起始位 让动画随机在起始位或者在中间显示
    reset(Random().nextDouble());

//当动画停止的时候重置下题目和让组件回到初始位置
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        //减分
        widget.scoreStream.add(-1);
        reset();
      }
    });

//监听当前用户键盘的输入值
    widget.inputStream.listen((input) {
      if (input == a + b) {
        //加分
        widget.scoreStream.add(1);
        reset();
      }
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Positioned(
          left: x,
          top: 680 * _controller.value - 100,
          child: Container(
            decoration: BoxDecoration(
              color: color.withOpacity(0.5), //半透明 (Opacity透明组件)
              border: Border.all(color: Colors.black),
              borderRadius: BorderRadius.circular(24),
            ),
            padding: EdgeInsets.all(8.0),
            child: Text(
              "$a+$b",
              style: TextStyle(fontSize: 24),
            ),
          ),
        );
      },
    );
  }
}

//键盘
class KeyPad extends StatelessWidget {
  final StreamController _controller;
  KeyPad(this._controller, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      shrinkWrap: true, //不让GridView上下占满
      padding: EdgeInsets.all(0.0), //取消默认下方留空(默认底部适配小白条啥的)
      physics: NeverScrollableScrollPhysics(), //让GridView不能滚动
      childAspectRatio: 2 / 1,
      crossAxisCount: 3,
      children: List.generate(9, (index) {
        // ignore: deprecated_member_use
        return FlatButton(
            shape: RoundedRectangleBorder(), //取消圆角边
            color: Colors.primaries[index][200],
            onPressed: () {
              _controller.add(index + 1);
            },
            child: Text('${index + 1}', style: TextStyle(fontSize: 24)));
      }),
    );
  }
}
 

若无特殊说明,此文章为博主原创。
写稿不易,如需转载,请注明出处: https://www.aaroner.cn/art/55.html




SITE MAP

  FOLLOW US