【Unity】カレンダーを作り直してみた

体重管理アプリを作っていて、
データの保存や表示の処理を悩んでいるケロです。(?+_+)

以前のアプリをブラッシュアップしようと思ったのですが、
作ったファイルが見当たらないので、一から作り直しております。

そんな事はさておき、
体重管理アプリと言う事でカレンダーが必要になるので、
昔の記事を読み返してみて、愕然としております(-_-;)

無駄が多い…
自分で見ても分かりにくい…

読んでくれた方には申し訳にない内容になっていました。
これに関しては今も変わらないような…
(°0°)\(--; オイオイ


それに開いた日付の月しか表示できないので、
使い勝手が悪いってのもあって、一から作ってみる事にしました。

はたして、あの頃より上達したのか
その辺も見てもらうと面白いかもしれません。

早速、Unityの設定をします。
プラットフォームはandroidです。

まず、Canvasを設置するのですが、
今回は、画面比率を18:9縦で設定して、Canvasサイズを横720×縦1440にします。
カレンダー①.jpg
UI用のCanvasを追加してレイヤーを1に設定します。
このCanvasにImageを追加してFrameにリネームします。
このフレームは、カレンダーの枠になります。

以前は、カレンダーの枠をスプライトで取り込んでいましたが、
わざわざ用意しなくてもフレームもどきが作れるので紹介します。
カレンダー③.jpg
フレームのサイズは、横716×縦734にしておきます。

フレームの中にImageで日付マスを用意するのですが、
日付マスのサイズを、横100×縦120で考えているためです。

続いて、フレームの中にコンポーネントを追加します。
カレンダー②.jpg
コンポーネント追加からレイアウト→グリッドレイアウトグループを追加。

このコンポーネントは、設定内容にあわせて、
中にあるオブジェクトをレイアウトしてくれる便利なものです。

追加したコンポーネントの設定をします。
カレンダー④.jpg
・パディング
 上下左右の余白です。ピクセル単位で調整してくれます。
 設定では、オール2にしています。
・セルサイズ
 オブジェクトのサイズです。
 入れ子にしたImageを自動的にこのサイズにしてくれます。
・間隔XY
 オブジェクトの間隔を調整します。

残りは、左詰めならそのままでオケ。

この設定でFrameの中にImageを42枚追加してみます。
カレンダー⑤.jpg
綺麗に並べてくれましたヽ(´▽`)/~♪

余白・間隔を調整すると枠があるように見えます。
Imageの座標調整も要らないので、非常に便利でっす。

スクリプトからクローンを生成しても自動的に配置してくれます。
と言う事でImageを一枚だけ残して削除、プレハブにしていきます。

ImageをDayPanelにリネームして、子にテキストを追加します。
テキストは日付表示用なので、DayTextにリネームして、
表示位置と文字サイズを調整しておきます。
カレンダー⑥.jpg
設定が終わったらPrefab化します。
Prefabにしたら元のDayPanelを削除します。

続いて、カレンダーの各パーツを追加します。
カレンダー⑦.jpg
カレンダーの月表示用のTextを追加します。
これは、スクリプトから書き換えるので単体で作っておくと
分かり易いと思います。

曜日のTextを追加します。
カレンダー⑧.jpg
日~土までのTextを追加します。
これもペアレントを作って、GridLayoutGroupで設定すると楽です。

最後は、カレンダーの月を変えるボタンを用意します。
ペアレントを用意してButtonを二つ追加します。
カレンダー⑨.jpg
月表示の両側に配置すればOKかと思います。

UIの準備ができたので、スクリプトを組んで行きます。
CalendarManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;

public class CalendarManager : MonoBehaviour
{
/// <summary>
/// オブジェクト・コンポーネント
/// </summary>

//パブリック
[Header("カレンダー枠")]
public GameObject frame;
[Header("カレンダーの年月テキスト")]
public GameObject calendarDate;
[Header("日付プレハブ")]
public GameObject dayPanel;

//プライベート
private Text calendarDateText = null; //カレンダー月表示テキスト
private Image[] dayPanelColor = new Image[42]; //日付パネルのイメージ
private Text[] dayText = new Text[42]; //日付テキスト

テキストの操作が必要なので、using UnityEngine.UIを宣言
日付のデータを取得する必要があるので、using Systemを宣言します。

カレンダーの枠・年月表示・日付のプレハブを取り込みます。
各コンポーネント取得用に変数を用意します。

続いて、起動時の日付を取得して
カレンダーの年月を表示します。

/// <summary>
/// 各種変数
/// </summary>

private DateTime firstDay = DateTime.MinValue; //表示月の月初日取得用
private DateTime nextMonth = DateTime.MinValue; //次月取得用
private DateTime firstPoint = DateTime.MinValue; //カレンダーの1枠目の日付取得用


/// <summary>
/// メソッド
/// </summary>

// Start is called before the first frame update
void Start()
{
GetTheDate();

//カレンダーの年月を表示する
calendarDateText = calendarDate.GetComponent<Text>();
calendarDateText.text = firstDay.ToString("yyyy年M月");
}

//今の日付から表示月と次月の月初日を取得する
private void GetTheDate()
{
DateTime temp = DateTime.Now.Date;
firstDay = new DateTime(temp.Year, temp.Month, 1);
nextMonth = firstDay.AddMonths(1);
}

firstDayに表示月の月初日を取得します。
nextMonthは、表示月と次月を判断する為に取得しておきます。
AddMonths(数値)で月だけを加算する事ができます。

取得したfirstDayをテキストに代入してやれば年月の表示ができます。
firstDayは、DateTimeで取得しているので、stringに変換する必要があります。

続いて、日付パネルのクローンを生成します。

/// <summary>
/// メソッド
/// </summary>

// Start is called before the first frame update
void Start()
{
GetTheDate();

//カレンダーの年月を表示する
calendarDateText = calendarDate.GetComponent<Text>();
calendarDateText.text = firstDay.ToString("yyyy年M月");


CreateDayPanel();
}

//日付パネルの生成
private void CreateDayPanel()
{
for(int i = 0; i < 42; i++)
{
GameObject createPanel = (GameObject)Instantiate(dayPanel);
createPanel.transform.SetParent(frame.transform, false);
dayPanelColor[i] = createPanel.GetComponent<Image>();
dayText[i] = createPanel.transform.GetChild(0).GetComponent<Text>();
}
}

プレハブからクローンを生成する時に必要なコンポーネントも取得しておきます。

dayTextには、日付パネルのテキストを取得しないといけないのですが、
日付パネルの中にテキストがあります。(日付パネルの子になっている)

プレハブは日付パネルを生成しているので、子であるテキストは
いきなりGetComponentができません。

なので、createPanel.transform.GetChild(0)これで子オブジェクトを指定して、
GetComponentしています。

ちなみに、子が複数ある場合はヒエラルキーの上から順に0、1、2と
指定する事ができます。

謎のdayPanelColor変数ですが日付パネルのカラーを取得しています。
これの使い道は、のちのち説明します。

日付パネルも生成できたので、日付を代入する処理を作ります。

/// <summary>
/// 各種変数
/// </summary>

//曜日ごとの補正値を設定しておく
private enum WEEK
{
sunday = 0,
monday = -1,
tuesday = -2,
wednesday = -3,
thursday = -4,
friday = -5,
saturday = -6,
}

//カラーコード設定
private Color green = new Color(0f, 0.4f, 0f, 1f);
private Color gray = new Color(0.9f, 0.9f, 0.9f, 1f);


/// <summary>
/// メソッド
/// </summary>

// Start is called before the first frame update
void Start()
{
GetTheDate();

//カレンダーの年月を表示する
calendarDateText = calendarDate.GetComponent<Text>();
calendarDateText.text = firstDay.ToString("yyyy年M月");


CreateDayPanel();
SetCalendarDate();
}

//カレンダーの日付をセットする
private void SetCalendarDate()
{
DayOfWeek firstWeek = firstDay.DayOfWeek; //月初の曜日を取得
int diff = 0; //1枠目との差を取得する変数

//月初の曜日から1枠目との差を割出す
if (firstWeek == DayOfWeek.Sunday) diff = (int)WEEK.sunday;
if (firstWeek == DayOfWeek.Monday) diff = (int)WEEK.monday;
if (firstWeek == DayOfWeek.Tuesday) diff = (int)WEEK.tuesday;
if (firstWeek == DayOfWeek.Wednesday) diff = (int)WEEK.wednesday;
if (firstWeek == DayOfWeek.Thursday) diff = (int)WEEK.thursday;
if (firstWeek == DayOfWeek.Friday) diff = (int)WEEK.friday;
if (firstWeek == DayOfWeek.Saturday) diff = (int)WEEK.saturday;
//差分を計算する
firstPoint = firstDay.AddDays(diff);


//各日付テキストに代入
for(int i = 0; i < 42; i++)
{
DateTime temp = firstPoint.AddDays(i);

//前月・次月は表示しない、日付パネルをグレーにする
if (temp < firstDay || temp >= nextMonth)
{
dayText[i].text = null;
dayPanelColor[i].color = gray;
}
else
{
dayText[i].text = temp.Day.ToString();
dayPanelColor[i].color = Color.white;
}

//曜日で文字色を変更する
switch (temp.DayOfWeek)
{
case DayOfWeek.Sunday:
dayText[i].color = Color.red;
break;

case DayOfWeek.Saturday:
dayText[i].color = Color.blue;
break;

default:
dayText[i].color = green;
break;
}
}
}

結構分かりにくいと思うので説明すると

カレンダー枠の1番目の日付を取得したいのですが、
割出し方としては、月初日との差で割り出す事ができます。

差を求めるには、月初日の曜日が必要になります。

例えば、2020/10/1をDayOfWeekに掛けるとThursdayと返ってきます。
カレンダー⑩.jpg
見てもらうと分かるのですが4日の差があります。
なので、10/1から4を引いてやれば1枠目の日付が計算できます。
曜日ごとに引く日数を設定しておけば計算できるので、
列挙体で日数を設定して、計算式に代入しています。

DateTimeの日を加算する場合は、AddDays(数値)で計算ができます。
一日前なら.AddDays(-1)、一日後なら.AddDays(1)とすればいいだけです。

1枠目の日付をfirstPointに取得しておいて、
後は、ループのカウントをfirstPointに加算すれば枠毎の日付が取得できます。

日付が計算できたらテキストに代入するのですが、
前後の月なら日付パネルをグレーにしたかったので、
if (temp < firstDay || temp >= nextMonth)
を設定しました。

表示月の範囲ならテキストを表示して、日付パネルを白にします。

最後に表示されたテキストの色を曜日毎に設定する為、
Switch分を設定しました。

文字色の設定が変な感じになっていると思いますが、
Unityでは、色が用意されています。Color.○○で呼び出す事ができます。

ただ、グリーンとグレーが思った色では無かったので、
好みの色に設定したカラーコードを用意して代入しています。


これでカレンダーの表示ができるので、
ボタン操作で表示月を変えれるようにします。

//表示月を変える処理
public void ChangeMonth(int month)
{
//表示月と次月の月初日を計算する
firstDay = firstDay.AddMonths(month);
nextMonth = firstDay.AddMonths(1);

//各テキストに日付を代入する
calendarDateText.text = firstDay.ToString("yyyy年M月");
SetCalendarDate();
}

メソッドに引数を設定して、引数をそのまま月に加算します。
取得した日付からfirstDayとnextMonthを割出して取得します。

後は、カレンダーの表示と日付パネルを更新すればOKでっす。

スクリプトができたのでUnityの設定をします。
カレンダー⑪.jpg
空のオブジェクトを追加してCalendarManagerにリネーム、
CalendarManagerスクリプトをアタッチします。

アタッチしたスクリプトのパブリック変数にそれぞれのオブジェクトをアタッチします。

続いて、ボタンの処理を設定します。
前月ボタンの処理
カレンダー⑫.jpg
次月ボタンの処理
カレンダー⑬.jpg
クリック時にCalendarManagerをアタッチして、ChangeMonthメソッドを指定します。
引数は、前月なら-1、次月なら1を設定します。

これで準備完了なのでPLAYすると

こんな感じでカレンダー表示ができます。

前後月のパネルをグレーにせず、日付をグレーにするなら

//前月・次月はグレーで表示する
if (temp < firstDay || temp >= nextMonth)
{
dayText[i].color = gray;
}
else
{
//曜日で文字色を変更する
switch (temp.DayOfWeek)
{
case DayOfWeek.Sunday:
dayText[i].color = Color.red;
break;

case DayOfWeek.Saturday:
dayText[i].color = Color.blue;
break;

default:
dayText[i].color = green;
break;
}
}
dayText[i].text = temp.Day.ToString();

こんな感じでいいかと思います。
日付パネル用のImage変数とGetComponentが要らないので削除をお忘れなく。

ちなみに
カレンダー⑭.jpg
PLAYするとこんな感じになります。

この辺は好みで変更すればOKかと。

最後に完成版のスクリプトを
CalendarManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;

public class CalendarManager : MonoBehaviour
{
/// <summary>
/// オブジェクト・コンポーネント
/// </summary>

//パブリック
[Header("カレンダー枠")]
public GameObject frame;
[Header("カレンダーの年月テキスト")]
public GameObject calendarDate;
[Header("日付プレハブ")]
public GameObject dayPanel;

//プライベート
private Text calendarDateText = null; //カレンダー月表示テキスト
private Image[] dayPanelColor = new Image[42]; //日付パネルのイメージ
private Text[] dayText = new Text[42]; //日付テキスト


/// <summary>
/// 各種変数
/// </summary>

private DateTime firstDay = DateTime.MinValue; //表示月の月初取得用
private DateTime nextMonth = DateTime.MinValue; //次月取得用
private DateTime firstPoint = DateTime.MinValue; //カレンダーの1枠目の日付取得用


//曜日ごとの補正値を設定しておく
private enum WEEK
{
sunday = 0,
monday = -1,
tuesday = -2,
wednesday = -3,
thursday = -4,
friday = -5,
saturday = -6,
}

//カラーコード設定
private Color green = new Color(0f, 0.4f, 0f, 1f);
private Color gray = new Color(0.9f, 0.9f, 0.9f, 1f);


/// <summary>
/// メソッド
/// </summary>

// Start is called before the first frame update
void Start()
{
GetTheDate();

//カレンダーの年月を表示する
calendarDateText = calendarDate.GetComponent<Text>();
calendarDateText.text = firstDay.ToString("yyyy年M月");


CreateDayPanel();
SetCalendarDate();
}

//今の日付から表示月と次月の月初日を取得する
private void GetTheDate()
{
DateTime temp = DateTime.Now.Date;
firstDay = new DateTime(temp.Year, temp.Month, 1);
nextMonth = firstDay.AddMonths(1);
}


//日付パネルの生成
private void CreateDayPanel()
{
for(int i = 0; i < 42; i++)
{
GameObject createPanel = (GameObject)Instantiate(dayPanel);
createPanel.transform.SetParent(frame.transform, false);
dayPanelColor[i] = createPanel.GetComponent<Image>();
dayText[i] = createPanel.transform.GetChild(0).GetComponent<Text>();
}
}

//カレンダーの日付をセットする
private void SetCalendarDate()
{
DayOfWeek firstWeek = firstDay.DayOfWeek; //月初の曜日を取得
int diff = 0; //1枠目との差を取得する変数

//月初の曜日から1枠目との差を割出す
if (firstWeek == DayOfWeek.Sunday) diff = (int)WEEK.sunday;
if (firstWeek == DayOfWeek.Monday) diff = (int)WEEK.monday;
if (firstWeek == DayOfWeek.Tuesday) diff = (int)WEEK.tuesday;
if (firstWeek == DayOfWeek.Wednesday) diff = (int)WEEK.wednesday;
if (firstWeek == DayOfWeek.Thursday) diff = (int)WEEK.thursday;
if (firstWeek == DayOfWeek.Friday) diff = (int)WEEK.friday;
if (firstWeek == DayOfWeek.Saturday) diff = (int)WEEK.saturday;
//差分を計算する
firstPoint = firstDay.AddDays(diff);


//各日付テキストに代入
for (int i = 0; i < 42; i++)
{
DateTime temp = firstPoint.AddDays(i);

//前月・次月は表示しない、日付パネルをグレーにする
if (temp < firstDay || temp >= nextMonth)
{
dayText[i].text = null;
dayPanelColor[i].color = gray;
}
else
{
dayText[i].text = temp.Day.ToString();
dayPanelColor[i].color = Color.white;
}

//曜日で文字色を変更する
switch (temp.DayOfWeek)
{
case DayOfWeek.Sunday:
dayText[i].color = Color.red;
break;

case DayOfWeek.Saturday:
dayText[i].color = Color.blue;
break;

default:
dayText[i].color = green;
break;
}
}
}

//表示月を変える処理
public void ChangeMonth(int month)
{
//表示月と次月の月初日を計算する
firstDay = firstDay.AddMonths(month);
nextMonth = firstDay.AddMonths(1);

//各テキストに日付を代入する
calendarDateText.text = firstDay.ToString("yyyy年M月");
SetCalendarDate();
}
}

以前と全然違うスクリプトになりました(;^_^A

読みやすいコードになったかは疑問が残りますが、
以前、放置した文字色の変更なんかも組み込めたので良かったと思います。

今回、Imageを使って日付のパネルを作りましたが、
Buttonでも同じ事ができます。

日付をクリックすると色がついて、選択できるようにしたいなら
ImageのEventtriggerを使うとボタンのような働きも作る事ができそうです。

簡単なカレンダー系のアプリを作るなら流用して頂けると幸いでっす。

ま、asset探せばもっといいのがありそうですが…
(;^o^) \(ToT )あんたほんとにそれでいいの

カレンダーについては以上です。
それではまたの機会に(o・・o)/~マタネェ

この記事へのコメント