フロント側をFlutter(スマホ)Thymeleaf(PC)、バックエンド側SpringBootの自動作成勉強中
9:41
①昨日の、『Flutter(スマホ)側のダイヤログ表示時に入力日付がわかるようにマークを付ける。も含めて、完成した。多分これなら使えると思う。(罫線の外枠がちょっと細いのは、このままでいいかなって思っている。)』の、”罫線の外枠がちょっと細い”という部分をどうしたら解決できるか考えた。これならいけそう。
・曜日行セルの罫線上部を太くする。さらに、日曜の左側、土曜の右側をを太くする。
・日付セル
・日曜の左側、土曜の右側をを太くする。
・表示されている月の日付のセルで、カレンダー最下位行になる日付セルの罫線下部を太くする。
ロジックは、
・WK1=表示されている月の1日の曜日により、日:0、月:1、火:2、水:3、木:4、金:5、土:6
・WK2=(表示されている月の末日+WK1)/7日
・WK3=WK2計算の余りにより、0:6、以外は、余り-1
・WK4=表示されている月末日-WK3
カレンダー最下位行になる日付セル=WK4の日~末日
・表示されている月の翌月となる日付セルの罫線下部を太くする。
15:05
①罫線の見づらさは解消された。
・カレンダー最下位行になる日付セルの算出ロジックを、日付ダイヤログメソッド呼出し直後と、『onPageChanged: (focusedDay)』の時に行ったが、全然だめだったので、てこづった。『onPageChanged: (focusedDay)』の時では、遅い(すでに次の月表示が始まっている)みたいだった。だから、outsideBuilder、disabledBuilder、defaultBuilder、todayBuilder。dowBuilder、selectedBuilderの中でfocusedDayを見て、カレンダー最下位行になる日付セルの算出ロジックを走らせた。
・実機でも遅いのでどうだろう、使えるかどうか。でも、時代の流れでCPUは早くなるのでいいかなと思う。
②できたFlutter(スマホ)側カレンダーダイヤグラム
日付選択ボタンを押下する。日付が空白だったため今日日付に青丸がつき、赤枠(選択済み)になる。
4月28日を選択する。4月28日が赤枠(選択済み)になる。
翌月を表示する。
前月を表示する。選択してあった4月28日が赤枠(選択済み)になる。
4月29日を選択する。4月29日が赤枠(選択済み)になる。
前月を表示する。
翌月を表示する。選択してあった4月29日が赤枠(選択済み)になる。
OKボタンを押下する。項目が4月29日になる。
日付選択ボタンを押下する。4月29日に赤枠(選択済み)になる。
15:32
①できたFlutter(スマホ)側カレンダーダイヤグラムプログラム
②昨日もソース載せたけど、変わったので今日も載せておこう。思いっきり大きなPGMになってしまった感があるけどこれで、思った感じのカレンダーダイヤログになった。ので、うれしい
■Flutter(スマホ)側のopenDateDialogCommメソッド呼びもと。入力日付が入っていなくても動く。
//--テスト日付入力 start---------------------------- contentWidgets.add( Row ( // mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ // テスト日付の入力フォーム Container( margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5), padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0), child:Text(_focusedDayStr, textAlign: TextAlign.left, overflow: TextOverflow.clip, ), ), Container( margin: const EdgeInsets.fromLTRB(5, 0, 5, 5), padding: const EdgeInsets.fromLTRB(5, 0, 5, 0), child: ElevatedButton( onPressed: () async { DateTime? _date; if (_focusedDayStr == "") { _date = null; } else { _date = DateTime.utc(int.parse((_focusedDayStr).substring(0, 4)), int.parse((_focusedDayStr).substring(5, 7)), int.parse((_focusedDayStr).substring(8, 10)),0,0); } String? _str = await CommUtils.openDateDialogComm(context, DateTime.utc(1900, 1, 1), DateTime.utc(2099, 12, 31), _date); if (_str != null && _str != Consts.cancel) { setState(() { _focusedDayStr = _str; }); } }, style: ElevatedButton.styleFrom( primary: Colors.blue, ), child: const Text("日付選択"), ), ), ] ), ); //--テスト日付入力 end----------------------------
■Flutter(スマホ)側のCommUtilsクラス(共通ユーティリティクラス)・openDateDialogCommメソッド
/// 日付入力ダイヤログ /// カレンダー表示の開始、終了年月日とフォーカスする年月日を入力し、 /// 選択日付、CANCELを戻す。 static Future<String?> openDateDialogComm(BuildContext context, DateTime _firstDay, DateTime _lastDay, DateTime? _inFocusedDay) async { initializeDateFormatting('ja', null); DateFormat outputFormat = DateFormat('yyyy/MM/dd'); DateTime? _selectedDay; DateTime _focusedDay = _inFocusedDay ?? DateTime.now(); DateTime _originFocusedDay = _focusedDay; /// 罫線表示に使用するのため、表示されている月の日付のセルで、カレンダー最下位行になる日付セルの最初の日付を戻す DateTime _getLastLineFromDay(DateTime focusedDay) { DateTime _lastLineFromDay; //表示されている月の1日 DateTime _firstDay = DateTime(focusedDay.year, focusedDay.month, 1, 0, 0); //表示されている月の末日 DateTime _lastDay = DateTime(focusedDay.year, focusedDay.month+1, 1, 0, 0).add(const Duration(days: -1)); // 表示されている月の1日の曜日により、日:0、月:1、火:2、水:3、木:4、金:5、土:6 // weekdayは1が月曜日、7が日曜日なので。 int _wk1; if (_firstDay.weekday == 7) { _wk1 = 0; } else { _wk1 = _firstDay.weekday; } // (表示されている月の末日+WK1)/7日の余りにより、0:6、以外は、余り-1 int _wk2 = (_lastDay.day + _wk1) % 7; int _wk3; if (_wk2 == 0) { _wk3 = 6; } else { _wk3 = _wk2 - 1; } // 表示されている月末日-WK3 int _wk4 = _lastDay.day - _wk3; _lastLineFromDay = DateTime(focusedDay.year, focusedDay.month, _wk4, 0, 0); return _lastLineFromDay; } // 罫線表示に使用するのための、表示されている月の日付のセルで、カレンダー最下位行になる日付セルの最初の日付 DateTime _lastLineFromDay = _getLastLineFromDay(_focusedDay); // 罫線表示に使用するのための、カレンダー最下位行になる日付セルの最初の日付の対象月 DateTime _lastLineFromDayForFocusedDay = _focusedDay; int getHashCode(DateTime key) { return key.day * 1000000 + key.month * 10000 + key.year; } // TableCalendarでは、カレンダーに読み込むイベントをMapで定義した場合、 // LinkedHashMap使用が推奨されている。 final _events = LinkedHashMap<DateTime, List>( equals: isSameDay, hashCode: getHashCode, ); List getEventForDay(DateTime day) { return _events[day] ?? []; } // 祝祭日リスト Map<DateTime, List> _eventsList = {}; /// イベントマーカーを赤丸”休”とする Widget _buildEventsMarker(DateTime date, List events) { return Positioned( right: 5, bottom: 5, child: AnimatedContainer( duration: const Duration(milliseconds: 300), decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.red[300], ), width: 16.0, height: 16.0, child: Center( child: Text( '休', style: const TextStyle().copyWith( color: Colors.white, fontSize: 12.0, ), ), ), ), ); } /// 日曜日を赤色、土曜日を青色にしたい Color _textColor(DateTime day) { const _defaultTextColor = Colors.black87; if (day.weekday == DateTime.sunday) { return Colors.red; } if (day.weekday == DateTime.saturday) { return Colors.blue[600]!; } return _defaultTextColor; } /// 日付セルの罫線をグリーンにする BoxDecoration _boxDecorationNormal(DateTime day, DateTime focusedDay, DateTime _lastLineFromDay) { BoxDecoration _boxDecoration; double _leftSize = 0.5; double _rightSize = 0.5; double _bottomSize = 0.5; double _topSize = 0.5; // 日曜の日付セル用 if (day.weekday == DateTime.sunday) { _leftSize = 1.0; } // 土曜の日付セル用 if (day.weekday == DateTime.saturday) { _rightSize = 1.0; } // 翌月の日付セル用 if (day.year == focusedDay.year && day.month > focusedDay.month) { _bottomSize = 1.0; } // カレンダー最下位行になる当月の日付セル用 if (day.year == focusedDay.year && day.month == focusedDay.month && day.day >= _lastLineFromDay.day) { _bottomSize = 1.0; } _boxDecoration = BoxDecoration( border: Border( left: BorderSide( color: Colors.green[600]!, width: _leftSize, ), top: BorderSide( color: Colors.green[600]!, width: _topSize, ), right: BorderSide( color: Colors.green[600]!, width: _rightSize, ), bottom: BorderSide( color: Colors.green[600]!, width: _bottomSize, ), ), ); return _boxDecoration; } /// 曜日セルの罫線をグリーンにする BoxDecoration _boWBoxDecoration(DateTime day) { BoxDecoration _boxDecoration; double _leftSize = 0.5; double _rightSize = 0.5; double _bottomSize = 0.5; double _topSize = 0.5; // 日曜の日付セル用 if (day.weekday == DateTime.sunday) { _leftSize = 1.0; } // 土曜の日付セル用 if (day.weekday == DateTime.saturday) { _rightSize = 1.0; } _topSize = 1.0; _boxDecoration = BoxDecoration( border: Border( left: BorderSide( color: Colors.green[600]!, width: _leftSize, ), top: BorderSide( color: Colors.green[600]!, width: _topSize, ), right: BorderSide( color: Colors.green[600]!, width: _rightSize, ), bottom: BorderSide( color: Colors.green[600]!, width: _bottomSize, ), ), ); return _boxDecoration; } /// 項目入力日及び選択日の罫線を赤にする。以外はグリーンにする BoxDecoration _boxDecoration(DateTime day, DateTime focusedDay, DateTime _lastLineFromDay) { BoxDecoration _boxDecoration; // ダイヤログ表示後、日付が選択されるまでと、日付が選択された時 if (_selectedDay == null && isSameDay(day, _originFocusedDay) || _selectedDay != null && isSameDay(day, _selectedDay)) { _boxDecoration = BoxDecoration( border: Border.all( color: Colors.red[800]!, width: 3.0, ), ); } else { _boxDecoration = _boxDecorationNormal(day, focusedDay, _lastLineFromDay); } return _boxDecoration; } /// todayBuilder向け。今日の罫線枠を赤にして、今日の日付を青丸文字にする AnimatedContainer _animatedContainerForToday(DateTime day, DateTime focusedDay, DateTime _lastLineFromDay) { return AnimatedContainer( duration: const Duration(milliseconds: 250), margin: EdgeInsets.zero, decoration: _boxDecoration(day, focusedDay, _lastLineFromDay), // 今日の日付を青丸文字にする child: AnimatedContainer ( duration: const Duration(milliseconds: 250), margin: const EdgeInsets.all(5), decoration: const BoxDecoration( color: Color(0xFF9FA8DA), shape: BoxShape.circle, ), alignment: Alignment.center, child: Text( day.day.toString(), style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ); } /// selectedBuilder向け。項目入力日及び選択日の罫線を赤にする。さらに、今日の日付を青丸文字にする AnimatedContainer _animatedContainerForSelect(DateTime day, DateTime focusedDay, DateTime _lastLineFromDay) { AnimatedContainer _animatedContainer; // 日付が今日の時 if (isSameDay(day, DateTime.now())) { _animatedContainer = _animatedContainerForToday(day, focusedDay, _lastLineFromDay); } else { // 日付が今日以外の時 _animatedContainer = AnimatedContainer( duration: const Duration(milliseconds: 250), margin: EdgeInsets.zero, decoration: BoxDecoration( border: Border.all( color: Colors.red[800]!, width: 3.0, ), ), alignment: Alignment.topCenter, child: Text( day.day.toString(), style: TextStyle( color: _textColor(day), ), ), ); } return _animatedContainer; } try { // 祝祭日XMLを読む final url = Uri.parse(Consts.myHttpUrl + '/js/holidays.xml'); HttpClientRequest request = await HttpClient().getUrl(url); HttpClientResponse response = await request.close(); // 祝祭日XMLのdateエレメントのみ抽出する final stream = response.transform(utf8.decoder) .toXmlEvents() .selectSubtreeEvents((event) => event.name == 'date'); // _eventsListに祝祭日を登録する await stream.forEachEvent(onText: (event) => _eventsList.addAll( {DateTime(int.parse((event.text).substring(0,4)), int.parse((event.text).substring(5,7)), int.parse((event.text).substring(8,10)),0,0): ['祝祭日']})); // _eventsListに登録した祝祭日を、eventsに登録する _events.addAll(_eventsList); } catch (e) { // holidays.xmlが取得できない場合、何もしない。 } // 日付入力ダイヤログを表示する var result = await showDialog<String>( context: context, barrierDismissible: false, builder: (BuildContext context) { return StatefulBuilder( builder: (context, setState) { return SimpleDialog( // title: Text('日付選択'), // 日付選択ダイヤログはタイトルを出さない children: <Widget>[ TableCalendar( locale: 'ja', // カレンダーの最初と最後の月を設定 firstDay: _firstDay, lastDay: _lastDay, // focusedDayによって表示月が決定される。 focusedDay: _focusedDay, // イベントを読み込む。祝祭日に赤丸”休”を表示 eventLoader: getEventForDay, // カレンダーフォーマットを月単位とする calendarFormat: CalendarFormat.month, // カレンダーフォーマット変更ボタンを表示しない headerStyle: const HeaderStyle( titleCentered: true, formatButtonVisible: false, ), // どの日が現在選択されているかを判断する。 // dayが選択されたら、trueを返す。 selectedDayPredicate: (day) { return isSameDay(_selectedDay, day); }, // 選択した日付を一時的に保存し、selectedDayPredicateオプションで // 保存された日付情報にフォーカスを当てて印が付けている。 onDaySelected: (selectedDay, focusedDay) { if (!isSameDay(_selectedDay, selectedDay)) { setState(() { _selectedDay = selectedDay; _focusedDay = focusedDay; }); } }, onPageChanged: (focusedDay) { _focusedDay = focusedDay; }, calendarBuilders: CalendarBuilders( /// 現在フォーカスされている月と一致しない日付部分を生成する outsideBuilder: ( BuildContext context, DateTime day, DateTime focusedDay) { if (!isSameDay(_lastLineFromDayForFocusedDay, focusedDay)) { _lastLineFromDay = _getLastLineFromDay(focusedDay); _lastLineFromDayForFocusedDay = focusedDay; } return AnimatedContainer( duration: const Duration(milliseconds: 250), margin: EdgeInsets.zero, decoration: _boxDecoration(day, focusedDay, _lastLineFromDay), alignment: Alignment.topCenter, child: Text( day.day.toString(), style: const TextStyle( color: Colors.grey, ), ), ); }, /// 有効範囲(firstDay~lastDay)以外の日付部分を生成する disabledBuilder: ( BuildContext context, DateTime day, DateTime focusedDay) { if (!isSameDay(_lastLineFromDayForFocusedDay, focusedDay)) { _lastLineFromDay = _getLastLineFromDay(focusedDay); _lastLineFromDayForFocusedDay = focusedDay; } return AnimatedContainer( duration: const Duration(milliseconds: 250), margin: EdgeInsets.zero, decoration: _boxDecoration(day, focusedDay, _lastLineFromDay), alignment: Alignment.topCenter, child: Text( day.day.toString(), style: const TextStyle( color: Colors.grey, ), ), ); }, /// デフォルトのセルを枠線のついたContainerに置き換える defaultBuilder: ( BuildContext context, DateTime day, DateTime focusedDay) { if (!isSameDay(_lastLineFromDayForFocusedDay, focusedDay)) { _lastLineFromDay = _getLastLineFromDay(focusedDay); _lastLineFromDayForFocusedDay = focusedDay; } return AnimatedContainer( duration: const Duration(milliseconds: 250), margin: EdgeInsets.zero, decoration: _boxDecoration(day, focusedDay, _lastLineFromDay), alignment: Alignment.topCenter, child: Text( day.day.toString(), style: TextStyle( color: _textColor(day), ), ), ); }, /// 今日のセルを枠線のついたContainerに置き換える todayBuilder: ( BuildContext context, DateTime day, DateTime focusedDay) { if (!isSameDay(_lastLineFromDayForFocusedDay, focusedDay)) { _lastLineFromDay = _getLastLineFromDay(focusedDay); _lastLineFromDayForFocusedDay = focusedDay; } return _animatedContainerForToday(day, focusedDay, _lastLineFromDay); }, /// 曜日のセルを枠線のついたContainerに置き換える dowBuilder: (BuildContext context, DateTime day) { final dowText = const DaysOfWeekStyle().dowTextFormatter?.call(day, 'ja') ?? DateFormat.E('ja').format(day); return Container( decoration: _boWBoxDecoration(day), child: Center( child: Text( dowText, style: TextStyle( color: _textColor(day), ), ), ), ); }, // 選択されたセルを枠線のついたContainerに置き換える selectedBuilder: ( BuildContext context, DateTime day, DateTime focusedDay) { if (!isSameDay(_lastLineFromDayForFocusedDay, focusedDay)) { _lastLineFromDay = _getLastLineFromDay(focusedDay); _lastLineFromDayForFocusedDay = focusedDay; } return _animatedContainerForSelect(day, focusedDay, _lastLineFromDay); }, /// イベントマーカーをmarkerBuilderを使用してカスタマイズする markerBuilder: (context, date, events) { if (events.isNotEmpty) { return _buildEventsMarker(date, events); } return null; }, ), ), Row( mainAxisAlignment: MainAxisAlignment.center, children:<Widget>[ SimpleDialogOption(child: const Text('OK'), onPressed: () { // 選択した日付を戻す。選択していないときは入力日付か、入力日付がnullの時は今日の日付を戻す。 Navigator.pop(context, _selectedDay==null?outputFormat.format(_originFocusedDay):outputFormat.format(_selectedDay!)); },), SimpleDialogOption(child: const Text('キャンセル'), onPressed: () { // ”cancel”を戻す Navigator.pop(context, Consts.cancel); },) ], ), ], ); } ); }, ); return result; }
■2022/06/15に、勉強した成果:『Flutter_JavaSpringプログラム自動作成◎自動生成ツール』をVectorに載せました。Zenn本も書きました。使ってみての感想や間違いの指定や、こうやったほうがいいとかの情報があればメールください。
・Vector
www.vector.co.jp
・Zenn本(Flutter_JavaSpringプログラム自動作成)
zenn.dev