下面是學習強國的效果
然後看下我實現的效果
重點有兩個部分:
-
使用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; //更新用戶輸入的答案,實時將答案顯示在方框中
});
}),
);
}
}