一.src檔案分析:
與calendar相關的包和類
Calendar子產品根據包的劃分,可以将Calendar初步歸類為:
四個視圖組:月視圖、周視圖、日視圖、日程視圖;
核心業務組:添加日程、設定日程提醒、月曆同步;
在calendar包下的上層代碼中有DayFragment.java、DayView.java這兩個與日視圖相關的類;
在/month包下是月視圖子產品;
在/agenda包下是日程視圖子產品;
在/event包下是日程的編輯、顯示子產品;
在/alert包下是日程提示子產品;
在/selectcalendars包下是與月曆同步相關的視圖與操作子產品;
在/widget包下,顧名思義是桌面小部件子產品。、
在/lunar包下,月曆陰曆顯示
二.啟動流程
I、概述:
1. AllInOneActivity.java //入口類,初始化月曆資料與界面
2. AsyncQueryService //為ContentProvide提供增删改查功能的支援
3. CalendarController.java //控制中心,溝通AllInOneActivity和各個子產品
4. CalendarViewAdapter.java //适配左上角的下拉導航菜單
5. CalendarSettingsActivity.java //ActionBar的Setting按鈕
6. EditEvent.java //建立日程活動,設定活動名稱,時間,地點,提醒鬧鈴等。
7. EventInfoActivity.java //檢視活動詳情,可以編輯更新活動内容。
8. SelectCalendarsActivity.java //月曆本地月曆是否同步和顯示。
9. CalendarPreferenceActivity.java //月曆設定,月曆視圖設定,提醒設定,關于等功能。
10. AlertActivity.java //月曆通知,提示月曆活動是否延遲或關閉活動提醒。
11. AlertService.java //鬧鈴提醒服務。
12. DismissAllAlarmsService.java //解雇所有警報服務。
13. SearchActivity.java //查詢日程活動(2.3.0版本沒有此功能);
14. LunarCalendar.java //陰曆
15. DayUtils.java //将日期轉換成各種不同的顯示樣式
16. DayFragment.java //顯示日視圖
17. DayOfMonthDrawable.java //點選“今天”之後重繪的the day of the month
18. DayView.java //對日視圖的重繪及其有關的資訊處理
19. EventInfoActivity.java //顯示詳細的事件資訊
20. EventInfoFragment.java //詳細事件的視圖
21. GeneralPreferences.java //儲存了月曆應用的預設配置
22. QuickResponseSettings.java //設定要發送月曆給Email時的快速響應
23. SearchActivity.java //“搜尋”按鈕的響應
24. Utils.java //各種輔助工具
25. ./month下:
26. MonthByWeekFragment.java //顯示月視圖
27. MonthWeekEventsView.java //顯示月視圖中的Event
28. SimpleDayPickerFragment.java//動态繪制一個可選Days的Title list of Week
29. SimpleWeekView.java //動态繪制單獨的一周的視圖
30. ./agenda下:
31. AgendaFragment.java //日程詳細視圖
32. AgendaListView.java //日程清單子產品
33. ./event下:
34. EditEventActivity.java //用來詳細編譯一個事件
35. EditEventFragment.java //顯示編輯事件的視圖
36. ./alerts下:
37. AlertActivity.java //提醒類事件
38. AlertService.java //背景運作的提醒類服務
39. QuickResponseActivity.java //使用者從notifications向外發送郵件時呈現一個賬戶List
40. ./selectcalendars下:
41. electCalendarsSyncFragment.java//選擇同步月曆的視圖
42. SelectSyncedCalendarsMultiAccountActivity.java//多賬戶同步設定業務
43. SelectVisibleCalendarsActivity.java//選擇可見賬戶月曆業務
44. SelectVisibleCalendarsFragment.java//可見月曆視圖
45. ./widget下:
46. CalendarAppWidgetService.java//Calendar對桌面小部件提供的遠端溝通服務
47. CalendarAppWidgetProvider.java//主要用來接收月曆Event的更新
II、AllInOneActivity.java啟動流程:
注釋:此類是用來啟動其他activity界面類後銷毀自己的類。
點選圖示進入AllInOneActivity.java:
1) onCreate()調用Utils.java進行:
主題設定(SharedPreference中的參數);
使用者賬戶設定(SharedPreference中的參數,如果是第一次使用月曆);
擷取CalendarController執行個體;
從intent或者icicle中擷取timeMillis、viewType;
然後初始化各種配置;
根據mIsTabletConfig判斷是否為手機,再來判斷是否需要mWeekTextView布局(在手機下不需要此布局);
設定allinone的contentView;
根據viewType設定ActionBar;
初始化各種布局;
注冊mController的事件處理(registerFirstEventHandler(HANDLER_KEY, this));
根據timeMillis、viewType初始化Fragment視圖;
2) 在onResume()中主要是:
對EventHandle的注冊;
更新Toady按鈕的icon;
更新options的menu;
更新時區、日期、時間、(調用Utils.java的方法);
3) 在onPause()中完成:
清除目前Activity的EventHandler;
清除mHomeTime的時間更新回調;
登出mContentResolver(内容接收器);
儲存配置到SharePreference;
登出TimeChangesReceiver;
4) 在public boolean onOptionsItemSelected(MenuItem item) {}中
通過各個item進入到CalendarController.java中采取對應的方法并且啟動相對應的Activity;
III. 布局中月,日,周等視圖的繪制方式:
分别從月視圖、周視圖、日視圖、日程視圖這四部分來詳細的介紹一下每個視圖的每個布局是怎麼填充或适配的。
下拉式導航菜單(NavigationMenuButtonsSpinner)
很明顯這是Calendar的月視圖。
左坐上角是下拉式導航菜單(NavigationMenuButtonsSpinner),當點選它的時候就會出現右圖所示的效果。它對應布局檔案是ActionBarMenuSpinner,它是在AllInOneActivity.java中通過ActionBarMenuAdapter.java來适配的。那麼我們就來了解一下它是怎麼被一步步建立起來的。
我們直接從AllInOneActivity.java的onCreate()方法内部開始看:
1. 忽略其他的方法,從配置主視圖開始
// setContentView must be called before configureActionBar
setContentView(R.layout.all_in_one);
設定試圖容器是必須要先于設定下拉式導航菜單。
2. 通過判斷裝置是否為手機(或者平闆)來擷取R.id.date_range_title布局,這個布局主要是用來設定MenuSpinner最上面顯示的一排日期的(在随後的講解中會詳細的介紹);
3. 做完了以上配置之後調用方法:
configureActionBar(viewType);
這個方法會自動适配到你添加的第一個fragment标簽頁上,是以我們需要在設定fragment之前調用它,來確定它不會在該tab上覆寫。
我們進入到configureActionBar(int viewType);這個方法内部,發現它是這樣的:
首先是建立下拉菜單ActionBar,然後設定Menu的顯示模式,DISPLAY_SHOW_CUSTOM(按使用者設定顯示Item)、DISPLAY_SHOW_HOME(顯示基礎元素,但是為icon和logo留下空間)。我們忽略它隻關心前者(Menu會在後面做詳細的介紹)。
我們再進入方法createButtonsSpinner(int ViewType, Boolean tabletConfig)
看完這個方法,我們就會發現,它才是設定這個下拉菜單的核心方法,它完成四個業務功能:
适配該視圖的Actionbar;
設定Actionbar的樣式;
設定目前樣式對應的回調方法;
根據目前視圖類型來為ActionBar設定對應的下拉菜單清單。
在CalendarViewAdapter .java中的這一段話說明了一切:
類CalendarViewAdapter是繼承BaseAdapter的,它對NavigationMenuButtonsSpinner做出了詳細的适配。
需要注意的是下拉菜單的頂部按鈕actionbar_pulldown_menu_top_button有兩種狀态:
actionbar_pulldown_menu_top_button_no_date(不顯示日期标簽)
actionbar_pulldown_menu_top_button(顯示日期标簽)
我們來看一下這兩個布局的XML檔案
actionbar_pulldown_menu_top_button.xml
actionbar_pulldown_menu_top_button_no_date.xml
這兩個XML檔案造成的效果分别如下:
左邊是在月視圖中Top-Button不帶date标簽;而右邊是在日視圖下的Top-Button,顯示date标簽。
這兩個效果是在getView()方法中對Top-Button進行布局設定的(getView()方法傳回值是用來顯示下拉框處于普通狀态時上面的view)。
那麼對于getDropDownView()方法來實作當下拉框被點選時浮在螢幕上的ListView的每個item的View(其實這部分内容隻需要看源碼就能知道了),它的布局是寫在actionbar_pulldown_menu_button.xml中的,它的效果如右圖(由兩個TextView組成):
總結一下下拉導航菜單,它是在AllInOneActivity的onCreate()中建立的,并且根據不同的Fragment選擇是否顯示對應頂部按鈕的tag,它的建立流程如下:
onCreate(Bundle icicle); //在onCreate()中被建立
configureActionBar(int viewType); //根據Fragment.Tab配置ActionBar[必須在setContentView之後]
createButtonsSpinner(viewType, mIsTabletConfig); //建立下拉導航菜單
new CalendarViewAdapter (this, viewType, !tabletConfig); //擷取一個Calendar擴充卡
getActionBar();
setNavigationMode();
setListNavigationCallbacks(); //配置ActionBar為Navigattion模式
setSelectedNavigationItem(int position); //設定下拉菜單的每個Item
getView(); //配置頂部按鈕的布局視圖
getDropDownView(); //配置下拉菜單的所有下拉出來的Item的布局視圖
onNavigationItemSelected() //在AllInOneActivity中設定對Spinner的監聽
對于getView()與getDropDownView()的關系,我們可以看到,在Calendar中getView之對Spinner的頂部選項(沒有向下展開時)做了布局的适配,并沒有對其他的項做适配;而在geyDropDownView()是對除了頂部那個一直顯示的項之外的下拉出來的Item做的布局适配。因為BaseAdapter是SpinnerAdapter的子類,而CalendarViewAdapter又是繼承的BaseAdapter,是以可以重寫getDropDownView()方法。
【個人經驗之談】其實打開Google源碼會發現getDropDownView()傳回的是getView(),
而getDropDownView方法用來顯示所有下拉出來的Item,getView方法是用來顯示Spinner最上面的一個Item,getDropDownView預設傳回值是getView的傳回值,這就意味着可以把原本寫在getView中的代碼寫在getDropDownView中,監聽也寫在此,getView中保留除監聽以外的代碼,但是要注意注冊監聽事件時候的一些問題,比如把監聽寫在getView()中時,點選Spinner時就不會觸發點選Item的效果了,隻能用setOnTouchListener監聽,因為長按和短按監聽會覆寫setOnItemSelectedListener監聽。
1. setOnItemClickListener和setOnClickListener都不能用來監聽Spinner,否則會出錯。
2. setOnLongClickListener能用來監聽Spinner的長按事件,隻能監聽Spinner被長按住,好像基本用不上。
3. setOnTouchListener能用來監聽Spinner控件的觸摸事件,注意一次快速的觸摸都能觸發這事件好幾次。
4. setOnItemSelectedListener監聽Spinner裡面的item選項的選擇事件。
對于Calendar子產品的代碼,是在AllInOneActivity中implements ActionBar.OnNavigationListener,并且已經設定了ActionBar為NavigationMode,ActionBar對該模式設定了專用的Item監聽,即 ActionBar.OnNavigationListener,是以就直接在AllInOneActivity中重寫onNavigationItemSelected()來對Spinner的各項進行監聽, 不用考慮上面的問題了。
Menu菜單欄(OptionsMenu)
在手機上觸摸按鍵Menu(實體按鈕、或者虛拟按鍵)時會彈出如右所示的Menu菜單選項。
那麼我們依然是從AllInOneActivity開始來介紹該OptionsMenu控件是怎麼能一直顯示在視圖當中,它又是怎麼被建立起來的。
先看onCreateOprionsMenu()這個方法,在它的内部使用的Menu是
它的清單檔案在右圖所示,是以可以看到Calendar的Menu項有“建立新日程”、“今天”、“搜尋”、“重新整理”、“選擇日期”、“要顯示的月曆”、“删除所有日程”、“設定”(action_create_event、...action_settings)。在上面的效果圖中之是以沒有“要顯示的月曆”是因為在截圖的時候沒有為月曆設定電子郵件賬戶、或者需要同步電子郵件月曆的賬戶。
其實看源碼的話,會發現這裡沒有什麼特别難懂的,就不怎麼介紹了,OptionsMenu的點選事件也比較容易了解在onOptionsItemSelected(MenuItem item)方法中判斷點選的Item然後通過CalendarController來派發對應的事件。
OptionsMenu的建立流程可參考下面的:
onCreateOptionsMenu() onOptionsItemSelected()調用mController來派發事件請求
IV.建立活動:
我們已經知道在OptionsMenu中會始終顯示一個“建立”按鈕,當點選這個建立按鈕的時候就自動跳入建立日程的編輯框,這個進入建立日程的業務流程如下:
1) 點選菜單建立月曆選項直接跳轉到EditEvent.java界面。
2) 建立活動界面EditEvent類:
建立活動界面顯示一些按鈕,文本框,下拉框,複選框,圖檔按鈕等控件。設定主題選折活動時間範圍,選擇地點,說明活動大概内容,清除搜尋記錄,設定邀請對象的電子郵件的快速回複,活動的起始日期預設可以下拉選擇,提醒時間分鐘,添加提醒時間。
除了點選OptionsMenu中的“建立”之外,我們還有第二類方法來開啟一個“建立日程”。
首先,我們來看一下如何在日視圖中建立一個日程:
下面左圖是一個日視圖,我們可以看到中間的時間線是目前所在的時間,在目前時間上已經添加了了四個日程,最左邊的綠塊兒跨越時間線,說明它是在目前時間之前發生的,并且正在進行還沒有結束的日程,而後三個綠塊兒,是即将要發生的日程。
那麼,從日視圖中添加一個日程需要在你標明的那個小時的Item上長按螢幕(也就是觸發一個LongTouchEvent事件),就會出現如右圖所示的AlertDialog,可以看到是在下午9:00所在的Item上觸發了一個LongTouchEvent事件。(當點選螢幕時觸發TouchEvent事件,隻是會在相應的Item上顯示一個“建立活動”的布局,我感覺是起到提示的作用,在這裡就不細細講述,它的效果如右邊所示)
在我們點選AlertDialog的“建立活動”之前的所有操作流程都是在DayView.java中進行的,那麼當你點選“建立活動”之後,就進入建立日程的業務當中了,當然在AllInOneActivity中如何進入到EditEventActivity去建立一個日程,當然是通過CalendarController這個控制中心了(已經介紹過了,這個CalendarController相當于MVC中的C)。
“建立日程”如右圖所示:
我們可以看到這個Fragment與之前4.0版本的“建立月曆”基本上是一樣的沒有做多大的改變,那麼在點選了“建立月曆”之後,start這個Activity之後,它是怎麼出現在螢幕的,當我們選擇了“取消”或者“儲存”之後,Calendar又做了哪些重要的背景操作,在接下來的介紹中,我會一一向大家說明。
首先需要注意的是,EditEventActivity.class是繼承自AbstractCalendarActivity.class的(如下所示):
我們可以看到AbstractCalendarActivity是采用的單例模式建立一個AsyncQueryService服務,這個服務是用來進行對CalendarProvider進行同步操作的。由于隻牽扯到界面的調用以及對MenuKey的調用,是以從源碼中可以看到EditEventActivity中對我們有用的方法有兩個onCreat、onOptionsItemSelected。
在onCreate中設定了視圖容器為下面的布局:
然後調用方法:
通過這個方法我們能夠從開啟這個Activity的Intent中擷取我們到的部分參數配置:EVENT_ID、isAllDayEvent、begin、end,通過這些參數對上圖中的一些布局進行資料的初始化工作。由于這部分的代碼流程比較通俗易懂,我就不詳細講解,代碼如下所示:
那麼擷取到了Event的事件Id、startTime、endTime等參數之後,onCreate調用FragmentManager來擷取此Activity要顯示的Fragment:
之後通過通過擷取mIsMultipane參數來配置ActionBar的DisplayOptions,再之後如果EditFragment不存在的話需要重新建立一個:
由于建立了一個EditEventFragment,而他的生命周期是伴随着Activity從onAttach開始的,是以接下來我們進入EditEventFragment.class,看他是如何建立起來的
它是調用的這個構造方法,可以明顯的看到它是帶有OptionsMenu的,可是實際的應用過程當中,它的OptionMenu卻被屏蔽掉了,也就是說在onCreateOptionsMenu中隻解析了CustomActionBar,而且onOptionsItemSelected()也僅僅是傳回的onActionBarItemSelected方法(這個方法是用來監聽CustomActionBar的),是以我說在EditFragment中的OptionsMenu沒有用。
那麼在EditFragment的onAttach中主要建立了一些執行個體:
然後是onCreate()方法,在onCreate中涉及到對之前狀态的加載(如果savedInstanceState存在,就添加儲存的配置),當onCreate方法執行之後,就會執行onCreateView(),在這個方法中,根據mIsReadOnly,mUseCustomActionBar這兩個boolean來對EditEventView和ActionBar設定不同的布局。
通過這一個構造方法,我們看到一個EditEventView在EditEventFragment中建立出來了。這個EditEventView與DayView相類似,我們所看到的上圖就是一個EditEventView的執行個體,它的布局檔案是edit_event.xml。這個布局檔案有兩層視圖最外層布局如右圖所示(我把他描述為第一層布局):
在這個布局當中比較重要的是在它的FrameLayout中外鍊的兩個布局,我把它描述為第二層布局。我們會發現在第一層布局中可以實作的是兩個功能:在進入EditView時顯示“正在載入…”;實作EditEventView的主題FrameLayout在布局内部上下滑動。真正填寫詳細的日程内容是在edit_event_1.xml的布局檔案當中的,大緻如右圖。
在上面的構造方法public EditEventView(Activity activity, View view, EditDoneRunnable done);中進行了對各種布局檔案的查找,以及對編輯器中的EditorInfo.IME_ACTION_DONE事件的監聽、設定TextView的最大輸入長度等。關于右邊的界面的一些參數的初始化以及監聽等都是在這個構造方法中進行的,并且最重要的是該構造方法調用了setModel方法,這個是個非常重要的方法,它使用給定的eventmodel,從裡面擷取配置參數,對EditEventView的各個View進行了适配。
對我來說比較主要的是在EditEventFragment中如何對EditEventView中布局進行初始化填充,點選“儲存”之後,又是如何對該Event事件進行處理(start-end是否合理、event是否已存在、是否需要更新已存在event、event是否已儲存到provider)。這一部分的操作都是在EditEventFrragment中進行的。
我們再次聚焦EditEventFragment的onAttach(Activity activity);方法
可以看到在這個方法中執行個體化了三個對象:mHelper、mHandler、mModel。
簡單說明一下這三個對象:
mHelper:一個工具類,主要功能有修正Model參數、儲存Event,判斷操作權限等;
mHandler:一個修正Model的工具,輔助Helper,在Helper處理Model之前修正Model;
mModel:通俗的講,就是一個模闆,對Event的各種參數添加與修改都是對這個末班的處理,最後儲存資料也是将這個模闆上的各種資料取出并儲存(Model就是一個Event的封裝)。
在onCreateView中我們注意到,EditEventFragment使用到了startQuery();方法:
startQuery(){};方法,其實也是對mModel進行修正的,判斷mEvent也就是啟動EditEventFragment的時候傳進來的Even、或Bandle是否為空,如果不為空,就修正id、startTime、endTime;然後根據Uri判斷是否是newEvent事件,如果是,那麼就再次修正Model并更新Modify模式為MODIFY_ALL,并讀取provider的清單。
三、AndroidManifest.xml檔案:
I、 權限:
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.INTERNET" />