【Androidアプリケーションの設定画面を作ろう】CalendarViewから日付を選ぶ設定項目を自作する

このエントリーを Google ブックマーク に追加
Pocket
[`yahoo` not found]

Preferenceを継承することで自作の設定項目を作成することができます。
今回はDialogPreferenceを継承することで、自作のダイアログを表示し、値を保存することができます。
実装方針はリファレンス 設定を参考にしました。
今回はCalendarViewを表示し選択された日付を”yyyy/MM/dd”形式で保存する設定項目を作成します。

ダイアログのレイアウトファイルを定義する

ダイアログに表示するViewはDialogPreference#setDialogLayoutResourceを使いレイアウトファイルから設定することができます。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <CalendarView
        android:id="@+id/calendar_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
</LinearLayout>

コンストラクタを定義する

コンストラクタではダイアログの基本的な情報を設定します。
    public CalendarPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
        setDialogLayoutResource(R.layout.my_calendar);
        setPositiveButtonText(android.R.string.ok);
        setNegativeButtonText(android.R.string.cancel);
        setDialogIcon(null);
    }

ダイアログにデータを設定する

ダイアログを表示するときに、プリファレンスに保存されている現在の値を元に表示を変更する必要があります。
DialogPreference#onBindDialogViewはダイアログに表示されるViewが生成された後に呼ばれるメソッドです。
今回はプリファレンスに保存されている日付文字列を元にCalendarViewの初期表示日付を設定します。
    // デフォルト値
    private static final String DEFAULT_VALUE = "2018/01/01";
    // 現在の値
    private String mCurrentValue;
    // 最新の値
    private String mNewValue;
    // ダイアログに表示されるCalendarView
    private CalendarView mCalendarView;

    @Override
    protected void onBindDialogView(View view) {
        super.onBindDialogView(view);
        // 現在の値をプリファレンスから取得する
        mCurrentValue = getPersistedString(DEFAULT_VALUE);
        // CalendarViewインスタンスを取得する
        mCalendarView = (CalendarView) view.findViewById(R.id.calendar_view);
        // CalendarViewに日付を設定する。
        mCalendarView.setDate(convertDateStringToLong(mCurrentValue));
        // CalendarViewの選択されている日付が変更されたら、新しい値を変更する。
        mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
            @Override
            public void onSelectedDayChange(@NonNull CalendarView view, int year, int month, int dayOfMonth) {
                Calendar calendar = Calendar.getInstance();
                calendar.set(year , month , dayOfMonth);
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
                mNewValue = simpleDateFormat.format(calendar.getTime());
            }
        });
    }

    /**
     * 日付文字列をlong値に変換する
     * @param value
     * @return
     */
    private long convertDateStringToLong(String value) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
        Date parse = null;
        try {
            parse = simpleDateFormat.parse(value);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        long time = parse.getTime();
        return time;
    }

設定項目の変更をプリファレンスに反映する

ダイアログはOKボタンやCancelボタンを押すことで閉じます。
DialogPreference#onDialogClosedが閉じたタイミングでプリファレンスに値を保存します。
    @Override
    protected void onDialogClosed(boolean positiveResult) {
        if(positiveResult) {
            persistString(mNewValue);
        }
    }

現在の値を初期化とデフォルト値を提供

詳細はリファレンス 設定を参考にしてください。
    @Override
    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
        if (restorePersistedValue) {
            mCurrentValue = this.getPersistedString(DEFAULT_VALUE);
        } else {
            mCurrentValue = (String) defaultValue;
            persistString(mCurrentValue);
        }
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        String string = a.getString(index);
        if(TextUtils.isEmpty(string)){
            return DEFAULT_VALUE;
        } else {
            return string;
        }
    }

プリファレンスの状態を保存し復元する

詳細はリファレンス 設定を参考にしてください。
    @Override
    protected Parcelable onSaveInstanceState() {
        final Parcelable superState = super.onSaveInstanceState();
        final MySavedState myState = new MySavedState(superState);
        myState.value = mNewValue;
        return myState;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state == null || !state.getClass().equals(MySavedState.class)) {
            super.onRestoreInstanceState(state);
            return;
        }
        MySavedState myState = (MySavedState) state;
        super.onRestoreInstanceState(myState.getSuperState());
        if(mCalendarView != null) {
            mCalendarView.setDate(convertDateStringToLong(myState.value));
        }
    }

    private static class MySavedState extends BaseSavedState {
        String value;

        public MySavedState(Parcelable superState) {
            super(superState);
        }

        public MySavedState(Parcel source) {
            super(source);
            value = source.readString();
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeString(value);
        }

        // Standard creator object using an instance of this class
        public static final Parcelable.Creator<MySavedState> CREATOR = new Parcelable.Creator<MySavedState>() {
            public MySavedState createFromParcel(Parcel in) {
                return new MySavedState(in);
            }

            public MySavedState[] newArray(int size) {
                return new MySavedState[size];
            }
        };
    }

まとめ

今回はCalendarViewから日付を選択するための設定項目を作成しました。
public class CalendarPreference extends DialogPreference {

    // デフォルト値
    private static final String DEFAULT_VALUE = "2018/01/01";
    // 現在の値
    private String mCurrentValue;
    // 最新の値
    private String mNewValue;
    // ダイアログに表示されるCalendarView
    private CalendarView mCalendarView;

    public CalendarPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
        // レイアウトファイル
        setDialogLayoutResource(R.layout.my_calendar);
        // ダイアログのポジティブボタンに表示する文字列を指定します。
        setPositiveButtonText(android.R.string.ok);
        // ダイアログのネガティブボタンに表示する文字列を指定します。
        setNegativeButtonText(android.R.string.cancel);
        // ダイアログのアイコンを設定します。
        setDialogIcon(null);
    }

    @Override
    protected void onBindDialogView(View view) {
        super.onBindDialogView(view);
        // 現在の値をプリファレンスから取得する
        mCurrentValue = getPersistedString(DEFAULT_VALUE);
        // CalendarViewインスタンスを取得する
        mCalendarView = (CalendarView) view.findViewById(R.id.calendar_view);
        // CalendarViewに日付を設定する。
        mCalendarView.setDate(convertDateStringToLong(mCurrentValue));
        // CalendarViewの選択されている日付が変更されたら、新しい値を変更する。
        mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
            @Override
            public void onSelectedDayChange(@NonNull CalendarView view, int year, int month, int dayOfMonth) {
                Calendar calendar = Calendar.getInstance();
                calendar.set(year , month , dayOfMonth);
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
                mNewValue = simpleDateFormat.format(calendar.getTime());
            }
        });
    }

    /**
     * 日付文字列をlong値に変換する
     * @param value
     * @return
     */
    private long convertDateStringToLong(String value) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
        Date parse = null;
        try {
            parse = simpleDateFormat.parse(value);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        long time = parse.getTime();
        return time;
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        if(positiveResult) {
            persistString(mNewValue);
        }
    }

    @Override
    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
        if (restorePersistedValue) {
            mCurrentValue = this.getPersistedString(DEFAULT_VALUE);
        } else {
            mCurrentValue = (String) defaultValue;
            persistString(mCurrentValue);
        }
    }


    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        String string = a.getString(index);
        if(TextUtils.isEmpty(string)){
            return DEFAULT_VALUE;
        } else {
            return string;
        }
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        final Parcelable superState = super.onSaveInstanceState();
        final MySavedState myState = new MySavedState(superState);
        myState.value = mNewValue;
        return myState;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state == null || !state.getClass().equals(MySavedState.class)) {
            super.onRestoreInstanceState(state);
            return;
        }
        MySavedState myState = (MySavedState) state;
        super.onRestoreInstanceState(myState.getSuperState());
        if(mCalendarView != null) {
            mCalendarView.setDate(convertDateStringToLong(myState.value));
        }
    }

    private static class MySavedState extends BaseSavedState {
        String value;

        public MySavedState(Parcelable superState) {
            super(superState);
        }

        public MySavedState(Parcel source) {
            super(source);
            value = source.readString();
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeString(value);
        }

        // Standard creator object using an instance of this class
        public static final Parcelable.Creator<MySavedState> CREATOR = new Parcelable.Creator<MySavedState>() {
            public MySavedState createFromParcel(Parcel in) {
                return new MySavedState(in);
            }

            public MySavedState[] newArray(int size) {
                return new MySavedState[size];
            }
        };
    }
}