Android向けカスタムカレンダー作成

CalendarViewを使用して、カレンダーを作成しようと思ったのですが、
せっかくなのでカスタムダイアログにて実装してみました。
標準で提供されるダイアログを使用するのもいいですが、
よりアプリケーションをリッチにするための勉強の一貫として挑戦です。

備忘録として記録していきたいと思います。



※カスタムカレンダーを作成するにあたり、以下のサイトを参考にさせて頂きました。
Android Tips #45 Dialog をフルカスタマイズする | Developers.IO


ダイアログ起動時のイメージ
f:id:nlinks:20151110183612p:plain:w350


① まずは背景色などを定義する用のXMLを作成していきます。

1)bg_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<stroke android:width="2dp" android:color="#FFBA6F" />
<gradient android:startColor="#ffffff" android:endColor="#dcdcdc" android:angle="90"/>
</shape>


ダイアログの背景

f:id:nlinks:20151110191714p:plain:w200

2)bg_grad.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<gradient
android:startColor="#FFBA6F"
android:endColor="#FF8F52"
android:angle="0"/>
</shape>

タイトルの背景
f:id:nlinks:20151110191804p:plain:w200

3)bt_dilog_close.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" 

android:shape="oval">
<solid android:color="@android:color/white"/>
<stroke android:width="3dp" android:color="#FB8F61" />
</shape>

処理終了ボタンの背景
f:id:nlinks:20151110191852p:plain:w200

4)bt_dilog_positive,xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="#FFBA6F" />
</shape>

OKボタンの背景
f:id:nlinks:20151110192138p:plain:w200


② 次にダイアログのレイアウトファイルを作成します。

1)calendar_dialog.xml

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginTop="6dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="@drawable/bg_dialog"
android:gravity="center_horizontal"
android:orientation="vertical"
android:weightSum="1">
<!-- タイトル -->
<TextView
android:id="@+id/calendarTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_grad"
android:padding="10dp"
android:textSize="17sp"
android:textColor="@android:color/white"
android:fontFamily="SERIF"
android:textStyle="italic" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:background="#fecfb1">
</LinearLayout>

<TableLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stretchColumns="0">

<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="2015"
android:id="@+id/yearTextView"
android:gravity="center"
android:textStyle="italic"
android:layout_gravity="center"
android:fontFamily="SERIF" />

</TableRow>
</TableLayout>

<TableLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stretchColumns="1">

<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageButton
android:layout_width="26.25dp"
android:layout_height="15dp"
android:id="@+id/backMonthImageViewButton"
android:background="@drawable/move_back"
android:layout_marginLeft="10dp"
android:layout_gravity="center_vertical" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/monthTextView"
android:gravity="center_vertical|center_horizontal"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="17sp" />

<ImageButton
android:layout_width="26.25dp"
android:layout_height="15dp"
android:id="@+id/moveMonthImageButton"
android:background="@drawable/move_on"
android:layout_marginRight="10dp"
android:layout_gravity="center_vertical" />

</TableRow>

</TableLayout>

<TableLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/calendarDateLayout">
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SUN"
android:id="@+id/sunTextView"
android:layout_weight=".2"
android:layout_marginLeft="5dp"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="11sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MON"
android:id="@+id/monTextView"
android:layout_weight=".2"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="11sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TUE"
android:id="@+id/tueTextView"
android:layout_weight=".2"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="11sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="WED"
android:id="@+id/wedTextView"
android:layout_weight=".2"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="11sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="THU"
android:id="@+id/thuTextView"
android:layout_weight=".2"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="11sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="FRI"
android:id="@+id/friTextView"
android:layout_weight=".2"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="11sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SAT"
android:id="@+id/satTextView"
android:layout_weight=".2"
android:layout_marginRight="5dp"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="11sp" />
</TableRow>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="#fecfb1">
</LinearLayout>

</TableLayout>

<Button
android:id="@+id/ok_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bt_dialog_positive"
android:text="OK"
android:textColor="@android:color/white" />

</LinearLayout>
<Button
android:id="@+id/end_button"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:background="@drawable/bt_dialog_close"
android:text="×"
android:textColor="#FFBA6F"/>
</RelativeLayout>

日付以外のレイアウトを定義
f:id:nlinks:20151110192717p:plain:w200


③日付部分の表示用のレイアウトファイルを作成します。

1)calendardaterow.xml

<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="0dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_weight=".2"
android:layout_marginLeft="5dp"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="13sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_weight=".2"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="13sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_weight=".2"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="13sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_weight=".2"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="13sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_weight=".2"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="13sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_weight=".2"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="13sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_weight=".2"
android:layout_marginRight="5dp"
android:gravity="center"
android:textStyle="italic"
android:fontFamily="SERIF"
android:textSize="13sp" />
</TableRow>

④ 最後にダイアログフラグメントクラスを作成していきます。

public class CalendarDialogFlagment extends DialogFragment
{
// 管理しているカレンダー情報
Calendar mCalendar;

// イベント通知用
OnClickOKButtonListener mListener;

// イベント生成
public interface OnClickOKButtonListener
{
public void onClickOKButton(int year, int month, int date);
}

// インスタンス生成
public static CalendarDialogFlagment newInstance(String title, int year, int month, int date)
{
CalendarDialogFlagment calendarDialogFlagment = new CalendarDialogFlagment();
/* 引数を設定 */
Bundle args = new Bundle();
args.putString("Title", title);
args.putInt("Year", year);
args.putInt("Month", month);
args.putInt("Date", date);
calendarDialogFlagment.setArguments(args);

return calendarDialogFlagment;
}

// OKボタン押下時のイベント登録
public void setLisner(OnClickOKButtonListener listner)
{
mListener = listner;
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
/* 初期表示する年月日およびダイアログに表示するタイトルの取得 */
Bundle args = getArguments();
int year = args.getInt("Year");
int month = args.getInt("Month");
int date = args.getInt("Date");
String title = args.getString("Title");

// 初期設定されている日付をメンバ変数に設定
mCalendar = Calendar.getInstance();
mCalendar.set(year, month - 1, date);

final Dialog dialog = new Dialog(getActivity());
// タイトル非表示
dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
// フルスクリーン
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);

// レイアウトファイルの設定
dialog.setContentView(R.layout.calendar_dialog);

// 背景を透明にする
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

/* ダイアログの設定 */
((TextView)dialog.findViewById(R.id.calendarTitle)).setText(title);
/* 年の設定 */
((TextView)dialog.findViewById(R.id.yearTextView)).setText(String.valueOf(year));
/* 月の設定 */
((TextView)dialog.findViewById(R.id.monthTextView)).setText(getMonth(month));

/* 表示するViewGroup */
ViewGroup viewGroup = (ViewGroup) dialog.findViewById(R.id.calendarDateLayout);

// 日付行の追加
for (int i = 0; i < 6; i++) {
getActivity().getLayoutInflater().inflate(R.layout.calendardaterow, viewGroup);
}

// 初期設定されている日付設定
Calendar calendar = Calendar.getInstance();
calendar.set(year, month - 1, 1);

/* setCalendar */
this.setCalendar(calendar, dialog);

/* イベント生成 */
for (int i = 0; i < 6; i++) {
TableRow dateTableRow = (TableRow) viewGroup.getChildAt(2 + i);
for (int j = 0; j < 7; j++) {
((TextView) (dateTableRow.getChildAt(j))).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v)
{
// 選択されたポジション取得
if (!((TextView) v).getText().equals("")) {
/* 表示するViewGroup */
// 色の初期化
ViewGroup viewGroup = (ViewGroup) dialog.findViewById(R.id.calendarDateLayout);
for (int i = 0; i < 5; i++) {
TableRow dateTableRow = (TableRow) viewGroup.getChildAt(2 + i);
for (int j = 0; j < 7; j++) {
((TextView) (dateTableRow.getChildAt(j))).setBackgroundColor(0x00000000);
}
}
/* 年の取得 */
String year = (String) ((TextView) dialog.findViewById(R.id.yearTextView)).getText();
/* 月の設定 */
String month = (String) ((TextView) dialog.findViewById(R.id.monthTextView)).getText();
/* 日の取得 */
String date = (String) ((TextView) v).getText();

//メンバ変数へ選択されている日付情報を取得(OKボタン押下時に親クラスに戻すため設定)
mCalendar.set(Integer.parseInt(year), getMonth(month) - 1, Integer.parseInt(date));
((TextView) v).setBackgroundColor(0xfffeccad);
}
}
});
}
}

//前の月に移動(move_back)
dialog.findViewById(R.id.backMonthImageViewButton).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
/* 年の設定 */
String year = (String)((TextView)dialog.findViewById(R.id.yearTextView)).getText();
/* 月の設定 */
String month = (String)((TextView)dialog.findViewById(R.id.monthTextView)).getText();

// 初期設定されている日付取得
Calendar calendar = Calendar.getInstance();
calendar.set(Integer.parseInt(year), getMonth(month) - 1, 1);
calendar.add(Calendar.MONTH, -1);
((TextView)dialog.findViewById(R.id.yearTextView)).setText(String.valueOf(calendar.get(Calendar.YEAR)));
((TextView)dialog.findViewById(R.id.monthTextView)).setText(getMonth(calendar.get(Calendar.MONTH) + 1));

/* 初期化 */
initializeCalendar(dialog);
/* Calendar設定 */
setCalendar(calendar, dialog);
}
});

//次の月に移動(move_on)
dialog.findViewById(R.id.moveMonthImageButton).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
/* 年の設定 */
String year = (String)((TextView)dialog.findViewById(R.id.yearTextView)).getText();
/* 月の設定 */
String month = (String)((TextView)dialog.findViewById(R.id.monthTextView)).getText();

// 初期設定されている日付取得
Calendar calendar = Calendar.getInstance();
calendar.set(Integer.parseInt(year), getMonth(month), 1);

((TextView)dialog.findViewById(R.id.yearTextView)).setText(String.valueOf(calendar.get(Calendar.YEAR)));
((TextView)dialog.findViewById(R.id.monthTextView)).setText(getMonth(calendar.get(Calendar.MONTH) + 1));

/* 初期化 */
initializeCalendar(dialog);
/* Calendar設定 */
setCalendar(calendar, dialog);
}
});

// OK ボタンのリスナ
dialog.findViewById(R.id.ok_button).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
mListener.onClickOKButton(mCalendar.get(Calendar.YEAR), mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DATE));
dismiss();
}
});

// Close ボタンのリスナ
dialog.findViewById(R.id.end_button).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
dismiss();
}
});

// Dialogを返す
return dialog;
}

/* 表示クリア */
private void initializeCalendar(Dialog dialog)
{
/* 表示するViewGroup */
ViewGroup viewGroup = (ViewGroup) dialog.findViewById(R.id.calendarDateLayout);
for (int i = 0; i < 6; i++) {
TableRow dateTableRow = (TableRow) viewGroup.getChildAt(2 + i);
for (int j = 0; j < 7; j++) {
((TextView) (dateTableRow.getChildAt(j))).setBackgroundColor(0x00000000);
((TextView) (dateTableRow.getChildAt(j))).setText("");
}
}
}

/* Calendar設定 */
private void setCalendar(Calendar calendar, Dialog dialog)
{
/* 表示するViewGroup */
ViewGroup viewGroup = (ViewGroup) dialog.findViewById(R.id.calendarDateLayout);

// 日付設定
int cahngeDateIndex = 7 - calendar.get(Calendar.DAY_OF_WEEK);
/* 日付設定 */
TableRow dateTableRow;
TextView dateTextView;
int index = 0;
int dayCount = calendar.getActualMaximum(Calendar.DATE);
for(int i = 0; i < dayCount; i++)
{
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
if (i - cahngeDateIndex <= 0)
{
dateTableRow = (TableRow) viewGroup.getChildAt(2);
dateTextView = (TextView) (dateTableRow.getChildAt(dayOfWeek - 1));
}
else
{
index = (i + 6 - cahngeDateIndex) / 7;
dateTableRow = (TableRow) viewGroup.getChildAt(2 + index);
dateTextView = (TextView) (dateTableRow.getChildAt(dayOfWeek - 1));
}
// 日付設定
dateTextView.setText(String.valueOf(i + 1));
//選択状態にする
if(mCalendar.get(Calendar.YEAR) == calendar.get(Calendar.YEAR) &&
mCalendar.get(Calendar.MONTH) == calendar.get(Calendar.MONTH) &&
mCalendar.get(Calendar.DATE) == calendar.get(Calendar.DATE))
{
dateTextView.setBackgroundColor(0xfffeccad);
}
// 次の日に移動
calendar.add(Calendar.DATE, 1);
}
}

/* 対応する月を表す文字列を取得 */
private String getMonth(int month)
{
String strMonth = "";
switch(month)
{
case 1:
strMonth = "January";
break;
case 2:
strMonth = "February";
break;
case 3:
strMonth = "March";
break;
case 4:
strMonth = "April";
break;
case 5:
strMonth = "May";
break;
case 6:
strMonth = "June";
break;
case 7:
strMonth = "July";
break;
case 8:
strMonth = "August";
break;
case 9:
strMonth = "September";
break;
case 10:
strMonth = "October";
break;
case 11:
strMonth = "November";
break;
case 12:
strMonth = "December";
break;
}
return strMonth;
}

private int getMonth(String month)
{
int integerMonth = 0;
switch(month)
{
case "January":
integerMonth = 1;
break;
case "February":
integerMonth = 2;
break;
case "March":
integerMonth = 3;
break;
case "April":
integerMonth = 4;
break;
case "May":
integerMonth = 5;
break;
case "June":
integerMonth = 6;
break;
case "July":
integerMonth = 7;
break;
case "August":
integerMonth = 8;
break;
case "September":
integerMonth = 9;
break;
case "October":
integerMonth = 10;
break;
case "November":
integerMonth = 11;
break;
case "December":
integerMonth = 12;
break;
}
return integerMonth;
}
}

以上でカレンダーダイアログは完成です。
親画面より呼び出せはダイアログが表示されます。
カスタマイズダイアログを作成できるようになると
処理の幅が広がるように思います。
バグ等ありましたら指摘して頂ければと思います。