美文网首页
flutter 实战App 之 提词器app

flutter 实战App 之 提词器app

作者: 微风_10a5 | 来源:发表于2021-11-30 23:20 被阅读0次

    手撕一个app,提词器,模仿FeelWorld app的核心功能实现,最终效果如下:


    ticiqiapp.gif image.png
    配合蓝牙手柄使用的效果如下:
    666.gif

    功能如上图所示

    播放速度设置
    字体大小设置
    字体颜色及背景颜色设置
    文本镜像设置
    是否循环播放设置
    强制横屏显示设置
    。。。

    所有代码如下:

    import 'dart:async';
    import 'dart:math';
    
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    import 'package:flutter_test_demos/custom_drawer.dart';
    
    class TiciqiPage extends StatefulWidget {
      @override
      _TiciqiPageState createState() => _TiciqiPageState();
    }
    
    class _TiciqiPageState extends State<TiciqiPage> {
      ScrollController _scrollController = ScrollController();
      double _screenHeight = 0.0;
      int _counter = 0;
      Timer _timer = null;
      bool _isPaused = false;
      double _speedValue = 5;
      bool _mirror = false;
      bool _loop = false;
      bool _vertical = false;
      int _textSelectedIndex = 0;
      int _fontSize = 20;
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        _startTimer();
      }
    
      _startTimer() {
        if (_timer == null) {
          _timer = Timer.periodic(Duration(seconds: 1), (timer) {
            _scrollController.animateTo((_counter * _speedValue * 3).toDouble(),
                duration: Duration(milliseconds: 250), curve: Curves.easeIn);
            _counter++;
    
            print("counter= $_counter");
          });
        }
      }
    
      _pauseTimer() {
        if (_timer != null) {
          _timer.cancel();
          _timer = null;
        }
      }
    
      _stopTimer() {
        if (_timer != null) {
          _counter = 0;
          _timer.cancel();
          _timer = null;
        }
      }
    
      Color _getScaffoldBackgroundColor() {
        Color desColor = Colors.black;
        if (_textSelectedIndex == 1) {
          desColor = Colors.white;
        } else if (_textSelectedIndex == 2) {
          desColor = Colors.green;
        } else if (_textSelectedIndex == 3) {
          desColor = Colors.pink;
        } else if (_textSelectedIndex == 4) {
          desColor = Colors.blue;
        }
        return desColor;
      }
    
      Color _getTextColor() {
        Color desColor = Colors.white;
        if (_textSelectedIndex == 1) {
          desColor = Colors.black;
        } else if (_textSelectedIndex == 2) {
          desColor = Colors.white;
        } else if (_textSelectedIndex == 3) {
          desColor = Colors.white;
        } else if (_textSelectedIndex == 4) {
          desColor = Colors.white;
        }
        return desColor;
      }
    
      @override
      Widget build(BuildContext context) {
        _screenHeight = MediaQuery.of(context).size.height;
    
        return Scaffold(
          backgroundColor: _getScaffoldBackgroundColor(),
          drawer: _drawer(),
          body: NotificationListener<ScrollNotification>(
            onNotification: (ScrollNotification notification) {
              double progress = notification.metrics.pixels /
                  notification.metrics.maxScrollExtent;
              if (progress >= 1) {
                _stopTimer();
    
                if (_loop) {
                  Future.delayed(Duration(seconds: 1), () {
                    _startTimer();
                  });
                }
              }
    
              print("BottomEdge: ${notification.metrics.extentAfter == 0}");
              return false;
              //return true; //放开此行注释后,进度条将失效
            },
            child: Stack(
              alignment: Alignment.center,
              children: [
                Transform(
                  // Transform widget
                  transform: Matrix4.rotationX(_mirror ? pi : 0),
                  alignment: FractionalOffset.center,
                  child: Container(
                    height: _screenHeight,
                    child: ListView(
                      controller: _scrollController,
                      children: [_itemRender()],
                    ),
                  ), // <<< set your widget here
                ),
                Container(
                  color: Colors.red,
                  height: 1,
                  alignment: Alignment.center,
                ),
              ],
            ),
          ),
          floatingActionButton: Builder(
            builder: (BuildContext context) {
              return FloatingActionButton(
                child: IconButton(
                  icon: Icon(Icons.refresh),
                  onPressed: () {
                    print("refresh");
                    //重新开始(循环)
    
                    // if(!_isPaused){//暂停与播放
                    //   _isPaused = true;
                    //   _pauseTimer();
                    // }else{
                    //   _isPaused = false;
                    //   _startTimer();
                    // }
    
                    //镜像
                    Scaffold.of(context).openDrawer();
    
                    // _handlerDrawerButton(context);
                  },
                ),
              );
            },
          ),
        );
      }
    
      Widget _drawerHeader() {
        return Builder(
          builder: (BuildContext context) {
            return Container(
                alignment: Alignment.centerLeft,
                width: double.maxFinite,
                // color: Colors.red,
                height: 100,
                child: TextButton(
                  style: ButtonStyle(
                      foregroundColor: MaterialStateProperty.all(Colors.black)),
                  onPressed: () {
                    Scaffold.of(context).openEndDrawer();
                  },
                  child: Row(
                    children: [Icon(Icons.arrow_back_ios), Text("设置")],
                  ),
                ));
          },
        );
      }
    
      Widget _drawerFooter() {
        return InkWell(
          onTap: () {
            print("_drawerFooter");
          },
          child: Container(
              alignment: Alignment.center,
              width: double.maxFinite,
              // color: Colors.red,
              height: 100,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  _createDivider(),
                  SizedBox(
                    height: 10,
                  ),
                  Text(
                    "恢复默认设置",
                    style: TextStyle(color: Colors.black),
                  )
                ],
              )),
        );
      }
    
      Widget _drawerMiddle() {
        return Expanded(
          child: ListView(
            children: [
              _createSliderItem(title: "播放速度", leftString: "慢", rightString: "快"),
              _createFontSizeSliderItem(
                  title: "字体大小", leftString: "小", rightString: "大"),
              // _createSliderItem(
              //     title: "文本显示位置", leftString: "0", rightString: "10"),
              _createSFontItem(),
              _createTextSwitchItem(title: "文本镜像"),
              _createLoopSwitchItem(title: "循环播放"),
              _createVerticalSwitchItem(title: "横屏显示"),
            ],
          ),
        );
      }
    
      Widget _createSliderItem(
          {String title, String leftString, String rightString}) {
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _createDivider(),
            SizedBox(
              height: 10,
            ),
            Text(title),
            Container(
              // color: Colors.red,
              child: Row(
                children: [
                  Text(leftString),
                  SliderTheme(
                    data: SliderThemeData(
                      tickMarkShape: RoundSliderTickMarkShape(tickMarkRadius: 2),
    
                      trackHeight: 5,
                      // activeTrackColor: Colors.red,
                      // inactiveTrackColor: Colors.grey,
                      // disabledActiveTrackColor: Colors.yellow,
                      // disabledInactiveTrackColor: Colors.cyan,
                      // activeTickMarkColor: Colors.black,
                      // inactiveTickMarkColor: Colors.red,
                      // overlayColor: Colors.yellow,
                      // overlappingShapeStrokeColor: Colors.black,
                      // overlayShape: RoundSliderOverlayShape(),
                      // valueIndicatorColor: Colors.red,
                      // showValueIndicator: ShowValueIndicator.onlyForDiscrete,
                      // minThumbSeparation: 100,
                      //
                      // rangeTrackShape: RoundedRectRangeSliderTrackShape(),
                    ),
                    child: Slider(
                        divisions: 20,
                        label: "${_speedValue.toInt()}",
                        min: 5,
                        max: 20,
                        value: _speedValue,
                        onChanged: (value) {
                          print("value = $value");
                          _speedValue = value;
                          setState(() {});
                        }),
                  ),
                  Text(rightString),
                ],
              ),
            )
          ],
        );
      }
    
      Widget _createFontSizeSliderItem(
          {String title, String leftString, String rightString}) {
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _createDivider(),
            SizedBox(
              height: 10,
            ),
            Text(title),
            Container(
              // color: Colors.red,
              child: Row(
                children: [
                  Text(leftString),
                  SliderTheme(
                    data: SliderThemeData(
                      tickMarkShape: RoundSliderTickMarkShape(tickMarkRadius: 2),
    
                      trackHeight: 5,
                      // activeTrackColor: Colors.red,
                      // inactiveTrackColor: Colors.grey,
                      // disabledActiveTrackColor: Colors.yellow,
                      // disabledInactiveTrackColor: Colors.cyan,
                      // activeTickMarkColor: Colors.black,
                      // inactiveTickMarkColor: Colors.red,
                      // overlayColor: Colors.yellow,
                      // overlappingShapeStrokeColor: Colors.black,
                      // overlayShape: RoundSliderOverlayShape(),
                      // valueIndicatorColor: Colors.red,
                      // showValueIndicator: ShowValueIndicator.onlyForDiscrete,
                      // minThumbSeparation: 100,
                      //
                      // rangeTrackShape: RoundedRectRangeSliderTrackShape(),
                    ),
                    child: Slider(
                        divisions: 20,
                        label: "$_fontSize",
                        min: 20,
                        max: 50,
                        value: _fontSize.toDouble(),
                        onChanged: (value) {
                          print("value = $value");
                          _fontSize = value.toInt();
                          setState(() {});
                        }),
                  ),
                  Text(rightString),
                ],
              ),
            )
          ],
        );
      }
    
      Widget _createSpeedSliderItem(
          {String title, String leftString, String rightString}) {
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _createDivider(),
            SizedBox(
              height: 10,
            ),
            Text(title),
            Container(
              // color: Colors.red,
              child: Row(
                children: [
                  Text(leftString),
                  SliderTheme(
                    data: SliderThemeData(
                      tickMarkShape: RoundSliderTickMarkShape(tickMarkRadius: 2),
    
                      trackHeight: 5,
                      // activeTrackColor: Colors.red,
                      // inactiveTrackColor: Colors.grey,
                      // disabledActiveTrackColor: Colors.yellow,
                      // disabledInactiveTrackColor: Colors.cyan,
                      // activeTickMarkColor: Colors.black,
                      // inactiveTickMarkColor: Colors.red,
                      // overlayColor: Colors.yellow,
                      // overlappingShapeStrokeColor: Colors.black,
                      // overlayShape: RoundSliderOverlayShape(),
                      // valueIndicatorColor: Colors.red,
                      // showValueIndicator: ShowValueIndicator.onlyForDiscrete,
                      // minThumbSeparation: 100,
                      //
                      // rangeTrackShape: RoundedRectRangeSliderTrackShape(),
                    ),
                    child: Slider(
                        divisions: 15,
                        label: "$_speedValue",
                        min: 5,
                        max: 20,
                        value: _speedValue.toDouble(),
                        onChanged: (value) {
                          print("value = $value");
                          _speedValue = value;
                          setState(() {});
                        }),
                  ),
                  Text(rightString),
                ],
              ),
            )
          ],
        );
      }
    
      Widget _createSFontItem() {
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _createDivider(),
            SizedBox(
              height: 10,
            ),
            Text("字体及背景颜色"),
            SizedBox(
              height: 15,
            ),
            Container(
              // color: Colors.red,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  InkWell(
                    onTap: () {
                      _textSelectedIndex = 0;
                      setState(() {});
                    },
                    child: Container(
                        decoration: BoxDecoration(
                            border: Border.all(width: 1, color: Colors.red),
                            borderRadius: BorderRadius.circular(30)),
                        width: 30,
                        height: 30,
                        child: CircleAvatar(
                          child: Text(
                            "黑",
                            style: TextStyle(color: Colors.white, fontSize: 10),
                          ),
                          backgroundColor: Colors.black,
                        )),
                  ),
                  InkWell(
                    onTap: () {
                      _textSelectedIndex = 1;
                      setState(() {});
                    },
                    child: Container(
                        decoration: BoxDecoration(
                            border: Border.all(width: 1, color: Colors.red),
                            borderRadius: BorderRadius.circular(30)),
                        width: 30,
                        height: 30,
                        child: CircleAvatar(
                          child: Text(
                            "白",
                            style: TextStyle(color: Colors.black, fontSize: 10),
                          ),
                          backgroundColor: Colors.white,
                        )),
                  ),
                  InkWell(
                    onTap: () {
                      _textSelectedIndex = 2;
                      setState(() {});
                    },
                    child: Container(
                        decoration: BoxDecoration(
                            border: Border.all(width: 1, color: Colors.red),
                            borderRadius: BorderRadius.circular(30)),
                        width: 30,
                        height: 30,
                        child: CircleAvatar(
                          child: Text(
                            "绿",
                            style: TextStyle(color: Colors.green, fontSize: 10),
                          ),
                          backgroundColor: Colors.blueGrey,
                        )),
                  ),
                  InkWell(
                    onTap: () {
                      _textSelectedIndex = 3;
                      setState(() {});
                    },
                    child: Container(
                        decoration: BoxDecoration(
                            border: Border.all(width: 1, color: Colors.red),
                            borderRadius: BorderRadius.circular(30)),
                        width: 30,
                        height: 30,
                        child: CircleAvatar(
                          child: Text(
                            "粉",
                            style: TextStyle(color: Colors.pink, fontSize: 10),
                          ),
                          backgroundColor: Colors.pinkAccent,
                        )),
                  ),
                  InkWell(
                    onTap: () {
                      _textSelectedIndex = 4;
                      setState(() {});
                    },
                    child: Container(
                        decoration: BoxDecoration(
                            border: Border.all(width: 1, color: Colors.red),
                            borderRadius: BorderRadius.circular(30)),
                        width: 30,
                        height: 30,
                        child: CircleAvatar(
                          child: Text(
                            "蓝",
                            style: TextStyle(color: Colors.blue, fontSize: 10),
                          ),
                          backgroundColor: Colors.lightBlueAccent,
                        )),
                  ),
                  SizedBox(
                    width: 30,
                  )
                ],
              ),
            ),
            SizedBox(
              height: 20,
            ),
          ],
        );
      }
    
      Widget _createTextSwitchItem({String title}) {
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _createDivider(),
            SizedBox(
              height: 10,
            ),
            Container(
              // color: Colors.red,
              child: Row(
                children: [
                  CupertinoSwitch(
                      value: _mirror,
                      onChanged: (value) {
                        print("Switch $value");
                        _mirror = value;
                        setState(() {});
                      }),
                  Text(title),
                ],
              ),
            ),
            SizedBox(
              height: 10,
            ),
          ],
        );
      }
    
      Widget _createLoopSwitchItem({String title}) {
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _createDivider(),
            SizedBox(
              height: 10,
            ),
            Container(
              // color: Colors.red,
              child: Row(
                children: [
                  CupertinoSwitch(
                      value: _loop,
                      onChanged: (value) {
                        print("Switch $value");
                        _loop = value;
                        setState(() {});
                      }),
                  Text(title),
                ],
              ),
            ),
            SizedBox(
              height: 10,
            ),
          ],
        );
      }
    
      Widget _createVerticalSwitchItem({String title}) {
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _createDivider(),
            SizedBox(
              height: 10,
            ),
            Container(
              // color: Colors.red,
              child: Row(
                children: [
                  CupertinoSwitch(
                      value: _vertical,
                      onChanged: (value) {
                        print("Switch $value");
                        _vertical = value;
                        if (_vertical) {
                          // 强制横屏
                          SystemChrome.setPreferredOrientations([
                            DeviceOrientation.landscapeLeft,
                            DeviceOrientation.landscapeRight
                          ]);
                        } else {
                          // 强制竖屏
                          SystemChrome.setPreferredOrientations([
                            DeviceOrientation.portraitUp,
                            DeviceOrientation.portraitDown
                          ]);
                        }
                        setState(() {});
                      }),
                  Text(title),
                ],
              ),
            ),
            SizedBox(
              height: 10,
            ),
          ],
        );
      }
    
      Widget _drawer() {
        return CustomDrawer(
          child: Padding(
            padding: EdgeInsets.only(left: 20),
            child: Column(
              children: [_drawerHeader(), _drawerMiddle(), _drawerFooter()],
            ),
          ),
          widthPercent: 0.7,
        );
      }
    
      void _handlerDrawerButton(BuildContext context) {
        Scaffold.of(context).openDrawer();
      }
    
      Widget _createDivider() {
        return Divider(
          height: 1,
          thickness: 0.5,
          color: Color.fromARGB(100, 200, 200, 200),
        );
      }
    
      Widget _itemRender() {
        return Column(
          children: [
            Container(
              child: Text("start"),
              height: _screenHeight * 0.5,
            ),
            Container(
                child: ListTile(
              title: Text(
                '''对王羲之这位书圣的生平,史书记载十分有限,但他召集的那次兰亭集会,千百年来却广为传颂,因为王羲之即兴创作、记录这次盛会的《兰亭集序》不仅是文学史上的不朽之作,也是中国书法史上无人逾越的高峰。
    
                            东晋永和九年(353年)农历三月三日,天朗气清,惠风和畅,王羲之和朋友们举行了一次修禊活动,地点选择在会稽境内的兰亭。所谓“修禊”,是古代一种消除污秽的祭祀活动,一般多在农历三月三日春光明媚之时,选择水边,一方面祭神,同时大家也洗洗手脚,据说可免除邪恶不祥。这项仪式始于西周,但逐渐从最初的祭神发展到了游春赏景、饮酒赋诗的踏青活动。
    
                            兰亭雅集的参加者,据唐何延之《兰亭记》的记载,有谢安、孙绰、支遁……还有王羲之的三个儿子共四十一人,众人饮酒赋诗作文,最后由已有几分酒意的王羲之执笔为之作序。王羲之轻拈鼠须笔、铺开蚕茧纸,用他最擅长的中锋行楷,洋洋洒洒28行、324字一挥而就。在这篇短文中,王羲之既描写了兰亭优美的自然环境,又抒写了与朋友相聚的欢欣,同时也抒发了人生苦短、及时行乐、快然自足的情怀。文章理趣深远,沁人心脾,而书法更是遒媚劲健变化无穷,二十多个“之”字无一雷同,如有神助。事后,王羲之将《兰亭集序》重写了几十幅,均自叹不如原本。
    ''',
                style: TextStyle(
                    wordSpacing: 3,
                    fontSize: _fontSize.toDouble(),
                    color: _getTextColor()),
              ),
            )),
            Container(
              alignment: Alignment.bottomRight,
              height: _screenHeight,
              child: Text("end"),
            ),
          ],
        );
      }
    }
    
    

    上面的代码,有用到一个自定义的Drawer类 CustomDrawer,代码如下:

    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
    
    class CustomDrawer extends StatefulWidget {
      const CustomDrawer({
        this.backgroundColor,
        this.elevation = 16.0,
        @required this.child,
        this.widthPercent,
        this.callback,
      }) : assert(widthPercent < 1.0 && widthPercent > 0.0);
    
      final Color backgroundColor;
      final double elevation;
      final Widget child;
      final double widthPercent;
      final DrawerCallback callback;
    
      @override
      _CustomDrawerState createState() => _CustomDrawerState();
    }
    
    class _CustomDrawerState extends State<CustomDrawer> {
      @override
      void initState() {
        if (widget.callback != null) {
          widget.callback(true);
        }
        super.initState();
      }
    
      @override
      void dispose() {
        if (widget.callback != null) {
          widget.callback(false);
        }
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        assert(debugCheckHasMaterialLocalizations(context));
        final double _width =
            MediaQuery.of(context).size.width * widget.widthPercent;
        return ConstrainedBox(
          constraints: BoxConstraints.expand(width: _width),
          child: Material(
            color: widget.backgroundColor,
            elevation: widget.elevation,
            child: widget.child,
          ),
        );
      }
    }
    
    

    结尾

    今天的干货分享到此,如果小伴们,觉得有点用的话,或者已经看到这里面来的请点个赞吧,后面如果有必要完善更多细节,比如:做缓存显示等功能,会持续更新此app的相关核心代码。期待一下吧。好运~

    相关文章

      网友评论

          本文标题:flutter 实战App 之 提词器app

          本文链接:https://www.haomeiwen.com/subject/kzyjxrtx.html