Dart: секунды в строку вида hh:mm:ss
Задача: преобразовать и отобразить секунды в строку часы-минуты-секунды
Решение:
Бонусом тоже самое на javascript:
Жизнь замечательных грибов
Задача: преобразовать и отобразить секунды в строку часы-минуты-секунды
Решение:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
String TimeLeft(diff){ var timestamp = diff.floor(); var hours = (timestamp / 60 / 60).floor(); var minutes = ((timestamp / 60) - (hours * 60)).floor(); var seconds = timestamp % 60; if (seconds<0){ seconds=0;hours=0;minutes=0; } String hs=hours.toString(); hs=hs.length>1?hs:"0"+hs; String ms=minutes.toString(); ms=ms.length>1?ms:"0"+hs; String ss=seconds.toString(); ss=ss.length>1?ss:"0"+ss; return "$hs:$ms:$ss"; } |
Бонусом тоже самое на javascript:
1 2 3 4 5 6 7 8 9 10 |
function TimeLeft(diff){ var timestamp = Math.floor(diff); var hours = Math.floor(timestamp / 60 / 60); var minutes = Math.floor(timestamp / 60) - (hours * 60); var seconds = timestamp % 60; if (seconds<0){seconds=0;hours=0;minutes=0;} h=("0"+hours).slice(-2); m=("0"+minutes).slice(-2); s=("0"+seconds).slice(-2); }; |
Обычно радиокнопки во Flutter обертываю виджетом ListTile. Однако в этом случае получаются слишком большие отступы между кнопками, которые обычными средствами не убираются. В моем варианте, кнопки размещаются просто в Row строке:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
enum ChangingTime {t30a,t30,t60,t90,t120,t150,t180} ... ChangingTime? _chtime = ChangingTime.t30a; ... Container RadioLine(String title,ChangingTime rvalue){ return Container( padding: const EdgeInsets.only(bottom: 0,left: 0,right: 0,top: 0), height: 20, child: Row( children: [ Expanded( child: Padding( padding: const EdgeInsets.only(bottom: 0,left: 8,right: 8,top: 0), child: Text(title), ) ), Radio<ChangingTime>( value: rvalue, groupValue: _chtime, onChanged: (ChangingTime? value) { setState(() { _chtime = value; print(value); }); }, ), ], ) ); } ... ... RadioLine("30 минут с автопродлением",ChangingTime.t30a), new Divider(), RadioLine("30 минут",ChangingTime.t30), new Divider(), RadioLine("1 час",ChangingTime.t60), |
Получается вполне приемлимое:
Одно но. В таком случае выбор кнопки будет только по щелчку по переключателю, а не по тексту. Как вариант можно попробовать RadioListTile, но с ним отступы до желаемого минимума мне убрать не удалось.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
RadioListTile RadioLine2(String title,ChangingTime rvalue){ return RadioListTile<ChangingTime>( title:Text( title, ), dense: true, value: rvalue, groupValue: _chtime, visualDensity: VisualDensity( horizontal: VisualDensity.minimumDensity, vertical: VisualDensity.minimumDensity, ), controlAffinity: ListTileControlAffinity.trailing, onChanged: (ChangingTime? value) { setState(() { _chtime = value; print(value); }); }, ); } |
Задача: показать двухуровневый список с анимацией отображения выбора второго уровня.
Что сделаем? Отображением займутся два вложенных друг в друга ListView.builder. Отлавливает выбор модели при помощи GestureDetector и при отрисовке второго ListView.builder со списокм автомобилей, показываем только совпадающие с моделью. Также отображение моделей обертываем в AnimatedOpacity для плавного показа.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
class _AddAutoState extends State<AddAuto> { final AddAuto_scaffoldKey = GlobalKey<ScaffoldState>(); bool auto_list_is_loading=false; List ListAutoes=[]; List FilteredListAutoes=[]; int cur_model_select=0; void LoadListAutoes(context){ TRequests req=new TRequests(); req.request("GetListVendorsAndModels", jsonEncode({}), (List result){ setState(() { ListAutoes=result; FilteredListAutoes=result; auto_list_is_loading=true; }); }, (String error){ EasyLoading.showToast(error); }); } @override void initState() { WidgetsBinding.instance.addPostFrameCallback((_) => LoadListAutoes(context)); // эвент после того как страница отобразилась - обновим данные по пользователю } @override Widget build(BuildContext context) { return Scaffold( key: AddAuto_scaffoldKey, appBar: AppBar( iconTheme: IconThemeData(color: Colors.black), backgroundColor: Colors.white, //automaticallyImplyLeading: false, // убрать кнопку "назад" title: Text('Добавление автомобиля', style: TextStyle(fontFamily: 'Poppins', color: Colors.black, fontSize: 22,),), actions: [], centerTitle: false, elevation: 2, ), body: SingleChildScrollView( child: Column( children: [ Visibility( visible: auto_list_is_loading, child: Container( padding: const EdgeInsets.all(8), child: ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: const EdgeInsets.all(0), itemCount: FilteredListAutoes.length, itemBuilder: (BuildContext context, int index) { return GestureDetector( onTap: () { setState(() { print("тыкнули по модели автомобиля.."); cur_model_select=index; }); }, child: Column( children: [ Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ Container( height: 16, width: 16, child: Transform.rotate( angle: index==cur_model_select?90 * 3.14/180:0, child: IconButton ( padding: const EdgeInsets.all(0), icon: SvgPicture.asset('lib/images/arrow-right-small.svg',height: 16,width: 16,), onPressed: () {print('IconButton pressed ...');}, ), ) ), Text( FilteredListAutoes[index]["name"]+" ("+FilteredListAutoes[index]["cars"].length.toString()+")", style: TextStyle( fontWeight: index==cur_model_select?FontWeight.bold:FontWeight.w100, ) ) ], ), // рисуем список автомобилей AnimatedOpacity( opacity: index==cur_model_select ? 1.0 : 0.0, duration: const Duration(milliseconds: 1500), child: ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: const EdgeInsets.all(0), itemCount: FilteredListAutoes[index]["cars"].length, itemBuilder: (BuildContext context, int index2) { return Visibility( visible: index==cur_model_select?true:false, child: Padding( padding: const EdgeInsets.only(left: 20,top: 4), child: GestureDetector( onTap: () { print("тыкнули по автомобилю.."+index2.toString()); }, child: Text(FilteredListAutoes[index]["cars"][index2]["name"]), ) ) ); } ), ), new Divider() ] ) ); } ), ) ), Visibility( visible: !auto_list_is_loading, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ CardLoading( height: 30, borderRadius: BorderRadius.all(Radius.circular(15)), width: 100, margin: EdgeInsets.only(bottom: 10), ), CardLoading( height: 100, borderRadius: BorderRadius.all(Radius.circular(15)), margin: EdgeInsets.only(bottom: 10), ), ] ) ), ], ) ) ); } } |
А именно, проблема заключается в том, что ничего не отображается, пока listview не будет обёрнут в контейнер с указанной высотой. И это на самом деле проблема, т.к. в этом случае нельзя указать «резиновую» высоту. Обёртывание в виджет Expanded тоже не поможет. Остаётся один выход — рассчитывать высоту в случае динамического списка. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
Container( height: EzsInfo["connectors"].length.toDouble()*72, width: double.maxFinite, child: ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: const EdgeInsets.all(8), itemCount: EzsInfo["connectors"].length, itemBuilder: (BuildContext context, int index) { return Container( //padding: EdgeInsets.symmetric(vertical: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(bottom: 0,left: 0,right: 10), child: Text(EzsInfo["connectors"][index]["station_name"],style: TextStyle(fontWeight: FontWeight.w100, fontSize: 8.0, color: Colors.black)), ), Container( width: 6.0, height: 6.0, decoration: BoxDecoration( color: EzsInfo["connectors"][index]["status"]==3?Colors.grey:Colors.green, shape: BoxShape.circle, ),), Padding( padding: const EdgeInsets.only(bottom: 0,left: 2,right: 0), child: Text(EzsInfo["connectors"][index]["status_name"],style: TextStyle(fontWeight: FontWeight.w100, fontSize: 8.0, color: Colors.black)), ), ], ), Row( children: [ IconButton ( padding: const EdgeInsets.only(bottom: 0,left: 0,right: 0), icon: Image.asset('lib/images/'+EzsInfo["connectors"][index]["url_connector"],height: 32,), onPressed: () {print('IconButton pressed ...');}, ), Container( alignment: Alignment.topLeft, width: 150, child: Column( children: [ Align( alignment: Alignment.topLeft, child: Text(EzsInfo["connectors"][index]["connector_name"],style: TextStyle(fontSize: 12.0, color: Colors.black),textAlign: TextAlign.left,), ), Align( alignment: Alignment.topLeft, child: Text(EzsInfo["connectors"][index]["maxpower"].toString()+" кВт",style: TextStyle(fontSize: 12.0, color: Colors.black),textAlign: TextAlign.left), ), ], ), ), Text(EzsInfo["connectors"][index]["price"].toString()+"Р кВт*ч",style: TextStyle(fontSize: 12.0, color: Colors.black)), ], ), new Divider(), ], ) ); } ), ) |
Результат:
Задача: при отображении данных при помощи виджета showDialog, необходимо показывать «прелоадер» во время загрузки данных со стороннего ресурса.
Решение: для отображения прелоадера, используем пакет card_loader. Однако! дело осложняется тем, что виджет showDialog не имеет метода setState, соответственно у нас нет возможности изменить уже отображенные данные, после окончания загрузки.
Для обхода этого ограничения, оформим showDialog как полноценный StatefulWidget с получением в связи с этим стандартных плюшек в виде setState, а так-же возможность вызова своих процедур после окончания отображения виджета:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
import 'package:flutter/material.dart'; import 'package:card_loading/card_loading.dart'; import 'dart:convert'; class MyMeDialogInfo extends StatefulWidget { @override State<StatefulWidget> createState() { return _MyMeDialogInfoState(); } } class _MyMeDialogInfoState extends State<MyMeDialogInfo> { bool info_is_load=false; void RefreshMeDialogData(context){ TRequests req=new TRequests(); req.request("adfserfserf", {}), (List result){ setState(() { //info_is_load=true; }); }, (String error){ }); } @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => RefreshMeDialogData(context)); // эвент после того как страница отобразилась - обновим данные по пользователю } @override Widget build(BuildContext context) { return Align ( alignment: Alignment.bottomCenter, child: Container( width: double.infinity, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(32.0), topRight: Radius.circular(32.0))), child:Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ // заголовок окна Padding( padding: const EdgeInsets.all(16.0), child: Material( child: Text("Заголовок окна", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0, color: Colors.black))), ), Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height - 220, decoration: BoxDecoration(), child: SingleChildScrollView( child: Stack( children: <Widget>[ Visibility( visible: info_is_load, child: Padding( padding: const EdgeInsets.all(16.0), child: Material( child: Text("Тут перечисляем какую инфу загружаемую асинхронно", style: TextStyle(fontSize: 14.0, color: Colors.black))), ) ), Visibility( visible: !info_is_load, child: Padding( padding: const EdgeInsets.only(bottom: 20,left: 20,right: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ CardLoading( height: 30, borderRadius: BorderRadius.all(Radius.circular(15)), width: 100, margin: EdgeInsets.only(bottom: 10), ), CardLoading( height: 100, borderRadius: BorderRadius.all(Radius.circular(15)), margin: EdgeInsets.only(bottom: 10), ), CardLoading( height: 30, width: 200, borderRadius: BorderRadius.all(Radius.circular(15)), margin: EdgeInsets.only(bottom: 10), ), ], ), ) ) ], ), ), ) ] ) ) ); } } class TMeDialog { void MeDialogInfoDialog(BuildContext context,Map ezs){ showDialog( context: context, builder: (BuildContext context) { return MyMeDialogInfo(ezs); }, ); } } |