下面是学习强国的效果
然后看下我实现的效果
重点有两个部分:
- 使用RichText,然后根据答案的长度,动态设置需要填空的个数。RichText中有WidgetSpan,使用这个就能方便地在RichText中添加自定义的控件,Android中应该不能这么简单地实现。
- 设置一个占位的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; //更新用户输入的答案,实时将答案显示在方框中
});
}),
);
}
}
如果这篇文章帮助到了大家,麻烦帮忙点个赞,谢谢!!