Androidカレンダーコントロールの実現コード
9086 ワード
まず動的な効果図を見ましょう。
プロジェクト住所:https://github.com/Othershe/CalendarView
ここでは主にカレンダーコントロールの作成中の主要な点を記録します。
一、主要機能
1、旧暦、節気、一般祝日をサポートする
2、日付範囲の設定、デフォルトサポートの最大日付範囲[1900.1~209.11]
3、標準選択の日付設定
4、単一選択、複数選択
5、指定の日付にジャンプ
6、カスタム属性によって日付の外観をカスタマイズし、簡単な日付アイテムレイアウトの設定を行う。
二、基本構造
私たちが実現したいカレンダーコントロールはViewer Pagerをメインフレームとして、CalendarViewはViewer Pagerを継承しています。こうして、左右のスライドとキャッシュの機能が生まれました。現在、私達はカレンダーを左右にスライドさせて月に切り替える操作を設定しています。毎月の表示はカスタムViewで実現されます。つまり私達のMonthViewです。月の日付はlayoutレイアウトで解析されたViewです。月によっては各MonthViewは6 x 7または5 x 7日の日付Viewが含まれます。したがって、PagerAdapterを継承し、Calendar PagerAdapterを拡張して、MonthViewの関連初期化と日付データのバインディングを完了します。
三、各MonthViewを計算するには、充填が必要な日付データ
上のスクリーンショットから見ると、各MonthViewの日付データは先月の後0~6日間、現在の月の日数と来月の前0~6日間で構成されるべきです。まず、現在の月が何日間あるかを計算します。これは簡単です。また、年月から現在の月の初日は何曜日ですか?
public static int getFirstWeekOfMonth(int year, int month) {
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, 1);
return calendar.get(Calendar.DAY_OF_WEEK) - 1;
}
0は日曜日を表し、1~6は月曜日から土曜日を表し、上のスクリーンショットを例にとって、2017年5月の最初の日は月曜日であることが分かります。
for (int i = 0; i < week; i++) {
ld = - week + 1 + i;
}
来月の日付と現在のMonthViewで表示されている行数に関しては、現在の月の日数+weekが7で割り切れるなら、来月の日付は含まれていません。さもなければ、含める来月の日付を計算する必要があります。疑似コードは以下の通りです。
for (int i = 0; i < 7 * - - week; i++) {
nd = i + 1;
}
このように必要な日付データは計算済みです。詳しい計算方法はソースコードを参照してください。四、カレンダーの総ページ数を計算する
総ページ数はカレンダーの開始年月によって得られるべきで、実はViewer Pagerの総ページ数を確定して、このように良い理解点です。次の方法で計算できます。
count = (dateEnd[0] - dateStart[0]) * 12 + dateEnd[1] - dateStart[1] + 1
その中でもdateStart、dateEndは、カレンダーの開始年月と終了年月を含む配列です。このcountもCalendarPager Adapterが必要です。五、positionで日付を計算する
PagerAdapterはinstantiateItem()の方法があります。
public Object instantiateItem(ViewGroup container, int position) {
return instantiateItem((View) container, position);
}
を作成しますので、カレンダーのページごとにここで作成します。つまりMonthViewです。ここでポイントがあります。どのようにpositionから年月を推定しますか?
public static int[] positionToDate(int position, int startY, int startM) {
int year = position / 12 + startY;
int month = position % 12 + startM;
if (month > 12) {
month = month % 12;
year = year + 1;
}
return new int[]{year, month};
}
そのうちstartY、startMはカレンダーの実際の年月を表します。対応する年月があれば、第二のポイントで日付データを計算し、MothViewに充填することができます。六、MothView
前に述べましたが、MonthViewはViewを継承し、カレンダーの各ページを受信した後、MonthViewではデータ構造に応じた日付Viewを作成し、ViewをMonthViewに追加し、最後にoneMeasre、onLayoutを通じて各Viewの最終的な大きさと位置を決定します。ここでViewer Pagerの基本条件を実行すれば満足です。上記のinstantiateItem()方法でMothViewの初期化を完了します。
public Object instantiateItem(ViewGroup container, int position) {
MonthView view = new MonthView(container.getContext());
// position 、
int[] date = CalendarUtil.positionToDate(position, dateStart[0], dateStart[1]);
view.setDateList(CalendarUtil.getMonthDate(date[0], date[1]), SolarUtil.getMonthDays(date[0], date[1]));
container.addView(view);
return view;
}
ここではコアコードのみを保持しています。カレンダーが月に切り替わると、自動的にpositionから対応月の日付データを計算してMonthViewに転送し、最後にMonthViewをViewer Pagerに追加します。七、月の選択日を切り替えます。
現在の設定では、現在の月のある日を選択してから、月を切り替えます。新しい月の中に前回選択した日付が見つかり、選択状態としてマークされます。見つけられない場合は、新しい月の最後の日を選択します。実はロジックは簡単です。鍵はどのようにして新月に日付を見つけて選択しますか?まず、前回選択した日付を記録します。Viewer Pagerはデフォルトでは2ページキャッシュされます。現在のページを合わせて3ページを記録します。Calendar PagerAdapterではpositionによって3ページのキャッシュを保存します。ViePagerがあるページに切り替わると、次のようなコールが実行されます。
addOnPageChangeListener(new SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
}
});
onPageSelected(int position)メソッドでは、positionを通じてキャッシュから対応するMonthViewを取得し、切り替えられたページでもあり、MonthViewでは、記録した日付に基づいて対応するサブ日付Viewを見つけ、選択状態に変更することができます。八、複数選択
理想的な多選択機能は、現在の月に複数の日付を選択した後、他の月に切り替えます。その後、選択された日付がある月に戻っても、選択された日付をマークすることができます。ViePagerにはデフォルトの3ページのキャッシュがありますので、現在の月に切り替えると、先月または来月には問題はありません。ただし、前の数ヶ月または後数ヶ月に切り替えると、選択された月に戻ります。前にキャッシュされたページが破壊されて再建されたので、選択された月は見えなくなります。私達の日付クリックイベントはMonthViewの中にあります。選択をクリックするたびに、対応する年月の選択を記録しなければなりません。選択をキャンセルする時は記録から対応日を削除します。どうやって保存しますか?CalendarViewではSparseArayを定義します。
SparseArray<HashSet<Integer>> chooseDate = new SparseArray<>()
その中のHashSetは指定年月に選択された日付で、私達の規則に従って異なった年月に変換されたpositionを設定するのは唯一の対応です。だから私達はpositionをSparsearrayのkeyとして使って、最後にCalendarViewで選択を受信します。
public void setLastChooseDate(int day, boolean flag) {
HashSet<Integer> days = chooseDate.get(currentPosition);
if (flag) {
if (days == null) {
days = new HashSet<>();
chooseDate.put(currentPosition, days);
}
days.add(day);
} else {
days.remove(day);
}
}
その後、月切替中に、保存した日付データに基づいて対応するモンテビューを更新し、選択状態の回復を実現します。これは6番目と同じです。九、指定日までジャンプ
指定された日付にジャンプするには、まず日付の年月日からターゲットのMonthViewのカレンダーのpositionを計算します。
public static int dateToPosition(int year, int month, int startY, int startM) {
return (year - startY) * 12 + month - startM;
}
View Pagerにはset CurrenntItemという方法があり、position対応のMonthViewにジャンプし、6番目の方法と合わせて対応する日付Viewを選択します。このようにカレンダーの日付設定範囲にジャンプする日はいつでも大丈夫です。十、ユーザー定義のカレンダースタイル
現在のCalendarViewが提供しているカスタム属性は以下の通りです。
<declare-styleable name="CalendarView">
<!-- -->
<attr name="multi_choose" format="boolean" />
<!-- -->
<attr name="show_lunar" format="boolean" />
<!-- -->
<attr name="show_last_next" format="boolean" />
<!-- -->
<attr name="show_holiday" format="boolean" />
<!-- -->
<attr name="show_term" format="boolean" />
<!-- (1990.1)-->
<attr name="date_start" format="string" />
<!-- (2020.12)-->
<attr name="date_end" format="string" />
<!-- 、 (2016.10.1)-->
<attr name="date_init" format="string" />
<!-- -->
<attr name="disable_before" format="boolean" />
<!-- -->
<attr name="color_solar" format="color" />
<!-- -->
<attr name="size_solar" format="integer" />
<!-- -->
<attr name="color_lunar" format="color" />
<!-- -->
<attr name="size_lunar" format="integer" />
<!-- -->
<attr name="color_holiday" format="color" />
<!-- -->
<attr name="color_choose" format="color" />
<!-- ( )-->
<attr name="day_bg" format="reference" />
<!-- , -->
<attr name="switch_choose" format="boolean" />
</declare-styleable>
基本的に日常の需要を満たすことができます。デフォルトの日付配列は太陽暦、旧暦の垂直配列です。祝日は旧暦でカバーされます。これは上の静止画から分かります。他の配列、例えば水平配列などを使用するには、所定のlayoutを提供する必要がある(ただし、現在は2つのTextView表示のみをサポートしている)。たとえば:
calendarView.setOnCalendarViewAdapter(R.layout.item_layout, new CalendarViewAdapter() {
@Override
public TextView[] convertView(View view, DateBean date) {
TextView solarDay = (TextView) view.findViewById(R.id.solar_day);
TextView lunarDay = (TextView) view.findViewById(R.id.lunar_day);
return new TextView[]{solarDay, lunarDay};
}
});
CalendarViewにインターフェースをバインドし、lauoytに伝え、太陽暦と旧暦を表すTextView配列を返す。十一、WeekView
日付と曜日の表示機能を分割しましたので、CalendarViewは曜日の表示を担当していません。WeekViewは曜日に表示されるカスタムViewです。日曜日から順番に月曜日から土曜日までです。曜日の表示文字と文字の色、サイズをカスタマイズすることができます。これはまだ比較的簡単です。
十二、まとめ
ここではカレンダーの基本的な実現原理といくつかの重要な点を紹介しましたが、実はこのような実現方式は比較的簡単で、理解しやすいです。もちろん足りないところがあります。後は必要に応じて徐々に改善し、拡張していきましょう。Githubには既成のCalendarがたくさんありますが、自分で一つを実現するにはやはり収穫がいっぱいで、一つは簡単に見えるもので、自分で試してこそ初めてその味を味わうことができます。