1. 程式人生 > >Android之日曆原始碼淺析

Android之日曆原始碼淺析

前言:本文在整理過程中由於水平有限,若有不當之處,請指正!

1 常見介面及佈局的實現

1.1 日曆主介面:

 日曆主介面是由AllInOneActivity實現,對應四種檢視型別動態載入相應的Fragment實現。各檢視如下:

(1) 日檢視:在AllInOneActivity上載入了DayFragmentDayFragment的佈局採用了自定義佈局DayView,而填充該佈局檔案時用到了ViewSwitcherViewSwitch是一個檢視切換元件,可以把多個檢視重疊在一起,而每次只顯示一個檢視,而給ViewSwitch而建立要顯示的View時,有兩種方式:既可以在xml檔案中新增,也可以通過實現

ViewFactory,重寫makeView()新增。原始碼中採用第二種方式,如下:

DayFragmentxml佈局:


Java程式碼:

 

日檢視效果如下:

 

(2) 周檢視:也是載入了DayFragment,效果如下:


(3) 月檢視:在AllInOneActivity上載入了MonthByWeekFragmentMonthByWeekFragment的佈局是一個自定義的MonthListView,而MonthByWeekFragment繼承了SimpleDayPickerFragment,這類繼承了ListFragmentListFragment是一個自身帶有一個ListView

Fragment,在SimpleDayPickerFragment中,通過介面卡SimpleWeeksAdapter給對應的ListView新增資料,這些資料包括週數、是否顯示週數、每週的起始日、高亮顯示的日期。效果如下:


(4) 日程檢視:在AllInOneActivity上載入了AgendaFragmentAgendaFragment的佈局檔案也是使用了自定義的ListView:AgendaListView,並通過介面卡AdendaWindoeAdapter載入日程資料。效果如下:


1.2 新建活動介面

 EditEventActivity上動態載入了EditEventFragment

,並將EditEventFragment的檢視物件傳給了EditEventView,所有的控制元件的例項化和事件處理都在自定義的EditEventView中完成。介面如下:


1.3 設定介面

設定介面的ActivityCalendarSettingsActivity繼承了PreferenceActivity,在CalendarSettingsActivity中又載入了GeneralPreferencesAboutPreferences兩個PreferenceFragment。在PreferenceActivity中使用“Header+ Fragment”的模式,實現首選項設定,在當前Activity中展示一個或者多個首選項的標題,每個標題對應一個相應的PreferenceFragment,使用PreferenceActivity時,需要重寫onBuildHeaders(List<Fragment> target)方法填充標題對應的PreferenceFragment。如原始碼中:

CalendarSettingsActivityxml佈局檔案:


Java程式碼:

 

而在PreferenceFragment中,通過xml檔案定製它的首選項,在xml檔案中,建立佈局檔案時,必須使用PreferenceScreen作為根節點,在根節點下可以設定許多子節點。常用的Preference有如下幾種:

ListPreference:以對話方塊的形式顯示一系列詞目的Preference;

CheckBoxPreference:提供了複選框功能的Preference;

DialogPreference:提供了對話方塊功能的Preference;

EditTextPreference:DialogPreference的子類,加入了EditText的功能;

RingtonePreference:選擇鈴聲的Preference;

原始碼中建立xml:


Java程式碼:


介面效果:


1.4 刪除事件介面

  刪除活動介面為DeleteEventsActivity,該Activity繼承了ListActivity,自身帶有ListView,用來顯示所有建立的事件,事件的載入使用了CursorLoader,通過CursorLoader對建立的事件進行查詢並返回一個cursor物件,再通過介面卡EventListAdapter將資料設定到ListView中。


2 常用類

2.1 Time

 Time類:屬於android.text.format包中,在API22中被棄用,使用GregorianCalendar替代。日曆中所有時間的設定都使用Time並開啟一個子執行緒進行更新,如在DayView中:


2.1.1常用成員變數
isDst:設定是否為夏令時,(其他國家使用),設定為正數---是夏令時,為0---不是夏令時,負數---未知;
minute:分鐘【0-59】;
hour:[0,23];
month:[0-11]
monthDay:[1-31]
weekDay:[0-6]
yearDay:[0-365]
    ......
2.1.2 構造方法
Time(String timezone);
Time();
2.1.3 常用方法
void setToNow():將給定的Time物件的時間設定為當前時間;
void set(int second, int minute, int hour, int monthDay, int month, int year);
void set(int monthDay, int month, int year);設定時間
long setJulianDay(int julianDay):設定時間為給定的儒歷日,前提是必須處於同一時區;
String toString( ):返回當前時間以該格式:YYYYMMDDTHHMMSS ;
long normalize(boolean ignoreDst):確保每個欄位的值在範圍內,例如:3月32號,該方法呼叫後可以變為4月1 號;ignoreDst若為true,會自動將isDst的值變為-1,即未知;
 long toMillis(boolean ignoreDst):將時間轉變為毫秒,如果ignoreDst=true,則表示該方法忽視當前是否設定isDst變數,自動計算出正確的isDst的值;如果ignore設定為false,這個方法將會使用當前設定的“isDst”欄位,並且調整返回的時間如果isDst的欄位是錯誤的的話。
static int getJulianDay(long millis, long gmtoff):得到指定時區的指定時間點的julian日;對於給定的日期julian日在每個時區都是相同的。
2.2 CalendarController
CalendarController是Calendar的“控制檯”,Calendar中所有的載入Fragment、事件處理等都是通過CalendarController來完成的,事件處理具體步驟如下:
(1) 獲取CalendarController例項: 
mController = CalendarController.getInsitance(this);
(2)註冊EventHandler:
mController.registerFirstEventHandler(HANDLER_KEY, this);
EventHandler是CalendarController中的一個內部介面,事件的處理最終會在該介面中HandleEvent()方法中進行處理。
(3)呼叫sendEvent()傳送事件進行處理: 
mController.sendEvent(this, EventType.UPDATE_TITLE, t, t, -1, ViewType.CURRENT,mController.getDateFlags(), null, null);
sendEvent方法有許多的過載函式,通過這些過載函式,將引數全部封裝到了EventInfo中。
(4)handleEvent()進行處理.
在呼叫sendEvent()時,需要傳入的一個引數為事件型別,CalendarController中定義的事件型別有14種,常見的有:EventType.CREATE_EVENT:新建活動;
EventType.EDIT_EVENT:編輯活動
EventType.DELETE_EVENT:刪除活動
EventType.GO_TO:切換檢視
EventType.SEARCH:搜尋活動
EventType.LAUNCH_SETTINGS:啟動設定介面
根據不同的EventType從而處理不同的事件。

3 主要功能實現

3.1 檢視的切換

 AllInOneActivity中,通過ActionBar進行檢視的轉換,呼叫actionBarsetNavigationMode()設定actionBar的導航欄模式為下拉列表式,並實現OnNavigationListener 介面,重寫onNavigationItemSelected()方法,選擇不同的條目時會觸發此方法進行回撥。程式碼如下:


當用戶點選actionBar的導航列表中的條目時,會觸發onNavigationItemSelected()方法,在該方法中,通過不同的itemId進行檢視的切換,切換檢視時,使用了CalendarControllersendEvent()方法,在通過sendEvent()的過載函式,將事件資訊封裝到EventInfo中,呼叫handleEvent(),handleEvent()方法是CalendarController中的內部介面EventHandler中的方法,在AllInOneActivity中繼承了該介面,重寫了該方法,從而在handleEvent()中,呼叫setMainPane()方法進行Fragment的切換。在setMainPane()中,分別對不同的檢視型別進行不同的Fragment的例項化,並載入到Activity中,從而完成檢視的切換。


3.2 事件的同步

在增加或者刪除事件時,介面總能同時完成更新,使用了Loader載入器中的CursorLoaderCursorLoader可以實現非同步載入資料,這樣可以避免同步查詢時UI執行緒阻塞的問題,使用CursorLoader時,呼叫getLoaderManager().initLoader(int id, Bundle args, LoaderCallbacks<D> callback)進行Loader的建立或複用。因此,需要實現LoaderManager.LoaderCallbacks介面作為上述方法的第三個引數,並重寫三個方法:

onCreateLoader():建立CursorLoader物件;

onLoaderFinish():資料載入完畢時回撥;

onLoaderReset()Loader物件重置時回撥;

最後,將查詢資料後返回的Cursor物件當做資料來源填充給介面卡,從而更新介面卡所在的介面卡控制元件。原始碼中使用如下:


3.3 新增賬戶功能
新建事件時,若沒有新增賬戶或者沒有同步,會彈出對話方塊進行新增賬戶。在EditEventViewFragment中,會通過例項化AsyncQueryHandler的子類QueryHandler進行查詢日曆。核心程式碼如下:
mHandler.startQuery(TOKEN_CALENDARS, null, Calendars.CONTENT_URI, EditEventHelper.CALENDARS_PROJECTION, EditEventHelper.CALENDARS_WHERE,selArgs /* selection args */, null /* sort order */);
當該方法執行完畢後,會觸發onQueryComplete()方法,在該方法中,若查詢完畢後返回的Cursor物件為空,說明不存在日曆賬戶,會彈出對話方塊,不再執行後面方法,若返回的Cursor物件不為空,則會在EditEventView中的CalendarSpinner中填充Cursor中的日曆物件。核心程式碼如下:
/*返回的Cursor物件為null時*/
if (cursor == null || cursor.getCount() == 0) {
            AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
            builder.setTitle(R.string.no_syncable_calendars).setIconAttribute(
                    android.R.attr.alertDialogIcon).setMessage(R.string.no_calendars_found)
                    .setPositiveButton(R.string.add_account, this)
                    .setNegativeButton(android.R.string.no, this).setOnCancelListener(this);
            mNoCalendarsDialog = builder.show();
            return;
        }
若cursor不為空,會將Cursor中的資料新增給CalendarsSpinner,給CalendarsSpinner填充資料時,將cursor中的對應列的值取出載入到介面卡CalendarsAdapter中,從而給CalendarsSpinner新增資料:
mCalendarsSpinner.setAdapter(adapter);
在CalendarsAdapter中取出Cursor中每列的列數,再通過列數獲得該列的值:
 int colorColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR);
 int nameColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_DISPLAY_NAME);
 int ownerColumn = cursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT);
點選“確定”按鈕,會進行跳轉到新增賬戶的頁面,核心程式碼如下:
public void onClick(DialogInterface dialog, int which) {
        if (dialog == mNoCalendarsDialog) {
            mDone.setDoneCode(Utils.DONE_REVERT);
            mDone.run();
            if (which == DialogInterface.BUTTON_POSITIVE) {
 //啟動Settings包中的AddAccountSettings
                Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT);
                final String[] array = {"com.android.calendar"};
                nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array);
                nextIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
                mActivity.startActivity(nextIntent);
            }
        }
    }

3.4 事件提醒功能流程淺析

 事件提醒功能主要在AlertReceiverAlertService中進行,AlertReceiver是一個廣播接收者,AlertService是一個服務,當建立事件後,系統會在廣播中進行監聽,傳送廣播,並通過廣播開啟服務進行通知的傳送,其流程如下:


5 檢視的繪製

5.1 月檢視的繪製

月檢視對應的FragmentMonthByWeekFragment,而MonthByWeekFragment的父類為SimpleDayPickerFragmentSimpleDayPickerFragment繼承自ListFragment。它們之間的結構關係如下:


因此,在進行繪製時,在MonthWeekEventsView中進行繪製,相當於給介面卡設定佈局格式,繪製完成後,將對應Adapter設定給MonthListView,從而顯示在介面。在檢視繪製過程中,使用了Paint類和Canvas類對介面各元件進行繪製,主要包括:間隔線的繪製、背景色的繪製、日期數字的繪製、農曆的繪製、事件標誌的繪製、點選效果的繪製。
5.1.1 間隔線的繪製
間隔區域的繪製,使用了canvas.drawLines()方法,在區域內進行線條的繪製從而實現區域分割。核心程式碼如下:
   protected void drawDaySeparators(Canvas canvas) {
        float lines[] = new float[8 * 4];
        int count = 6 * 4;
        while (i < count) {
            int x = computeDayLeftPosition(i / 4 - wkNumOffset);
            lines[i++] = x;
            lines[i++] = y0;
            lines[i++] = x;
            lines[i++] = y1;
        }
        p.setColor(mDaySeparatorInnerColor);
        p.setStrokeWidth(DAY_SEPARATOR_INNER_WIDTH);
        canvas.drawLines(lines, 0, count, p);//lines包括2個座標,表示起始座標和終點座標
    }
5.1.2 背景色的繪製
繪製背景時,分為三種情況,“今天”的背景色,奇數月的背景、偶數月的背景色,通過給奇數月和偶數月設定不同的背景,能快速的區別每個月,原始碼如下:
protected void drawBackground(Canvas canvas) {
        int i = 0;
        int offset = 0;
        r.top = DAY_SEPARATOR_INNER_WIDTH;
        r.bottom = mHeight;
/*奇數月背景*/
        if (!mOddMonth[i]) {
            while (++i < mOddMonth.length && !mOddMonth[i])
                ;
            r.right = computeDayLeftPosition(i - offset);
            r.left = 0;
            p.setColor(mMonthBGOtherColor);
            canvas.drawRect(r, p);
            // compute left edge for i, set up r, draw
/*非奇數月但奇數月的前幾天和獲取焦點的月數的後幾天位於同一行*/
        } else if (!mOddMonth[(i = mOddMonth.length - 1)]) {
            while (--i >= offset && !mOddMonth[i]);
            i++;
            // compute left edge for i, set up r, draw
            r.right = mWidth;
            r.left = computeDayLeftPosition(i - offset);
            p.setColor(mMonthBGOtherColor);
            canvas.drawRect(r, p);
        }
        if (mHasToday) {//“今天”的背景,高亮顯示
            p.setColor(mMonthBGTodayColor);
            r.left = computeDayLeftPosition(mTodayIndex);
            r.right = computeDayLeftPosition(mTodayIndex + 1);
            canvas.drawRect(r, p);
        }
    }
5.1.3 天數的繪製
 繪製天數時,也分為兩種情況:獲取了焦點的月份的天數和未獲取焦點的月份的天數,天數的取值為[1,31],通過Time類的month屬性就可以設定某天的天數。
原始碼如下:
得到天數的陣列:
mDayNumbers[i] = Integer.toString(time.monthDay++);
繪製核心程式碼如下:
protected void drawWeekNums(Canvas canvas) {
        boolean isFocusMonth = mFocusDay[i];
        boolean isBold = false;
        mMonthNumPaint.setColor(isFocusMonth ? Color.RED : Color.YELLOW);


        // Get the julian monday used to show the lunar info.
        int julianMonday = Utils.getJulianMondayFromWeeksSinceEpoch(mWeek);
        Time time = new Time(mTimeZone);
        time.setJulianDay(julianMonday);
/*判斷是否是“今天”*/
        for (; i < numCount; i++) {
            if (mHasToday && todayIndex == i) {
                mMonthNumPaint.setColor(Color.BLUE);
                mMonthNumPaint.setFakeBoldText(isBold = true);
/*判斷是否是獲取了焦點的月*/
            } else if (mFocusDay[i] != isFocusMonth) {
                isFocusMonth = mFocusDay[i];
                mMonthNumPaint.setColor(isFocusMonth ? Color.RED : Color.YELLOW);
            }
            x = computeDayLeftPosition(i - offset) - (SIDE_PADDING_MONTH_NUMBER);
      canvas.drawText(mDayNumbers[i], x, y, mMonthNumPaint);
在繪製天數的方法中,會對農曆也進行繪製,繪製時首先判斷當前語言環境是否支援農曆,其次進行繪製,通過LunarUtils中的靜態方法進行判斷是否顯示農曆,程式碼如下:
public static boolean showLunar(Context context) {
        Locale locale = Locale.getDefault();
        String language = locale.getLanguage().toLowerCase();
        String country = locale.getCountry().toLowerCase();
        return ("zh".equals(language) && ( "cn".equals(country) || ("tw".equals(country)  )  || ("hk".equals(country))));
}
在繪製數字時通過呼叫該靜態方法判斷是否顯示農曆,若顯示,則進行農曆的繪製,核心程式碼如下:
ArrayList<String> infos = new ArrayList<String>();
/*獲取給定日期的農曆*/
LunarUtils.get(getContext(), year, month, monthDay,
                        LunarUtils.FORMAT_LUNAR_SHORT | LunarUtils.FORMAT_MULTI_FESTIVAL, false,
                        infos);
    for (int index = 0; index < infos.size(); index++) {
        String info = infos.get(index);
         if (TextUtils.isEmpty(info)) continue;
         infoX = x;
         infoY = y + (mMonthNumHeight + LUNAR_PADDING_LUNAR) * (num + 1);
         canvas.drawText(info, infoX, infoY, mMonthNumPaint);
          num = num + 1;
}                    
}
5.1.4 事件標誌的繪製
當某一天存在使用者新建的活動時,會在當月的區域內繪製一個小矩形,繪製原理與繪製間隔線相同,略去。
5.1.5 點選事件效果的繪製
當點選月檢視某天時,會出現類似於selector的效果,也是通過繪製進行實現,核心程式碼如下:
private void drawClick(Canvas canvas) {
        if (mClickedDayIndex != -1) {
            int alpha = p.getAlpha();
            p.setColor(mClickedDayColor);
            p.setAlpha(mClickedAlpha);
            r.left = computeDayLeftPosition(mClickedDayIndex);
            r.right = computeDayLeftPosition(mClickedDayIndex + 1);
            r.top = DAY_SEPARATOR_INNER_WIDTH;
            r.bottom = mHeight;
            canvas.drawRect(r, p);
            p.setAlpha(alpha);//設定透明度
        }
}
5.1.6 星期的繪製
在介面的月數上端,會顯示對應日期是周幾,這部分內容是通過利用Strin[]陣列和android.text.format包中的DateUtil類進行設定星期幾。在setUpHeader()中:
protected void setUpHeader() {
    mDayLabels = new String[7];
    for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
/*獲取 “星期幾” */
   mDayLabels[i-Calendar.SUNDAY]= DateUtils.getDayOfWeekString(i,DateUtils.LENGTH_MEDIUM).toUpperCase();}}




5.2 日檢視、周檢視的繪製
日檢視和周檢視都是通過在AllInOneActivity中動態載入DayFragment,並給DayFragment設定自定義檢視DayView實現的。因此可以歸納在一起。在DayView中進行繪製時,區別繪製日檢視還是周檢視通過AllInOneActivity中例項化DayFragment時傳入的引數numOfDays確定。原始碼如下:
載入日檢視時,numOfDays = 1:
frag = new DayFragment(timeMillis, 1);
載入周檢視時,numOfDays = 7:
frag = new DayFragment(timeMillis, 7);
 從而在DayFragment中建立DayView時,通過numOfDays作為判斷條件,獲取不同效果的日檢視和周檢視。日檢視和周檢視的繪製都在DayView中完成。繪製各效果的方法之間的關係如下圖:


各方法功能如下:
doDraw()裡面包括:
drawBgColors(): 繪製檢視背景色;
drawGridBackground():繪製佈局間隔線,通過傳入的mNumDays計算是周檢視還是日檢視;
drawHours() ---> setupHourTextPaint(p):  繪製小時
drawSelectedRect():繪製點選某一區域時的圖案,包括所選中區域的背景和“+新建事件”的繪製。
drawEvents() ----> drawEventRect()、drawEventText();繪製事件;
drawCurrentTimeLine();繪製當前時間線
drawAfterScroll()裡包括:
drawAllDayHighlights():左上角高亮表示全天活動;
drawAllDayEvents():繪製全天活動的邊界;
drawUpperLeftCorner():當存在全天活動時,繪製全天活動左上角的圖案;
drawDayHeaderLoop(): 繪製周檢視標題欄;
drawAmPm(canvas, p):繪製“上午”、“下午”
drawScrollLine():繪製主介面與標題欄之間的分割線。
使用以上方法進行介面的繪製,繪製內容主要包括繪製字型、繪製矩形區域、繪製線條。繪製時呼叫以下方法,如下:
Canvas.drawText(String text, float x, float y, Paint paint);//繪製字型
Canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint)//繪製線條
Canvas.drawRect(Rect r, Paint paint)//繪製矩形

5.3 日程檢視的載入過程淺析

日程檢視是在AllInOneActivity中載入了AgendaFragmentAgendaFragment中的佈局檔案是自定義的ListView,所有的事件都是以ListView中條目的形式展示在螢幕上,因此不存在View的繪製,而是通過繼承ListViewAdapter來實現。

日程檢視的最頂層佈局是自定義的StickyHeaderListView,繼承自FrameLayout,主要提供了一些介面,以及處理滑動事件的監聽,主介面是AgendaListView,繼承自ListView,介面卡為AgendaWindowAdapter,日程中還包括一些介面卡,它們的功能如下:

AgendaWindowAdapter:為AgendaListView新增資料,將AgendaAdapterAgendaByDayAdapter中的資料進行整合;

AgendaByDayAdapter:顯示星期、月份的條目。

AgendaAdapter:顯示每個事件的標題、時間、地點和左邊紅點;

介面效果如下:

①區域:給ListView設定HeadTestFooterText.原始碼如下:

mAgendaListView.addHeaderView(mHeaderView);

mAgendaListView.addFooterView(mFooterView);

當觸控更新Header時,每次在查詢完成之後,也就是onQueryComplete()方法中呼叫以下方法進行日期的更新:updateHeaderFooter(final int start, final int end)

②區域:使用AgendaByDayAdapter將資料載入到AgendaListView中:包括星期和日期。重寫getView()載入佈局,載入佈局為:agenda_day.xml;

③區域:使用AgendaAdapter將資料載入到AgendaListView中,包括事件標題、時間、地點等,載入的佈局為:agenda_item.xml ;


5.3.1 AgendaWindowAdapter的載入過程分析
AgendaFragment中只有一個ListView——AgendaListView,給該ListView設定介面卡,原始碼如下:
setAdapter(mWindowAdapter);
需要介面卡物件,例項化介面卡時,通過重寫getView()方法載入佈局。核心程式碼如下:
public View getView(int position, View convertView, ViewGroup parent) {
        final View v;
        DayAdapterInfo info = getAdapterInfoByPosition(position);
        if (info != null) {
            int offset = position - info.offset;
            v = info.dayAdapter.getView(offset, convertView,
                    parent);
                } else {
            TextView tv = new TextView(mContext);
            tv.setText("Bug! " + position);
            v = tv;
        }
 DayAdapterInfo是AgendaWindowAdapter的內部類,這個類中將AgendaByDayAdapter的物件作為它的一個屬性,也就是說DayAdapterInfo可以持有AgendaByDayAdapter的物件,通過DayAdapterInfo物件獲取AgendaByDayAdapter的例項從而開始呼叫AgendaByDayAdapter的getView()方法。
 在AgendaByDayAdapter中,有一個內部類RowInfo,主要作用是將資料庫中查詢到的事件資訊作為它的屬性,使用時可通過例項化它的物件進行獲取。其中有個屬性mType,區分是否是一個事件 (TYPE_DAY or an event TYPE_MEETING)。在getView()方法中,通過RowInfo.mType進行判斷,從而進行不同條目佈局的載入,核心程式碼如下:
public View getView(int position, View convertView, ViewGroup parent) {
        RowInfo row = mRowInfo.get(position);
/*是日期條目,也就是2區域*/
 if (row.mType == TYPE_DAY) {
            ViewHolder holder = null;
            View agendaDayView = null;
            if (holder == null) {
                holder = new ViewHolder();
agendaDayView = mInflater.inflate(R.layout.agenda_day, parent, false);
holder.dayView = (TextView) agendaDayView.findViewById(R.id.day);
holder.dateView = (TextView) agendaDayView.findViewById
(R.id.date);
 }
/*是一個事件*/
} else if (row.mType == TYPE_MEETING) {
  View itemView = mAgendaAdapter.getView(row.mPosition, convertView, parent);
 AgendaAdapter.ViewHolder holder = ((AgendaAdapter.ViewHolder) itemView.getTag());
            return itemView;
}
 從上述程式碼中可以看出,當需要載入的資料項為日期時,直接載入佈局,當需要載入的資料項為事件時,呼叫AgendaAdapter的getView()進行載入.在AgendaAdapter中,通過bindView()繫結佈局檔案。流程圖如下: