[Flutter/dart]カレンダーを自作する
概要
スマホアプリでカレンダーを使う機会は多いですよね。カレンダーを使用できるライブラリは多いのですが、自分がやりたいことができない、ということも多いでしょう。特に、日付と一緒に他のwidgetを表示することがやりにくいものが多い印象です。
そんな場合は自分でカレンダーを作ってしまいましょう。
方法
指定の月に含まれる日にちを取得
まずはある月に含まれる日にちを取得しましょう。1日から始めて、月が変わるまで1日づつaddしていきます。(もちろん西向く士は30日で、、、という場合分けをしてもいいですが、閏年とかも考慮すると結構面倒です。)
List<List<DateTime>> _datesInMonth(int year, int month) {
List<List<DateTime>> _dates = [];
List<DateTime> _datesInWeek=[];
DateTime _date = DateTime(year, month, 1);
while (_date.month == month) { //1
_datesInWeek.add(_date);
if (_date.weekday==DateTime.saturday){
_dates.add(_datesInWeek);
_datesInWeek=[]; //2 土曜日で区切る
}
_date = _date.add(Duration(days: 1));
}
if (_datesInWeek.length>0){
_dates.add(_datesInWeek);
}
return _dates;
}
「1週間に含まれる日にちのリスト」のリストを返す関数です。
その月の1日から始め、月が変わるまで日にちを追加していきます(1)。
土曜日を追加したら「1週間のリスト」は完成です(2)。
「1週間のリスト」をカレンダーのwidgetに
次に、各「1週間のリスト」をカレンダーの形式のwidgetに変換します。
TableRow _weekWidget(List<DateTime> _dates){
List<Widget> widgets=[];
int startWeekDay=_dates[0].weekday;
bool _isFirstWeek =(_dates[0].day==1);
DateTime _additionalDate;
if (startWeekDay==DateTime.sunday){ //日曜はじまり
widgets=_dates.map((DateTime d) =>
_dayWidget(d, _isFirstWeek)).toList();
_additionalDate=_dates[_dates.length-1].add(Duration(days: 1));
while(widgets.length<DateTime.daysPerWeek){
//月末は1週間が7日になるまで翌月の日を追加
widgets.add(_dayWidget(_additionalDate, _isFirstWeek));
_additionalDate=_additionalDate.add(Duration(days: 1));
}
}
else{ //日曜以外から始まり(=第1週)
_additionalDate=_dates[0].subtract(Duration(days: 1));
List<DateTime> _additionalDates=[];
//前の月
while(_additionalDates.length<DateTime.daysPerWeek-_dates.length){
_additionalDates.add(_additionalDate);
_additionalDate=_additionalDate.subtract(Duration(days: 1));
}
int _len=_additionalDates.length;
for (int i=0; i<_len; i++){
widgets.add(_dayWidget(_additionalDates[_len-i-1],
_isFirstWeek));
}
widgets.addAll(_dates.map((DateTime d)
=>_dayWidget(d, _isFirstWeek)));
}
return TableRow(
children: widgets
);
}
1週間のリストに含まれる各日をwidgetにしていきます(_dayWidget 詳細は後述)。
ただし、月頭と月末は、1週間のリストに7日未満しか含まれていない可能性があるので、前月や翌月を加えます。
日にちをwidgetに
各日をwidgetにするメソッドは以下の通りです。
Widget _dayWidget(DateTime date, bool addWeekDayStr){
const double PAD_DEFAULT=5;
double pad_left= date.weekday==DateTime.sunday? 0: PAD_DEFAULT;
double pad_right=date.weekday==DateTime.saturday?0: PAD_DEFAULT;
List<Widget> _dayColumn=[];
DateTime _now=DateTime.now();
bool _isToday=((date.year==_now.year) && (date.month==_now.month)
&& (date.day==_now.day));
if (addWeekDayStr) {
_dayColumn.add(Text(WEEKDAYS[date.weekday])); //1 曜日
_dayColumn.add(SizedBox(height: 8,));
}
_dayColumn.addAll([
Container(
padding: const EdgeInsets.all(2),
alignment: Alignment.center,
decoration: _isToday? BoxDecoration(color: Colors.red ,shape:
BoxShape.circle): null, //2 今日は背景を赤く
child: Text(
date.day.toString(),
style:
TextStyle(
fontSize: 16,
color: _isToday? Colors.white :dateColor(date),
//3 土日は色を変える
),
),
),
SizedBox(height: 4,)
]);
//4 お好みのwidgetを追加
_dayColumn.add(Icon(Icons.brightness_1, color: Colors.green,));
return Container(
padding: EdgeInsets.only(left: pad_left, right: pad_right,
bottom: 5),
child: Column(
children: _dayColumn,
),
);
}
その日が月の第1週の場合、addWeekDayStrがtrueになり、曜日が日付の上に追加されます(1)。
今日は日付の背景を赤くします(2)。
日付の文字色は平日黒、土曜青、日曜赤にします(3)。
日付の下にお好みのwidgetを追加できます(4)。
曜日に応じて日付の色を返すヘルパメソッドはこちら
static Color dateColor(DateTime date){
switch(date.weekday){
case DateTime.sunday:
return Colors.red;
case DateTime.saturday:
return Colors.blue;
default:
return Colors.black;
}
}
表示する月を変更
表示する月の変更は以下の通りです。
void _changeRange(int direction){
_selectedDay = DateTime(_selectedDay.year, _selectedDay.month +
direction, 1);
setState(() {});
}
全体
以上を用いて以下のようにカレンダーを作成します。
Widget _calendarWidget(DateTime startDay){
int _year=startDay.year;
int _month=startDay.month;
List<List<DateTime>> _drawnDates;
const int MOVE_LEFT=-1;
const int MOVE_RIGHT=1;
_drawnDates=_datesInMonth(_selectedDay.year, _selectedDay.month);
return
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
child: Container(
padding: const EdgeInsets.only(right: 16),
color: Colors.transparent,
child: Text(
"<",
style: TextStyle(
fontSize: 18
),
),
),
onTap: ()=>_changeRange(MOVE_LEFT),
),
Text(
"${_year}年${_month}月",
style: TextStyle(
fontSize: 18
),
),
GestureDetector(
child: Container(
padding: const EdgeInsets.only(left: 16),
color: Colors.transparent,
child: Text(
">",
style: TextStyle(
fontSize: 18
),
),
),
onTap: ()=>_changeRange(MOVE_RIGHT),
),
],
),
SizedBox(height: 16,),
Table(
children: _drawnDates.map((List<DateTime> _days)
=>_weekWidget(_days)).toList(),
),
]
);
}
以下のようなカレンダーができます。
最新記事
すべて表示やりたいこと TextFieldで入力フォームを作りたい。 例えば入力内容が金額の場合、3桁区切りで頭に¥を付けた表記にしたい。 ただしユーザにこれらを入力させるのではなく、ユーザはあくまで数字を入力するだけで、アプリ側で自動でフォーマットしたい。 方法...
現象 やってること iosシミュレータで画像をデバイスのローカルに保存 保存したパスをデータベースに保存 アプリ立ち上げ時にデータベースから画像パスを取得し、そのパスの画像を画面上に表示 起きている現象 iosシミュレータを再起動した場合、上記3で「ファイルパスが見つからな...
やりたいこと 初期値さえ決まればあとは不変な変数がある ただし、コンストラクタ起動時にはまだ決定できない このような変数について late finalで変数を定義 (何らかのタイミングで)初期化されたかどうかをチェックし、されていなければ値を入れる(チェックしないとfina...
Comments