在上一篇文章中 ListView 嵌套 ListView 滾動問題提到 你們平時都作過問卷、試卷,知道題目列表包含:大題的題目,小題的題目,小題的選項等,作這種佈局須要用到很多組件,首先是列表ListView來顯示題目列表和選項列表,選項中包括單選和多選,這就用到了Radio和Checkbox,題目中有些是簡答題,這就用到了文本框TextField,能夠設置多行或者單行。html
具體邏輯用語言文字描述起來太費勁,直接上代碼吧,有些邏輯承接上一篇文章,這裏就省略掉了。segmentfault
// 詳細信息 初始化默認爲"" Map questionnaireDetail = { "title": '', "startDate": '', "endDate": '', "remark": '', }; // 列表視圖(`ListView`)中要顯示的數據。 List questionList = new List(); ScrollController _scrollController = new ScrollController();
// 調用接口獲取詳情和題目選項數據,具體代碼邏輯略
@override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("問卷詳情"), ), body: GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { //點擊空白處取消TextField焦點 觸摸收起鍵盤 FocusScope.of(context).requestFocus(FocusNode()); }, child: new ListView( shrinkWrap: true, //是否根據子widget的總長度來設置ListView的長度,默認值爲false controller: _scrollController, children: <Widget>[ Padding( padding: const EdgeInsets.all(10.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ new Text( questionnaireDetail["title"], overflow: TextOverflow.ellipsis, // 文字超長時顯示爲省略號... maxLines: 2, // 設置最多顯示兩行文字 style: TextStyle( color: Color.fromRGBO(0, 0, 0, 1.0), //opacity:不透明度 fontFamily: 'PingFangBold', fontSize: 15.0, ), ), Container( child: _buildList(), ), ], ), ), ], ), ), ); }
Widget _buildList() { return ListView.builder( shrinkWrap: true, //是否根據子widget的總長度來設置ListView的長度,默認值爲false physics: new NeverScrollableScrollPhysics(), // 禁用問題列表子組件的滾動事件 //itemCount +1 爲了顯示加載中和暫無數據progressbar itemCount: questionList.length + 1, itemBuilder: (context, index) { // 列表顯示 return Container( padding: new EdgeInsets.fromLTRB(10, 5, 10, 5), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ new Text( questionList[index]["title"]), Offstage( // 控制 最多可選maxChoice項組件 是否隱藏 offstage: questionList[index]['questionType'] == "多選題" && questionList[index]["maxChoice"] != 0 ? false : true, child: new Text('(最多可選:' + questionList[index]["maxChoice"].toString() + "項)"), ), questionList[index]['questionType'] == "單選題" ? _buildRadioChoiceRow(questionList[index]) : questionList[index]['questionType'] == "多選題" ? _buildCheckboxChoiceRow(questionList[index], questionList[index]["maxChoice"]) : _buildTextControllerRow(questionList[index]), ], ), ); }, ); }
// 構建 單選框Radio 單選題選項列表 組件 Widget _buildRadioChoiceRow(question) { return new ListView.builder( physics: new NeverScrollableScrollPhysics(), // 禁用選項列表子組件的滾動事件 shrinkWrap: true, //是否根據子widget的總長度來設置ListView的長度,默認值爲false itemCount: question['options'].length, itemBuilder: (context, index) { var optionContent = question['options'][index]["optionContent"]; if (optionContent.indexOf("#OTHER#") == -1) { // print('不是其餘: ' + optionContent.indexOf("#OTHER#").toString()); return _radioListItem(question, optionContent, index, optionContent); } else { // 其餘選項 帶輸入框 var radioTitle = optionContent.replaceAll("#OTHER#", ""); // print('其餘的文字: ' + radioTitle); // print('其餘: ' + optionContent.indexOf("#OTHER#").toString()); return Row( children: <Widget>[ Container( width: 150, child: _radioListItem(question, optionContent, index, radioTitle), ), Container( width: MediaQuery.of(context).size.width - 200, color: const Color(0xFFFFFFFF), // 其餘選項 輸入框 child: _buildTextOtherController(question), ) ], ); } }, ); }
Widget _radioListItem(question, optionContent, optionIndex, radioTitle) { return new Row( children: <Widget>[ // 此處也能夠使用RadioListTile,可是這個組件不知足咱們這邊的需求,因此本身後來寫了佈局 new Radio( value: question['options'][optionIndex]['id'], // 該值爲string類型 groupValue: question['groupValue'], // 與value同樣是選中 onChanged: (val) { // 收起鍵盤 FocusScope.of(context).requestFocus(FocusNode()); setState(() { question['groupValue'] = val; // print('選中了: ' + val.toString()); }); }, ), Expanded( // Row的子元素Text實現換行 須要加Expanded child: Text( radioTitle, softWrap: true, // 自動換行 ), ), ], ); }
// 構建 複選框Checkbox 多選題選項列表 組件 Widget _buildCheckboxChoiceRow(question, maxChoice) { return new ListView.builder( physics: new NeverScrollableScrollPhysics(), // 禁用選項列表子組件的滾動事件 shrinkWrap: true, //是否根據子widget的總長度來設置ListView的長度,默認值爲false itemCount: question['options'].length, itemBuilder: (context, index) { var optionContent = question['options'][index]["optionContent"]; if (optionContent.indexOf("#OTHER#") == -1) { return _checkboxListItem( question, maxChoice, optionContent, index, optionContent); } else { // 其餘選項 帶輸入框 var checkboxTitle = optionContent.replaceAll("#OTHER#", ""); // print('其餘的文字: ' + checkboxTitle); return new Row( children: <Widget>[ Container( width: 150, child: _checkboxListItem( question, maxChoice, optionContent, index, checkboxTitle), ), Container( width: MediaQuery.of(context).size.width - 200, color: const Color(0xFFFFFFFF), // 其餘選項 輸入框 child: _buildTextOtherController(question), ) ], ); } }, ); }
Widget _checkboxListItem( question, maxChoice, optionContent, optionIndex, checkboxTitle) { return new Row( children: <Widget>[ // 此處也能夠使用CheckboxListTile,可是這個組件不知足咱們這邊的需求,因此後來本身寫了佈局 Checkbox( value: question['options'][optionIndex] ['isCheck'], // 該值爲bool類型 false即不選中 onChanged: (isCheck) { // 收起鍵盤 FocusScope.of(context).requestFocus(FocusNode()); _checkMaxChoise(question, maxChoice, optionIndex, isCheck); }, ), Expanded( // Row的子元素Text實現換行 須要加Expanded child: Text( checkboxTitle, softWrap: true, // 自動換行 ), ), ], ); }
// 多選題 判斷maxChoice最多選擇項的邏輯 void _checkMaxChoise(question, maxChoice, optionIndex, isCheck) { setState(() { var optionId = question['options'][optionIndex]['id']; question['options'][optionIndex]['isCheck'] = isCheck; if (isCheck) { // print('選中了: ' + optionId); question['checked'].add(optionId); if (maxChoice != 0 && question['checked'].length > maxChoice) { question['checked'].remove(optionId); question['options'][optionIndex]['isCheck'] = false; showToast("當前選中數已超過本題的最大選項數"); } // print('選中的: ' + question['checked'].toString()); } else { question['checked'].remove(optionId); // print('選中的: ' + question['checked'].toString()); } }); }
// 構建 輸入框行 簡答題 組件 Widget _buildTextControllerRow(question) { return new Padding( padding: const EdgeInsets.all(8.0), child: Container( color: const Color(0xFFFFFFFF), padding: EdgeInsets.only(left: 8.0), child: _buildTextField(question['textController']), ); }
// 構建 選項的其餘輸入框 組件 Widget _buildTextOtherController(question) { return _buildTextField(question['textOtherController']); }
// 構建 輸入框 組件 Widget _buildTextField(controller) { // 文本字段(`TextField`)組件,容許用戶使用硬件鍵盤或屏幕鍵盤輸入文本。 return new TextField( cursorColor: const Color(0xFFFE7C30), cursorWidth: 2.0, keyboardType: TextInputType.multiline, //多行 decoration: InputDecoration( contentPadding: EdgeInsets.all(10.0), // 圓角矩形的邊框 border: OutlineInputBorder( borderRadius: BorderRadius.circular(10.0), ), ), controller: controller, // 控制正在編輯的文本 ); }
TextField class
Radio<T> class
Checkbox class
flutter筆記(九)-----單選框Radio、RadioListTile
Flutter學習之旅——實用入坑指南api
Flutter 點擊空白處取消TextField焦點並收起鍵盤app