您好,登錄后才能下訂單哦!
1、程序結(jié)構(gòu):
千言萬語(yǔ),不如一張圖來的清晰
1) 由于CalendarController包含了一個(gè)UITableView指針,因此CalendarController需要實(shí)現(xiàn)UITableDataSource以及UITableViewDelegate與UITableView進(jìn)行交互。 2) UITableView包含多個(gè)CalendarView,這樣就能利用UITableView的手勢(shì)滑動(dòng)功能以及Cell重用功能。 3) CalendarView繼承自UIControl,因?yàn)閁IControl將相對(duì)底層的觸摸事件轉(zhuǎn)換為容易操作的控件事件。主要為了使用UIControlEventTouchUpInside這個(gè)事件。 4) CalendarDelegate仿照ios mvc模式,用于類之間的解耦(面向接口編程)以及類之間的通信。
2、 CalendarDelegate 協(xié)議:
@protocol CalendarDelegate <NSObject> //年月和UITableView以及其中CalendarView之間關(guān)系映射 //具體見下面代碼分析 -(int) calcCalendarCount; -(SDate) mapIndexToYearMonth : (int) index; -(int) mapYearMonthToIndex : (SDate) date; //用于顯示到指定的年月范圍 -(void) showCalendarAtYearMonth : (SDate) date; //用于時(shí)間期限管理以及選中判斷 -(BOOL) isInSelectedDateRange : (SDate) date; -(void) setSelectedDateRangeStart : (SDate) start end : (SDate) end; -(void) setEndSelectedDate : (SDate) end; //迫使整個(gè)UITableView重繪 -(void) repaintCalendarViews; //計(jì)數(shù)器,用于判斷touch次數(shù) -(void) updateHitCounter; -(int) getHitCounter; @end
3、 CalendarController:
//.h文件接口聲明 #import <UIKit/UIKit.h> @interface ViewController :UIViewController @end
//.m文件 #import "ViewController.h" #import "CalendarDelegate.h" #import "CalendarView.h" //實(shí)現(xiàn)了如下三個(gè)delegate @interface ViewController () <UITableViewDataSource,UITableViewDelegate,CalendarDelegate> { //用于計(jì)算出多少個(gè)月歷,具體見下面代碼 int _startYear; int _endYear; //每個(gè)月歷控件的高度,上面的個(gè)數(shù)和此地的高度,就可以計(jì)算整個(gè)UITableView的高度以及進(jìn)行定位操作 float _calendarHeight; //用于選中操作時(shí)候,時(shí)間范圍的比較(time_t實(shí)際是個(gè)64位的整型值,適合做比較操作,具體看實(shí)現(xiàn)代碼) time_t _startTime; time_t _endTime; } //選中值的年月表示方式,方便顯示而已,實(shí)際操作都轉(zhuǎn)換成time_t類型 @property (nonatomic,assign) SDate begDate; @property (nonatomic,assign) SDate endDate; //點(diǎn)擊計(jì)數(shù)器,用于確定當(dāng)前點(diǎn)擊的奇偶性,因此改月歷控件涉及兩次操作,用于區(qū)域選者 @property (nonatomic) int hitCounter; //作為Calendar的父容器,用于處理滑動(dòng)以及cell重用 @property (weak, nonatomic) IBOutlet UITableView *tableView; @end
-(int) calcCalendarCount { SDate date; date_get_now(&date); //計(jì)算出當(dāng)前的年月到n年前的1月份的月數(shù) //加設(shè)當(dāng)前為2016年8月,n為5,則月份范圍為[2011年1月---2016年8月 總計(jì)月數(shù)為68],具體算法如下: int diff = _endYear - _startYear + 1; diff = diff * 12; diff -= 12 - date.month; return diff; } //UITableView的DatatSource有個(gè)必須實(shí)現(xiàn)的協(xié)議函數(shù),用于返回當(dāng)前UITableView可以容納的總數(shù): - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { int ret = [self calcCalendarCount]; return ret; }
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { //返回的是當(dāng)前的calendarView的高度 //UITableView需要知道月歷(月份)的個(gè)數(shù)以及月歷控件的高度,就可以計(jì)算出整個(gè)UITableView的Content的height了 return _calendarHeight; }
千言萬語(yǔ),不如再來一張圖來的清晰
-(SDate) mapIndexToYearMonth : (int) index { SDate ret; //調(diào)用c函數(shù),將索引號(hào)映射成年月,用于UITableView創(chuàng)建calendarView時(shí)現(xiàn)實(shí)月歷標(biāo)題 date_map_index_to_year_month(&ret, _startYear, index); return ret; } //調(diào)用mapIndexToYearMonth: - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString* calendarID = @"calendarID"; float width = self.tableView.frame.size.width; //從行索引號(hào)映射到年月 SDate date = [self mapIndexToYearMonth:(int)indexPath.row]; //獲取重用的cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:calendarID ]; //如果為null,說明不存在,創(chuàng)建該cell if(cell == nil) { //可以在此斷點(diǎn),查看一下具體生成了多少個(gè)calendarView(我這里生成了3個(gè)) //說明UITableView可見rect有三個(gè)calendarView相交 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:calendarID]; [cell setTag:10]; //手動(dòng)創(chuàng)建CalendarView CalendarView* calendarView = [[CalendarView alloc] initWithFrame:CGRectMake(0, 0, width, _calendarHeight)]; //設(shè)置CalednarDelegate calendarView.calendarDelegate = self; //給定一個(gè)tag號(hào),用于重用時(shí)獲得該view [calendarView setTag:1000]; [cell.contentView addSubview:calendarView]; } //通過tag號(hào),獲取view CalendarView* view =(CalendarView*) [cell.contentView viewWithTag:1000]; //設(shè)置CalendarView的年月 [view setYearMonth:date.year month:date.month]; //[view setNeedsDisplay]; return cell; } -(int) mapYearMonthToIndex:(SDate)date { int yearDiff = date.year - _startYear; int index = yearDiff * 12; index += date.month; index -= 1; return index; } //調(diào)用mapYearMonthToIndex -(void) showCalendarAtYearMonth:(SDate)date { if(date.year < _startYear || date.year > _endYear) return; //將年月表示映射成UITableView中的索引號(hào),根據(jù)索引計(jì)算出要滾動(dòng)到的目的地 int idx = [self mapYearMonthToIndex:date]; //如上圖所示:當(dāng)idx = calendarViews.length-1時(shí),可能存在超過整個(gè)UITableView ContentSize.height情況,此時(shí),UITableView會(huì)自動(dòng)調(diào)整contentOffset的值,使其符合定位到最底端,android listview也是如此。 self.tableView.contentOffset = CGPointMake(0.0F, idx * _calendarHeight ); }
1) 從上圖以及代碼,應(yīng)該很清楚的了解了映射和定位問題的過程 2) 從上圖中,我們也可以了解到UITableView的滾動(dòng)原理,UITableView的Frame是Clip區(qū)域,滾動(dòng)的內(nèi)容存放于Content中。 3) UITableView可以說是移動(dòng)開發(fā)中最常用,最重要的一個(gè)控件(還有一個(gè)是UICollectionView)。有兩個(gè)主要功能點(diǎn):滾動(dòng)(UIScrollView父類)和cell復(fù)用。以后有機(jī)會(huì)我們來從頭到尾實(shí)現(xiàn)一個(gè)帶有上述功能的控件。
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //獲取當(dāng)前的年月 SDate date; date_get_now(&date); //default有3年 _startYear = date.year-3; _endYear = date.year; /* //當(dāng)年也支持 _startYear = date.year; _endYear = date.year; */ //touch 計(jì)數(shù)器 _hitCounter = 0; float scale = 0.6F;//硬編碼,最好由外部設(shè)置 //float scale = 0.5F;//硬編碼,最好由外部設(shè)置 _calendarHeight = self.tableView.frame.size.height * scale; self.tableView.dataSource = self; self.tableView.delegate = self; //default定位顯示當(dāng)前月份 if (self.begDate.year == 0) { self.begDate = date; [self showCalendarAtYearMonth:date]; }else{ //當(dāng)然你也可以設(shè)置具體月份重點(diǎn)顯示 [self showCalendarAtYearMonth:self.begDate]; } }
到目前為止,支持CalendarController運(yùn)行的所有方法都分析完畢,接下來我們要看一下CalendarView相關(guān)的實(shí)現(xiàn)。(CalendarDelegate還有一些方法沒分析,因?yàn)檫@些方法是由CalendarView調(diào)用的,由此可見,IOS中的Delegate除了面向接口編程外,還有一個(gè)功能就是類之間的通信)
4、 CalendarView:
//.h文件 @interface CalendarView : UIControl -(void) setYearMonth : (int) year month : (int) month; @property (weak, nonatomic) id calendarDelegate; @end
//.m文件 @interface CalendarView() { /* blf: 引用c結(jié)構(gòu),所有月歷相關(guān)操作委托給SCalendar的相關(guān)函數(shù) SCalendar 使用棧內(nèi)存分配 */ SCalendar _calendar; //這是一個(gè)很重要的變量,具體源碼中說明 int _lastMonthDayCount; //存放月歷的日期和星期字符串 NSMutableArray* _dayAndWeekStringArray; //string繪制時(shí)的大小 CGSize _dayStringDrawingSize; CGSize _weekStringDrawingSize; }
- (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code //年月區(qū)塊和星期區(qū)塊的大小按當(dāng)前view高度的比例來設(shè)定 float yearMonthHeight = frame.size.height * 0.095F; float weekHeight = frame.size.height * 0.089F; //初始化月歷控件,計(jì)算出各個(gè)區(qū)塊部分的大小 calendar_init(&_calendar, frame.size, yearMonthHeight, weekHeight); SDate date = _calendar.date; //此時(shí)date是上個(gè)月 date_get_prev_month(&date, 1); self.backgroundColor = [UIColor clearColor]; //設(shè)置日期區(qū)塊的大小 CGRect rc; calendar_get_day_cell_rect(&_calendar,&rc,0,0); CGSize size; size.height = rc.size.height- 15 ; size.width = rc.size.width - 15; //預(yù)先分配38個(gè)字符串容量的數(shù)組 _dayAndWeekStringArray = [NSMutableArray arrayWithCapacity:38]; //0--30表示最多31天日期字符串 for(int i = 0; i < 31; i++) [_dayAndWeekStringArray addObject: [NSString stringWithFormat:@"%02d",i+1]]; //31--37存儲(chǔ)星期字符串 [_dayAndWeekStringArray addObject:@"周日"]; [_dayAndWeekStringArray addObject:@"周一"]; [_dayAndWeekStringArray addObject:@"周二"]; [_dayAndWeekStringArray addObject:@"周三"]; [_dayAndWeekStringArray addObject:@"周四"]; [_dayAndWeekStringArray addObject:@"周五"]; [_dayAndWeekStringArray addObject:@"周六"]; //計(jì)算出日期字符串的繪制用尺寸 _dayStringDrawingSize = [self getStringDrawingSize: [_dayAndWeekStringArray objectAtIndex:0]]; //計(jì)算出星期字符串的繪制用尺寸 _weekStringDrawingSize = [self getStringDrawingSize: [_dayAndWeekStringArray objectAtIndex:31]]; //UIControl基于控件的事件處理系統(tǒng),掛接UIControlEventTouchUpInside處理程序 [self addTarget:self action:@selector(handleTouchEvent:forEvent:) forControlEvents:UIControlEventTouchUpInside]; } return self; } //計(jì)算要繪制字符串的尺寸的函數(shù)如下: -(CGSize) getStringDrawingSize:(NSString*)str { NSAttributedString* attStr = [[NSAttributedString alloc] initWithString:str]; NSRange range = NSMakeRange(0, attStr.length); NSDictionary* dic = [attStr attributesAtIndex:0 effectiveRange:&range]; CGRect rect = [str boundingRectWithSize:CGSizeMake(0, 0) options:NSStringDrawingUsesLineFragmentOrigin attributes:dic context:nil]; return rect.size; }
從上面類聲明和初始化代碼,引出幾個(gè)問題:
1)為什么繼承自UIControl? 2)為什么delegate使用weak? 3)為什么delegate 聲明為id? 4)為什么棧分配? 5)為什么同一個(gè)CalendarView的類聲明需要分別在.h和.m文件中,或者換種說法:這樣做有什么好處? 6)為什么初始化只實(shí)現(xiàn)了initWithFrame,沒有實(shí)現(xiàn)initWithCoder,在哪種情況下,還需要override initWithCoder函數(shù)?
-(void) drawStringInRectWithSize : (NSString*) string rect:(CGRect)rect size:(CGSize) size color : (UIColor*) color { CGPoint pos; //下面算法是讓文字位于要繪制的Rect的水平和垂直中心 //也就是劇中對(duì)齊 pos.x = (rect.size.width - size.width) * 0.5F; pos.y = (rect.size.height - size.height) * 0.5F; pos.x += rect.origin.x; pos.y += rect.origin.y; //由于周日和周六與平常文字顏色有差別,因此需要color NSDictionary * attsDict = [NSDictionary dictionaryWithObjectsAndKeys: color, NSForegroundColorAttributeName, nil ]; [string drawAtPoint:pos withAttributes:attsDict]; }
1) opengles API 利用gpu加速,速度最快,難度相對(duì)最大,自由度也最高,需要?jiǎng)?chuàng)建專用的GL上下文環(huán)境。基于狀態(tài)機(jī)模式,需要設(shè)置各種繪制狀態(tài)以及恢復(fù)狀態(tài)。最重要的是跨平臺(tái),android以及windows,Linux都可以用(cocos2d-x基于opengles)。 2) quartz API 使用cpu光柵化,不需要GL上下文環(huán)境,直接可在控件表面進(jìn)行繪制,相對(duì)底層,基于狀態(tài)機(jī)模式,需要設(shè)置各種繪制狀態(tài)以及恢復(fù)狀態(tài) 3) UIKit中對(duì)quartz API的二次封裝,例如UIBezierPath類,封裝了大部分的shape,方便易用,我們就用這個(gè)類來進(jìn)行繪制。上面兩種API,以后有機(jī)會(huì)我們可以專門來分析一下。
圓的貝塞爾路徑對(duì)象(由圓心和半徑定義):
-(void) drawCircleInRect : (CGRect) rect color : (UIColor*) color isFill : (BOOL) isFill { //取width和height最小的值作為要繪制的圓的直徑,這樣就不會(huì)將圓繪制范圍超出rect float radiu = rect.size.width < rect.size.height ? rect.size.width : rect.size.height; //將圓的中心點(diǎn)從rect的左上角平移到rect的中心點(diǎn) CGPoint center; center.x = rect.origin.x + rect.size.width * 0.5F; center.y = rect.origin. y + rect.size.height * 0.5F; //圓是由圓心和半徑定義的 radiu *= 0.5F; //創(chuàng)建一個(gè)圓的bezier路徑對(duì)象 UIBezierPath* circle = [UIBezierPath bezierPathWithArcCenter:center radius:radiu startAngle:0.0F endAngle:2.0F*3.1415926F clockwise:true]; //填充繪制(日期選中狀態(tài)) if(isFill == YES) { [color setFill]; [circle fill]; } else { //沒選中狀態(tài),用stroke方式繪制 [color setStroke]; [circle stroke]; } }
圓角矩形的貝塞爾對(duì)象(由Rect和半徑定義):
-(void) drawRoundRect : (CGRect) rect radius : (CGFloat)radius { UIBezierPath* roundRect = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius]; [[UIColor colorWithRed:52/255.0 green:175/255.0 blue:248/255.0 alpha:1.0] setFill]; [roundRect fill]; }
代碼很長(zhǎng),我們拆分成幾個(gè)區(qū)塊來分析
//1、見上圖,繪制年月信息 //blf:獲取原生繪圖context指針,所有原生繪圖api都是c語(yǔ)言api方式 //CGContextRef context = UIGraphicsGetCurrentContext(); CGRect rc; calendar_get_year_month_section_rect(&_calendar, &rc); //NSString* drawStr = @" " + _calendar.date.year + @"年" + _calendar.date.month + @"月"; NSString* drawStr = [NSString stringWithFormat:@"%d年%d月",_calendar.date.year,_calendar.date.month]; //繪制年月信息 [self drawYearMonthStr:drawStr rect:rc];
//2、見上圖,繪制星期信息 //_dayAndWeekStringArray中31-37索引保存的是星期字符串 for(int i= 0; i < 7; i++) { //獲取星期區(qū)塊中某個(gè)cell的rect calendar_get_week_cell_rect(&_calendar, &rc, i); if(i == 0 || i == 6) { //雙休日黑色 [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex:31 + i] rect:rc size:_weekStringDrawingSize color: [UIColor blackColor]]; } else { //其他時(shí)間藍(lán)色 [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex:31 + i] rect:rc size:_weekStringDrawingSize color: [UIColor blueColor]]; } }
//3、見上圖紅色邊框部分,繪制上個(gè)月日期信息 CGPoint dayRectOffset; //獲取日期區(qū)塊的rect calendar_get_day_section_rect(&_calendar, &rc); //紀(jì)錄日期區(qū)塊的起始位置 dayRectOffset = rc.origin; //當(dāng)前月份1號(hào)在日期cells中的起始索引號(hào) int begin = _calendar.dayBeginIdx; //當(dāng)前月份結(jié)束索引號(hào) int end = begin + _calendar.dayCount; //繪制上個(gè)月的日期,假設(shè)begin = 5 i=[4,3,2,1,0] for(int i = begin - 1; i >= 0; i--) { calendar_get_day_cell_rect_by_index(&_calendar, &rc, i); //計(jì)算出位置偏移量 rc.origin.x += dayRectOffset.x; rc.origin.y += dayRectOffset.y; //縮小一下繪制rect的尺寸而已 rc.origin.x += 5; rc.origin.y += 5; rc.size.width -= 10; rc.size.height -= 10; //繪制圓圈 [self drawCircleInRect:rc color:[UIColor colorWithRed:245/255.0 green:245/255.0 blue:245/255.0 alpha:1.0] isFill:YES]; //計(jì)算方式涉及到了_lastMonthDayCount //假設(shè)上個(gè)月有30天,本月的begin為5,則 //則30-(5-4)= 29 ---->0base--->30號(hào) // 30- (5-3)= 28 ---->0base--->29號(hào) // 30- (5-2)= 27 ---->0base--->28號(hào) // 30- (5-1)= 26 ---->0base--->27號(hào) // 30- (5-0)= 25 ---->0base--->26號(hào) int dayIdx = _lastMonthDayCount - (begin - i); //繪制圓圈中的日期 [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex: dayIdx] rect:rc size:_dayStringDrawingSize color:[UIColor colorWithRed:223/255.0 green:223/255.0 blue:223/255.0 alpha:1.0]]; }
//4、見上圖紅色邊框部分,繪制下個(gè)月日期信息 for(int i = end; i < 42; i++) { calendar_get_day_cell_rect_by_index(&_calendar, &rc, i); rc.origin.x += dayRectOffset.x; rc.origin.y += dayRectOffset.y; rc.origin.x += 5; rc.origin.y += 5; rc.size.width -= 10; rc.size.height -= 10; [self drawCircleInRect:rc color:[UIColor colorWithRed:245/255.0 green:245/255.0 blue:245/255.0 alpha:1.0] isFill:YES]; //索引是i-end,很容易理解的 [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex: i - end] rect:rc size:_dayStringDrawingSize color:[UIColor colorWithRed:223/255.0 green:223/255.0 blue:223/255.0 alpha:1.0]]; }
typedef struct _selectRange { int rowIdx; //為了方便處理是否同一行 int columIdx;//行列轉(zhuǎn)換一緯數(shù)組索引 CGRect rect; //紀(jì)錄要繪制的rect } selectRange;
//5、繪制當(dāng)前月份的日期,包括選中,未選中以及日期文字 //使用c結(jié)構(gòu),并初始化相關(guān)變量 selectRange ranges[31]; memset(ranges,0,sizeof(ranges)); int rangeCount = 0; //繪制當(dāng)前月的日期 for(int i = begin ; i < end; i++) { calendar_get_day_cell_rect_by_index(&_calendar, &rc, i); rc.origin.x += dayRectOffset.x; rc.origin.y += dayRectOffset.y; rc.origin.x += 5; rc.origin.y += 5; rc.size.width -= 10; rc.size.height -= 10; SDate date; date_set(&date, _calendar.date.year, _calendar.date.month, i - begin + 1 ); //如果當(dāng)前日期在選中時(shí)間范圍內(nèi),則batch起來,由drawSelectRange進(jìn)行繪制 //因?yàn)樾枰幚頁(yè)Q行這種效果(drawSelectRange中處理,因此緩存起來二次處理比較方便) //與delegate通信 if([self.calendarDelegate isInSelectedDateRange:date]) { ranges[rangeCount].rowIdx = i / 7; //映射成行索引 ranges[rangeCount].columIdx = i % 7; //映射成列索引 ranges[rangeCount].rect = rc; //當(dāng)前行列的rect紀(jì)錄下來 rangeCount++; //計(jì)數(shù)器增加1 } else { //沒有選中的,就直接繪制圓圈和當(dāng)中的日期號(hào) [self drawCircleInRect:rc color:[UIColor colorWithRed:234/255.0 green:234/255.0 blue:234/255.0 alpha:1.0] isFill:NO]; [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex: i - _calendar.dayBeginIdx] rect:rc size:_dayStringDrawingSize color:[UIColor colorWithRed:107/255.0 green:107/255.0 blue:107/255.0 alpha:1.0]]; } } //NSLog(@"select day count = %d",rangeCount); //rangeCount紀(jì)錄了選中的數(shù)量,ranges則紀(jì)錄了要繪制的所有信息 [self drawSelectRange:ranges count:rangeCount]; //選中的圈圈的文字由下面代碼繪制 for(int i = 0; i < rangeCount; i++) { //重新將行列(二維)索引號(hào)映射一緯數(shù)組索引號(hào) int idx = ranges[i].rowIdx * 7 + ranges[i].columIdx; //idx - begin就是當(dāng)前的要繪制的日期文字的索引號(hào) [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex: idx - begin] rect:ranges[i].rect size:_dayStringDrawingSize color:[UIColor whiteColor]]; }
//blf:注意 參數(shù)ranges是數(shù)組名,數(shù)組名表示數(shù)組的首地址 // 還有就是selectRange是c結(jié)構(gòu),當(dāng)做指針操作時(shí)要用->而不是.尋址操作符 -(void) drawSelectRange : (selectRange* ) ranges count : (int) count { //兩種情況下count = 1 //第一選則,或者第二次選中的和第一次選中的是同一個(gè)日期cell //此時(shí)是繪制圓形而不是roundedRect if(count == 1) { [self drawCircleInRect : ranges[0].rect color:[UIColor colorWithRed:52/255.0 green:175/255.0 blue:248/255.0 alpha:1.0] isFill:YES]; //退出函數(shù) return; } //并不是第一次選者且第二次選者不是和第一次選者一致時(shí) //獲取cell rect的width CGRect rect; calendar_get_day_cell_rect_by_index(&_calendar, &rect, 0); float width = rect.size.width; //用于紀(jì)錄上一次的行號(hào),初始化,紀(jì)錄的是第一行的索引號(hào) int lastRowIdx = ranges[0].rowIdx; //計(jì)數(shù)器,用來紀(jì)錄當(dāng)前行的cell的數(shù)量 int sameRowCellCount = 0; for(int i = 0; i < count; i++) { //從ranges數(shù)組中獲取一個(gè)結(jié)構(gòu)時(shí)候,使用了&取地址操作符 //因?yàn)榉乐拱l(fā)生拷貝,如果不是取地址的話,賦值會(huì)發(fā)生memcopy行為 selectRange* range = &ranges[i]; //行號(hào)相同,則同一行啦 if(range->rowIdx == lastRowIdx) { sameRowCellCount++; } else { //行號(hào)不同,說明換行了,因此要繪制當(dāng)前行 CGRect rc; //i - sameRowCellCount找到起始索引 rc.origin = ranges[i - sameRowCellCount].rect.origin; rc.size.height = range->rect.size.height; rc.size.width = width* (sameRowCellCount) - 10.0F; //很可能存在這種情況,既選中的是周六開始的,因此繪制的是圓形而不是roundedRect if(sameRowCellCount == 1) { [self drawCircleInRect:rc color:[UIColor colorWithRed:52/255.0 green:175/255.0 blue:248/255.0 alpha:1.0] isFill:YES]; } else { //一般情況,繪制roundedRect [self drawRoundRect:rc radius:rc.size.height]; } sameRowCellCount = 1;//標(biāo)記值,為了下面繪制最后一行的代碼使用,=1和>1要分別處理 //紀(jì)錄上一次的行號(hào) lastRowIdx = range->rowIdx; } } //將最后一行拆分出來單獨(dú)處理,這樣就方便處理一些特殊情況 //繪制最后一行 if(sameRowCellCount > 0) { CGRect rc; rc.origin = ranges[count - sameRowCellCount].rect.origin; rc.size.height = ranges[count - sameRowCellCount].rect.size.height; rc.size.width = width* (sameRowCellCount) - 10.0F; //最后一行有多個(gè)cell被選中 if(sameRowCellCount != 1) { [self drawRoundRect:rc radius:rc.size.height]; } else//最后一行僅周日被選中,只有一個(gè),圓圈 [self drawCircleInRect:rc color:[UIColor colorWithRed:52/255.0 green:175/255.0 blue:248/255.0 alpha:1.0] isFill:YES]; } }
//由于UITableView采用了cell重用機(jī)制,因此僅有很屏幕rect相交的cell存在 //所以是cells一直輪替交換,所以我們必須在每次自繪時(shí)候判斷當(dāng)前的cell中的月歷的每個(gè)日期是否處于選中狀態(tài) //而本函數(shù)就是起到這樣的作用,判斷月歷中某個(gè)日期是否處于選中的區(qū)間范圍 -(BOOL)isInSelectedDateRange : (SDate) date { time_t curr = date_get_time_t(&date); if(curr < _startTime || curr > _endTime) return NO; return YES; }
控件的狀態(tài)初始化 控件的繪制 控件的事件觸發(fā)和處理 控件的布局
-(void) handleTouchEvent:(id) sender forEvent:(UIEvent *)event { NSSet *touches = [event allTouches]; UITouch *touch = [touches anyObject]; //獲取UITouch,將其轉(zhuǎn)換到當(dāng)前CalendarView的局部坐標(biāo)系表示 CGPoint upLoc = [touch locationInView:self]; //通過局部坐標(biāo)系的點(diǎn)獲取點(diǎn)擊處的cell的索引號(hào),優(yōu)化部分請(qǐng)看c的相關(guān)實(shí)現(xiàn) //這個(gè)碰撞檢測(cè)原理實(shí)際在游戲中經(jīng)常使用,分區(qū)縮小范圍,然后檢測(cè)該范圍內(nèi)所有物體的與點(diǎn)(2D) //或光線(3D)是否發(fā)生碰撞,用于此處也非常適合 int hitIdx = calendar_get_hitted_day_cell_index(&_calendar, upLoc); //選中了,則 if(hitIdx != -1) { SDate date; date_set(&date, _calendar.date.year, _calendar.date.month, hitIdx - _calendar.dayBeginIdx + 1); //=0為第一次點(diǎn)擊,僅選中一個(gè)cell //mod為了周而復(fù)始,并在[0,1]之間 if([self.calendarDelegate getHitCounter] % 2 == 0) { //第一次點(diǎn)擊,讓開始和結(jié)束Date相同 [self.calendarDelegate setSelectedDateRangeStart:date end:date]; } else//=1為第二次點(diǎn)擊,形成選區(qū) { [self.calendarDelegate setEndSelectedDate:date]; } //每次點(diǎn)擊,delegate中的點(diǎn)擊計(jì)數(shù)器都要遞增的 [self.calendarDelegate updateHitCounter]; //需要觸發(fā)重繪,讓ios進(jìn)行重新繪制,這個(gè)很關(guān)鍵,有一些細(xì)節(jié),在下面會(huì)說明的 [self.calendarDelegate repaintCalendarViews]; } }
//屬于CalendarDelegate的接口函數(shù),實(shí)現(xiàn)代碼如下: -(void) repaintCalendarViews { //[self.tableView setNeedsDisplay]; for(UIView * subview in self.tableView.subviews) { for(UIView* view2 in subview.subviews) { UITableViewCell* cell = (UITableViewCell*)view2; CalendarView* cview =(CalendarView*) [cell.contentView.subviews objectAtIndex:0]; [cview setNeedsDisplay]; } } }
1) 由于calendarView的選擇可能跨越多個(gè)CalendarView,因此不能僅僅在CalendarView級(jí)別setNeedsDisplay,而是需要讓整個(gè)UITableView以及他的所有子孫控件都要重繪。 2) 按照正常思路,你在UITableView上調(diào)用setNeedsDisplay,你會(huì)發(fā)現(xiàn)無效。 3) 由此可見,IOS中的臟區(qū)局部刷新機(jī)制采用的是以控件為基礎(chǔ)的后備緩沖圖,而不是以整個(gè)屏幕為基礎(chǔ)的后背緩沖圖。 4) 以控件為基礎(chǔ)的后備緩沖圖內(nèi)存消耗高,但是能夠解決重復(fù)繪制,提高效率,典型的以空間換時(shí)間策略。 介紹一個(gè)微軟開源項(xiàng)目WinObjc,非常強(qiáng)大,可以在gitHub中去查找。 為win10和Winphone實(shí)現(xiàn)了整個(gè)ios sdk,目的是讓ios的app直接在winphone上跑。 我研究過他整個(gè)局部刷新的機(jī)制,還是蠻帥的。 foundation,uikit, glkit,spritekit,gamekit,homekit....各種kit都實(shí)現(xiàn)了。而且最重要的是有源碼。 5) 以整個(gè)屏幕(或者說整個(gè)APP顯示根節(jié)點(diǎn)的size)為大小的后備緩沖區(qū),其只需要增加一張內(nèi)存位圖。 獲取臟區(qū)后,僅僅遞歸該臟區(qū)以及所有和父節(jié)點(diǎn)臟區(qū)相交部分的區(qū)域 進(jìn)行更新,因此更新區(qū)域會(huì)逐漸減小,但是不能完全去除重復(fù)繪制。 我曾經(jīng)實(shí)現(xiàn)了opengl和dx版本的2D局部刷新機(jī)制,并入到一個(gè)2d UI引擎中,利用后背緩沖區(qū)以及 基于修改投影矩陣方式,在光柵化之前裁剪掉所有不可見的頂點(diǎn)后, 其渲染速度飛速提高,并且CPU使用率控制在5%以下,大部分時(shí)間都是 在1%)。源碼不能公布,因?yàn)槭巧虡I(yè)代碼,但是demo以后可以在github上下載,很帥的IPhone4仿真模擬。
#define MYSWAP(x,y,type) \ { \ type t = x; \ x = y; \ y = t; \ } -(void)setSelectedDateRangeStart:(SDate)start end:(SDate)end { //將date轉(zhuǎn)換為time_t _startTime = date_get_time_t(&start); _endTime = date_get_time_t(&end); //如果起始時(shí)間大于結(jié)束時(shí)間,說明先點(diǎn)擊后一天,再點(diǎn)擊前一天,繪制時(shí)的邏輯不正確,需要交換一下時(shí)間 if(_startTime > _endTime) { MYSWAP(_startTime,_endTime,time_t); //紀(jì)錄下年月表示起始結(jié)束date _begDate = end; _endDate = start; }else{ _begDate = start;//記錄日期 _endDate = end; } } -(void)setEndSelectedDate:(SDate)end { //同上,只是針對(duì)第二次點(diǎn)擊而已 _endTime = date_get_time_t(&end); if(_startTime > _endTime) { MYSWAP(_startTime,_endTime,time_t); _endDate = _begDate; _begDate = end; }else{ _endDate = end; } } -(void) updateHitCounter { _hitCounter++; } -(int) getHitCounter { return _hitCounter; }
至此,IOS版本的源碼全部分析完畢,希望對(duì)大家有幫助。
關(guān)于控件的布局,本DEMO中沒什么用到,控件的布局可以說是比較復(fù)雜的部分,有各種算法,各種方法,是個(gè)比較大的主題,以后有機(jī)會(huì)探討。
總體來說,apple公司的objc編譯器前端程序Clang支持Objc,c,c++的詞法分析,AST的產(chǎn)生,然后進(jìn)入llvm,生成對(duì)應(yīng)CPU的指令。再在IOS上運(yùn)行,由于都是二進(jìn)制,所以效率非常高(蘋果公司不允許使用虛擬機(jī)代碼方式,只能以靜態(tài)鏈接庫(kù)【二進(jìn)制】方式 運(yùn)行app,高效, 難以反編譯,因此相對(duì)非常安全,唯一的例外是運(yùn)行于瀏覽器中的js代碼)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。