Flutter仿学习强国填空题

下面是学习强国的效果
在这里插入图片描述

然后看下我实现的效果
在这里插入图片描述

重点有两个部分:

  1. 使用RichText,然后根据答案的长度,动态设置需要填空的个数。RichText中有WidgetSpan,使用这个就能方便地在RichText中添加自定义的控件,Android中应该不能这么简单地实现。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  2. 设置一个占位的TextField,它的宽度为0,也就是相当于输入框是隐藏的状态,用于获取用户的输入
    在这里插入图片描述

下面是完整代码,这个代码是简化后的,因为实际项目中逻辑会复杂很多。实际项目中因为有填空题,单选题和多选题,挑战答题,需要分组件去开发,可以使用provider来实现各个组件间的通信。在挑战答题中还有一些动画效果。如果想完全仿学习强国的业务逻辑,还是有些复杂的。

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

//import 'learn_color_m.dart';

///填空题
//GlobalKey<_FillQuestionState> fillQuestionKey = GlobalKey();

class FillQuestion extends StatefulWidget {
  final fillQuesCallBack;

  FillQuestion({Key key, this.fillQuesCallBack}) : super(key: key);

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

class _FillQuestionState extends State<FillQuestion> {
  TextEditingController controller = new TextEditingController();
  FocusNode textFieldFocusNode = new FocusNode();
  double boxSize;

  int answerCount; //答案的字数
  bool isFocus = false; //用户是否点击了填空的方框:因为进入的时候第一个方框是不显示边框的,当用户点击了方框第一个方框就显示边框
  String fillAnswer; //题目的答案
  String answerValue = ""; //用户输入的答案


//  String stem = "";
  String stem1 =
      "要大力发展文学艺术、新闻出版、广播影视等事业,弘扬民族优秀文化,吸收外国文化有益成果,加强"
  ;
  String stem2 = ",加强文化市场管理,营造良好的文化环境,不断提高人民群众的文化生活质量,使人民群众充分享受自己创造的物质文化成果。";
  String fillResult = "文化基础设施建设";
  String questionId = "";
  @override
  void initState() {
    super.initState();
    answerCount = fillResult.length;
    boxSize = 25;
  }

  @override
  Widget build(BuildContext context) {
    return _buildColumn();
  }

  Widget _buildColumn() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.start,
      children: <Widget>[
        RichText(
          strutStyle: StrutStyle(
            forceStrutHeight: false,
            height: 3,
          ),
          text: TextSpan(
            text: stem1,
            style: TextStyle(color: Colors.black, fontSize:15),
            children: <InlineSpan>[
              WidgetSpan(child: _buildPlaceholder()),
              TextSpan(
                children: _buildSpans(fillResult.length, answerValue, isFocus),
              ),
              TextSpan(text: stem2, style: TextStyle(color: Colors.black, fontSize: 15)),
            ],
          ),
        ),
      ],
    );
  }

  List<InlineSpan> _buildSpans(int count, String answerStr, bool isFocus) {
    List<InlineSpan> children = new List();
    TextSpan textSpan = new TextSpan(text: "  ");
    children.add(textSpan);
    int answerLength = answerStr.length;
    for (int i = 0; i < count; i++) {
      if (i < answerLength) {
        bool isShowBorder = false;
        if (i == count - 1) {
          ///如果所有空都填了,最后一个方框显示边框
          isShowBorder = true;
        }
        WidgetSpan widgetSpan = new WidgetSpan(child: _buildSpanChild(answerStr[i], isShowBorder));
        children.add(widgetSpan);
      } else {
        bool isShowBorder = false;
        if (i == answerLength && isFocus) {
          isShowBorder = true;
        }
        WidgetSpan widgetSpan = new WidgetSpan(child: _buildSpanChild(" ", isShowBorder));
        children.add(widgetSpan);
      }
      TextSpan textSpan = new TextSpan(text: "  ");
      children.add(textSpan);
    }
    return children;
  }

  Widget _buildSpanChild(String dataText, bool isShowBorder) {
    return GestureDetector(
      onTap: () {
        ///用户点击方框
        ///如果已经回答的情况,此时不应再弹出键盘(这里只关心回答错误的情况,因为回答正确直接跳入下一题)
//        if (isConfirm) {
//          return;
//        }
        if (!isFocus) {
          ///如果是首次点击方框,就把第一个方框的边框显示出来
          setState(() {
            isFocus = true;
            answerValue = "";
          });
        }
        ///获取键盘的焦点,并且主动调起键盘
        FocusScope.of(context).requestFocus(textFieldFocusNode); // 获取焦点
        SystemChannels.textInput.invokeMethod<void>('TextInput.show'); //主动调起键盘,否则按返回键隐藏键盘后不能再次弹出
      },
      child: Container(
        alignment: Alignment.center,
        width: boxSize,
        height: boxSize,
        child: Text(
          dataText,
          style: getTextStyle(),
        ),
        decoration: getBoxDecoration(isShowBorder),
      ),
    );
  }

  getTextStyle() {
    Color color;

    ///确定了
//    if (isConfirm) {
//      ///正确了,显示绿色;错误了,显示红色
//      color = isCorrect ? Color(0xff3EBE77) : Color(0xffF6444D);
//    }

    ///还未确定
//    else {
      color = Color(0xFF4b90c5);
//    }
    return TextStyle(color: color, fontSize: 15);
  }

  getBoxDecoration(bool isShowBorder) {
    Border border;

    ///确定了
//    if (isConfirm) {
//      ///正确了,显示绿色;错误了,显示红色
//      border =
//          new Border.all(color: isCorrect ? Color(0xff3EBE77) : Color(0xffEEA6A5), width: ScreenUtil().setWidth(1));
//    }

    ///还未确定
//    else {
      border =
          isShowBorder ? new Border.all(color: Color(0xFFdbefff), width: 1) : null;
//    }
    return BoxDecoration(
      border: border,
      color: Color(0xFFF2F3F5),
    );
  }

  ///这个输入框不显示,只是用于获取用户的输入
  Widget _buildPlaceholder() {
    return Container(
      width: 0,
      child: TextField(
          decoration: InputDecoration(
            counterText: '',
            fillColor: Colors.transparent,
          ),
          showCursor: false,
          style: TextStyle(color: Colors.transparent),
          maxLength: answerCount,
          focusNode: textFieldFocusNode,
          controller: controller,
          onChanged: (value) {
            setState(() {
              answerValue = value; //更新用户输入的答案,实时将答案显示在方框中
            });
          }),
    );
  }
}

如果这篇文章帮助到了大家,麻烦帮忙点个赞,谢谢!!

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值