91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

(ios實現)用c/c++混合編程方式為ios/android實現一個自繪日期選擇控件(二)

發布時間:2020-09-05 16:00:32 來源:網絡 閱讀:1134 作者:jackyBLF 欄目:移動開發

二、IOS實現版本:

1、程序結構:
千言萬語,不如一張圖來的清晰

(ios實現)用c/c++混合編程方式為ios/android實現一個自繪日期選擇控件(二)

1) 由于CalendarController包含了一個UITableView指針,因此CalendarController需要實現UITableDataSource以及UITableViewDelegate與UITableView進行交互。
2) UITableView包含多個CalendarView,這樣就能利用UITableView的手勢滑動功能以及Cell重用功能。
3) CalendarView繼承自UIControl,因為UIControl將相對底層的觸摸事件轉換為容易操作的控件事件。主要為了使用UIControlEventTouchUpInside這個事件。 
4) CalendarDelegate仿照ios mvc模式,用于類之間的解耦(面向接口編程)以及類之間的通信。

2、 CalendarDelegate 協議:

@protocol CalendarDelegate <NSObject>

//年月和UITableView以及其中CalendarView之間關系映射
//具體見下面代碼分析
-(int)    calcCalendarCount;
-(SDate)  mapIndexToYearMonth : (int) index;
-(int)    mapYearMonthToIndex : (SDate) date;

//用于顯示到指定的年月范圍
-(void)   showCalendarAtYearMonth : (SDate) date;

//用于時間期限管理以及選中判斷
-(BOOL)   isInSelectedDateRange : (SDate) date;
-(void)   setSelectedDateRangeStart : (SDate) start end : (SDate) end;
-(void)   setEndSelectedDate : (SDate) end;

//迫使整個UITableView重繪
-(void)   repaintCalendarViews;

//計數器,用于判斷touch次數
-(void)   updateHitCounter;
-(int)    getHitCounter;

@end

3、 CalendarController:

(ios實現)用c/c++混合編程方式為ios/android實現一個自繪日期選擇控件(二)

CalendarController的聲明
//.h文件接口聲明
#import <UIKit/UIKit.h>

@interface ViewController :UIViewController
@end
//.m文件
#import "ViewController.h"
#import "CalendarDelegate.h"
#import "CalendarView.h"

//實現了如下三個delegate
@interface ViewController () <UITableViewDataSource,UITableViewDelegate,CalendarDelegate>
{
    //用于計算出多少個月歷,具體見下面代碼
    int      _startYear;
    int      _endYear;

    //每個月歷控件的高度,上面的個數和此地的高度,就可以計算整個UITableView的高度以及進行定位操作
    float    _calendarHeight;

    //用于選中操作時候,時間范圍的比較(time_t實際是個64位的整型值,適合做比較操作,具體看實現代碼)
    time_t   _startTime;
    time_t   _endTime;
}

//選中值的年月表示方式,方便顯示而已,實際操作都轉換成time_t類型
@property  (nonatomic,assign)  SDate  begDate;
@property  (nonatomic,assign)  SDate  endDate;

//點擊計數器,用于確定當前點擊的奇偶性,因此改月歷控件涉及兩次操作,用于區域選者
@property (nonatomic)  int  hitCounter;

//作為Calendar的父容器,用于處理滑動以及cell重用
@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end
CalendarDelegate的協議函數calcCalendarCount的實現和調用
-(int) calcCalendarCount
{
    SDate date;
    date_get_now(&date);

    //計算出當前的年月到n年前的1月份的月數
    //加設當前為2016年8月,n為5,則月份范圍為[2011年1月---2016年8月 總計月數為68],具體算法如下:
    int diff = _endYear - _startYear + 1;
    diff = diff * 12;
    diff -= 12 - date.month;
    return diff;
}

//UITableView的DatatSource有個必須實現的協議函數,用于返回當前UITableView可以容納的總數:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    int ret = [self calcCalendarCount];
    return ret;
}
UITableViewDelegate需要實現的一個協議函數:
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
   //返回的是當前的calendarView的高度
   //UITableView需要知道月歷(月份)的個數以及月歷控件的高度,就可以計算出整個UITableView的Content的height了
    return _calendarHeight;
}
CalendarDelegate協議中索引、月份映射關系以及UITableView中CalendarView的定位問題:

千言萬語,不如再來一張圖來的清晰

(ios實現)用c/c++混合編程方式為ios/android實現一個自繪日期選擇控件(二)

-(SDate) mapIndexToYearMonth : (int) index
{
    SDate ret;
    //調用c函數,將索引號映射成年月,用于UITableView創建calendarView時現實月歷標題
    date_map_index_to_year_month(&ret, _startYear, index);
    return ret;
}

//調用mapIndexToYearMonth:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString* calendarID = @"calendarID";

    float width = self.tableView.frame.size.width;

    //從行索引號映射到年月
    SDate date = [self mapIndexToYearMonth:(int)indexPath.row];

    //獲取重用的cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:calendarID ];

    //如果為null,說明不存在,創建該cell
    if(cell == nil)
    {
        //可以在此斷點,查看一下具體生成了多少個calendarView(我這里生成了3個)
        //說明UITableView可見rect有三個calendarView相交
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:calendarID];
        [cell setTag:10];

        //手動創建CalendarView
        CalendarView* calendarView = [[CalendarView alloc] initWithFrame:CGRectMake(0, 0, width, _calendarHeight)];

        //設置CalednarDelegate
        calendarView.calendarDelegate = self;

        //給定一個tag號,用于重用時獲得該view
        [calendarView setTag:1000];

        [cell.contentView addSubview:calendarView];
    }

    //通過tag號,獲取view
    CalendarView* view =(CalendarView*) [cell.contentView viewWithTag:1000];

    //設置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;
}

//調用mapYearMonthToIndex
-(void) showCalendarAtYearMonth:(SDate)date
{
    if(date.year < _startYear || date.year > _endYear)
        return;

    //將年月表示映射成UITableView中的索引號,根據索引計算出要滾動到的目的地
    int idx = [self mapYearMonthToIndex:date];

    //如上圖所示:當idx = calendarViews.length-1時,可能存在超過整個UITableView ContentSize.height情況,此時,UITableView會自動調整contentOffset的值,使其符合定位到最底端,android listview也是如此。
    self.tableView.contentOffset = CGPointMake(0.0F, idx * _calendarHeight );
}
 1) 從上圖以及代碼,應該很清楚的了解了映射和定位問題的過程
 2) 從上圖中,我們也可以了解到UITableView的滾動原理,UITableView的Frame是Clip區域,滾動的內容存放于Content中。
 3) UITableView可以說是移動開發中最常用,最重要的一個控件(還有一個是UICollectionView)。有兩個主要功能點:滾動(UIScrollView父類)和cell復用。以后有機會我們來從頭到尾實現一個帶有上述功能的控件。
有了上面的代碼,我們就可以初始化CalendarController:
- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

    //獲取當前的年月
    SDate date;
    date_get_now(&date);

    //default有3年
    _startYear   = date.year-3;
    _endYear     = date.year;

    /*
    //當年也支持
    _startYear   = date.year;
    _endYear     = date.year;
    */

    //touch 計數器
    _hitCounter  = 0;

    float scale = 0.6F;//硬編碼,最好由外部設置
    //float scale = 0.5F;//硬編碼,最好由外部設置
    _calendarHeight  = self.tableView.frame.size.height * scale;


    self.tableView.dataSource = self;
    self.tableView.delegate = self;

    //default定位顯示當前月份
    if (self.begDate.year == 0) {
        self.begDate = date;
        [self showCalendarAtYearMonth:date];
    }else{
         //當然你也可以設置具體月份重點顯示
        [self showCalendarAtYearMonth:self.begDate];
    }
}

到目前為止,支持CalendarController運行的所有方法都分析完畢,接下來我們要看一下CalendarView相關的實現。(CalendarDelegate還有一些方法沒分析,因為這些方法是由CalendarView調用的,由此可見,IOS中的Delegate除了面向接口編程外,還有一個功能就是類之間的通信)

4、 CalendarView:

CalendarView的聲明:
//.h文件
@interface CalendarView : UIControl
-(void) setYearMonth : (int) year month : (int) month;
@property (weak, nonatomic) id  calendarDelegate;
@end
//.m文件
@interface CalendarView()
{
    /*
     blf: 
         引用c結構,所有月歷相關操作委托給SCalendar的相關函數
         SCalendar 使用棧內存分配
    */
    SCalendar         _calendar;

    //這是一個很重要的變量,具體源碼中說明
    int               _lastMonthDayCount;

    //存放月歷的日期和星期字符串
    NSMutableArray*   _dayAndWeekStringArray;

    //string繪制時的大小
    CGSize            _dayStringDrawingSize;
    CGSize            _weekStringDrawingSize;
}
CalenderView初始化:
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code

        //年月區塊和星期區塊的大小按當前view高度的比例來設定
        float yearMonthHeight = frame.size.height * 0.095F;
        float weekHeight = frame.size.height * 0.089F;

        //初始化月歷控件,計算出各個區塊部分的大小
        calendar_init(&_calendar, frame.size, yearMonthHeight, weekHeight);


        SDate date = _calendar.date;

        //此時date是上個月
        date_get_prev_month(&date, 1);


        self.backgroundColor = [UIColor clearColor];

        //設置日期區塊的大小
        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;

        //預先分配38個字符串容量的數組
        _dayAndWeekStringArray = [NSMutableArray arrayWithCapacity:38];

        //0--30表示最多31天日期字符串
        for(int i = 0; i < 31; i++)
            [_dayAndWeekStringArray addObject: [NSString stringWithFormat:@"%02d",i+1]];

        //31--37存儲星期字符串
        [_dayAndWeekStringArray addObject:@"周日"];
        [_dayAndWeekStringArray addObject:@"周一"];
        [_dayAndWeekStringArray addObject:@"周二"];
        [_dayAndWeekStringArray addObject:@"周三"];
        [_dayAndWeekStringArray addObject:@"周四"];
        [_dayAndWeekStringArray addObject:@"周五"];
        [_dayAndWeekStringArray addObject:@"周六"];

        //計算出日期字符串的繪制用尺寸
        _dayStringDrawingSize   = [self getStringDrawingSize: [_dayAndWeekStringArray objectAtIndex:0]];
        //計算出星期字符串的繪制用尺寸
        _weekStringDrawingSize  = [self getStringDrawingSize: [_dayAndWeekStringArray objectAtIndex:31]];

        //UIControl基于控件的事件處理系統,掛接UIControlEventTouchUpInside處理程序
        [self addTarget:self action:@selector(handleTouchEvent:forEvent:) forControlEvents:UIControlEventTouchUpInside];
    }
    return self;
}

//計算要繪制字符串的尺寸的函數如下:
-(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;
}

從上面類聲明和初始化代碼,引出幾個問題:

1)為什么繼承自UIControl?
2)為什么delegate使用weak?
3)為什么delegate 聲明為id?
4)為什么棧分配?
5)為什么同一個CalendarView的類聲明需要分別在.h和.m文件中,或者換種說法:這樣做有什么好處?
6)為什么初始化只實現了initWithFrame,沒有實現initWithCoder,在哪種情況下,還需要override initWithCoder函數?
有興趣的,可以留言回答!呵呵呵!!!
CalendarView字符串居中對齊繪制函數:
-(void) drawStringInRectWithSize : (NSString*) string rect:(CGRect)rect size:(CGSize) size color : (UIColor*) color
{
    CGPoint pos;
    //下面算法是讓文字位于要繪制的Rect的水平和垂直中心
    //也就是劇中對齊
    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];
}
CalendarView shape繪制函數:
    1) opengles API 利用gpu加速,速度最快,難度相對最大,自由度也最高,需要創建專用的GL上下文環境。基于狀態機模式,需要設置各種繪制狀態以及恢復狀態。最重要的是跨平臺,android以及windows,Linux都可以用(cocos2d-x基于opengles)。
    2) quartz API 使用cpu光柵化,不需要GL上下文環境,直接可在控件表面進行繪制,相對底層,基于狀態機模式,需要設置各種繪制狀態以及恢復狀態
    3) UIKit中對quartz API的二次封裝,例如UIBezierPath類,封裝了大部分的shape,方便易用,我們就用這個類來進行繪制。上面兩種API,以后有機會我們可以專門來分析一下。

圓的貝塞爾路徑對象(由圓心和半徑定義):

-(void) drawCircleInRect : (CGRect) rect color : (UIColor*) color isFill : (BOOL) isFill
{
    //取width和height最小的值作為要繪制的圓的直徑,這樣就不會將圓繪制范圍超出rect
    float radiu = rect.size.width < rect.size.height ? rect.size.width : rect.size.height;

    //將圓的中心點從rect的左上角平移到rect的中心點
    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;

    //創建一個圓的bezier路徑對象
    UIBezierPath* circle = [UIBezierPath bezierPathWithArcCenter:center radius:radiu startAngle:0.0F endAngle:2.0F*3.1415926F clockwise:true];

    //填充繪制(日期選中狀態)
    if(isFill == YES)
    {
        [color setFill];
        [circle fill];
    }
    else
    {
        //沒選中狀態,用stroke方式繪制
        [color setStroke];
        [circle stroke];
    }
}

圓角矩形的貝塞爾對象(由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];
}

(ios實現)用c/c++混合編程方式為ios/android實現一個自繪日期選擇控件(二)

override drawRect函數,接管所有繪圖:

代碼很長,我們拆分成幾個區塊來分析

//1、見上圖,繪制年月信息

    //blf:獲取原生繪圖context指針,所有原生繪圖api都是c語言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++)
    {
        //獲取星期區塊中某個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
        {
            //其他時間藍色
            [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex:31 + i] rect:rc size:_weekStringDrawingSize  color: [UIColor blueColor]];
        }
    }
//3、見上圖紅色邊框部分,繪制上個月日期信息
    CGPoint dayRectOffset;
    //獲取日期區塊的rect
    calendar_get_day_section_rect(&_calendar, &rc);
    //紀錄日期區塊的起始位置
    dayRectOffset = rc.origin;

    //當前月份1號在日期cells中的起始索引號
    int begin = _calendar.dayBeginIdx;
    //當前月份結束索引號
    int end   = begin + _calendar.dayCount;

    //繪制上個月的日期,假設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);

        //計算出位置偏移量
        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];

        //計算方式涉及到了_lastMonthDayCount
        //假設上個月有30天,本月的begin為5,則
        //則30-(5-4)= 29 ---->0base--->30號
        //  30- (5-3)= 28 ---->0base--->29號
        //  30- (5-2)= 27 ---->0base--->28號
        //  30- (5-1)= 26 ---->0base--->27號
        //  30- (5-0)= 25 ---->0base--->26號
        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、見上圖紅色邊框部分,繪制下個月日期信息
    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]];

    }

(ios實現)用c/c++混合編程方式為ios/android實現一個自繪日期選擇控件(二)

當前月份的繪制分為選中狀態的日期繪制和非選中狀態日期的繪制,上圖是選中狀態繪制的說明圖
typedef struct _selectRange
{
    int      rowIdx; //為了方便處理是否同一行
    int      columIdx;//行列轉換一緯數組索引
    CGRect   rect; //紀錄要繪制的rect
} selectRange;
//5、繪制當前月份的日期,包括選中,未選中以及日期文字

    //使用c結構,并初始化相關變量
    selectRange  ranges[31];
    memset(ranges,0,sizeof(ranges));
    int          rangeCount = 0;

    //繪制當前月的日期
    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 );

        //如果當前日期在選中時間范圍內,則batch起來,由drawSelectRange進行繪制
        //因為需要處理換行這種效果(drawSelectRange中處理,因此緩存起來二次處理比較方便)
        //與delegate通信
        if([self.calendarDelegate isInSelectedDateRange:date])
        {

            ranges[rangeCount].rowIdx = i / 7; //映射成行索引
            ranges[rangeCount].columIdx = i % 7; //映射成列索引
            ranges[rangeCount].rect = rc; //當前行列的rect紀錄下來
            rangeCount++; //計數器增加1
        }
        else
        {

            //沒有選中的,就直接繪制圓圈和當中的日期號
            [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紀錄了選中的數量,ranges則紀錄了要繪制的所有信息
    [self drawSelectRange:ranges count:rangeCount];

    //選中的圈圈的文字由下面代碼繪制
    for(int i = 0; i < rangeCount; i++)
    {
        //重新將行列(二維)索引號映射一緯數組索引號
        int idx = ranges[i].rowIdx * 7 + ranges[i].columIdx;
        //idx - begin就是當前的要繪制的日期文字的索引號
        [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex: idx - begin] rect:ranges[i].rect size:_dayStringDrawingSize  color:[UIColor whiteColor]];
    }
關鍵的drawSelectRange函數:
//blf:注意 參數ranges是數組名,數組名表示數組的首地址
//         還有就是selectRange是c結構,當做指針操作時要用->而不是.尋址操作符
-(void) drawSelectRange : (selectRange* ) ranges count : (int) count
{
    //兩種情況下count = 1
    //第一選則,或者第二次選中的和第一次選中的是同一個日期cell
    //此時是繪制圓形而不是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];

        //退出函數
        return;
    }

    //并不是第一次選者且第二次選者不是和第一次選者一致時

    //獲取cell rect的width
    CGRect rect;
    calendar_get_day_cell_rect_by_index(&_calendar, &rect, 0);
    float width = rect.size.width;

    //用于紀錄上一次的行號,初始化,紀錄的是第一行的索引號
    int lastRowIdx = ranges[0].rowIdx;
    //計數器,用來紀錄當前行的cell的數量
    int sameRowCellCount = 0;

    for(int i = 0; i < count; i++)
    {
        //從ranges數組中獲取一個結構時候,使用了&取地址操作符
        //因為防止發生拷貝,如果不是取地址的話,賦值會發生memcopy行為
        selectRange* range = &ranges[i];
        //行號相同,則同一行啦
        if(range->rowIdx == lastRowIdx)
        {
            sameRowCellCount++;
        }
        else
        {
            //行號不同,說明換行了,因此要繪制當前行
            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;//標記值,為了下面繪制最后一行的代碼使用,=1和>1要分別處理

            //紀錄上一次的行號
            lastRowIdx = range->rowIdx;
        }
    }

    //將最后一行拆分出來單獨處理,這樣就方便處理一些特殊情況

    //繪制最后一行
    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;

        //最后一行有多個cell被選中
        if(sameRowCellCount != 1)
        {
            [self drawRoundRect:rc radius:rc.size.height];
        }
        else//最后一行僅周日被選中,只有一個,圓圈
            [self drawCircleInRect:rc color:[UIColor colorWithRed:52/255.0 green:175/255.0 blue:248/255.0 alpha:1.0] isFill:YES];

    }
}
至此,CalendarView的繪圖部分代碼全部完畢,我們來看看與delegate通信的選中判斷函數:
//由于UITableView采用了cell重用機制,因此僅有很屏幕rect相交的cell存在
//所以是cells一直輪替交換,所以我們必須在每次自繪時候判斷當前的cell中的月歷的每個日期是否處于選中狀態
//而本函數就是起到這樣的作用,判斷月歷中某個日期是否處于選中的區間范圍
-(BOOL)isInSelectedDateRange : (SDate) date
{
    time_t curr = date_get_time_t(&date);

    if(curr < _startTime || curr > _endTime)
        return NO;

    return YES;
}
控件開發,不管是IOS,android還是windows,萬流歸宗,歸根到底就是做4件事情:
   控件的狀態初始化  
   控件的繪制  
   控件的事件觸發和處理  
   控件的布局
接下來我們看看如何處理CalendarView的觸摸事件:
-(void) handleTouchEvent:(id) sender forEvent:(UIEvent *)event
{
    NSSet *touches = [event allTouches];
    UITouch *touch = [touches anyObject];

    //獲取UITouch,將其轉換到當前CalendarView的局部坐標系表示
    CGPoint upLoc = [touch locationInView:self];

    //通過局部坐標系的點獲取點擊處的cell的索引號,優化部分請看c的相關實現
    //這個碰撞檢測原理實際在游戲中經常使用,分區縮小范圍,然后檢測該范圍內所有物體的與點(2D)
    //或光線(3D)是否發生碰撞,用于此處也非常適合
    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為第一次點擊,僅選中一個cell
        //mod為了周而復始,并在[0,1]之間
        if([self.calendarDelegate getHitCounter] % 2 == 0)
        {
            //第一次點擊,讓開始和結束Date相同
            [self.calendarDelegate setSelectedDateRangeStart:date end:date];
        }
        else//=1為第二次點擊,形成選區
        {
            [self.calendarDelegate setEndSelectedDate:date];
        }

        //每次點擊,delegate中的點擊計數器都要遞增的
        [self.calendarDelegate updateHitCounter];

        //需要觸發重繪,讓ios進行重新繪制,這個很關鍵,有一些細節,在下面會說明的
        [self.calendarDelegate repaintCalendarViews];
    }

}
先來看一下重繪代碼,然后推導一些細節:
//屬于CalendarDelegate的接口函數,實現代碼如下:
-(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];
        }
    }
}
由上面的的代碼,可以了解到如何從UITable尋址到各個CalenderView:  UITableView->UITableViewCell->ContentView->CalendarView->setNeedsDisplay
1) 由于calendarView的選擇可能跨越多個CalendarView,因此不能僅僅在CalendarView級別setNeedsDisplay,而是需要讓整個UITableView以及他的所有子孫控件都要重繪。
2) 按照正常思路,你在UITableView上調用setNeedsDisplay,你會發現無效。
3) 由此可見,IOS中的臟區局部刷新機制采用的是以控件為基礎的后備緩沖圖,而不是以整個屏幕為基礎的后背緩沖圖。
4) 以控件為基礎的后備緩沖圖內存消耗高,但是能夠解決重復繪制,提高效率,典型的以空間換時間策略。
    介紹一個微軟開源項目WinObjc,非常強大,可以在gitHub中去查找。
    為win10和Winphone實現了整個ios sdk,目的是讓ios的app直接在winphone上跑。
    我研究過他整個局部刷新的機制,還是蠻帥的。
    foundation,uikit, glkit,spritekit,gamekit,homekit....各種kit都實現了。而且最重要的是有源碼。
5) 以整個屏幕(或者說整個APP顯示根節點的size)為大小的后備緩沖區,其只需要增加一張內存位圖。
   獲取臟區后,僅僅遞歸該臟區以及所有和父節點臟區相交部分的區域 進行更新,因此更新區域會逐漸減小,但是不能完全去除重復繪制。
   我曾經實現了opengl和dx版本的2D局部刷新機制,并入到一個2d UI引擎中,利用后背緩沖區以及
   基于修改投影矩陣方式,在光柵化之前裁剪掉所有不可見的頂點后,
   其渲染速度飛速提高,并且CPU使用率控制在5%以下,大部分時間都是
   在1%)。源碼不能公布,因為是商業代碼,但是demo以后可以在github上下載,很帥的IPhone4仿真模擬。
還有幾個delegate中用到的協議方法:
#define MYSWAP(x,y,type)    \
{                           \
    type t = x;             \
    x = y;                  \
    y = t;                  \
}

-(void)setSelectedDateRangeStart:(SDate)start end:(SDate)end
{

    //將date轉換為time_t
    _startTime = date_get_time_t(&start);
    _endTime   = date_get_time_t(&end);

    //如果起始時間大于結束時間,說明先點擊后一天,再點擊前一天,繪制時的邏輯不正確,需要交換一下時間
    if(_startTime > _endTime)
    {
        MYSWAP(_startTime,_endTime,time_t);

        //紀錄下年月表示起始結束date
        _begDate = end;
        _endDate = start;

    }else{

        _begDate = start;//記錄日期
        _endDate = end;
    }
}

-(void)setEndSelectedDate:(SDate)end
{
    //同上,只是針對第二次點擊而已
    _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版本的源碼全部分析完畢,希望對大家有幫助。

關于控件的布局,本DEMO中沒什么用到,控件的布局可以說是比較復雜的部分,有各種算法,各種方法,是個比較大的主題,以后有機會探討。

總體來說,apple公司的objc編譯器前端程序Clang支持Objc,c,c++的詞法分析,AST的產生,然后進入llvm,生成對應CPU的指令。再在IOS上運行,由于都是二進制,所以效率非常高(蘋果公司不允許使用虛擬機代碼方式,只能以靜態鏈接庫【二進制】方式 運行app,高效,  難以反編譯,因此相對非常安全,唯一的例外是運行于瀏覽器中的js代碼)。

IOS部分完畢,下一篇與android有關。由于Ios objc對c和c++支持非常好,所以沒什么難度,但是android就很復雜了,所以我個人認為更有價值。下篇中,我們不再以代碼為主,而是了解android中如何方便,高效的進行JNI交互。對了,實際上上面的源碼還可以更多的優化,大家可以建議,探討。
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

株洲市| 枣强县| 新竹市| 高雄市| 宜兴市| 平乐县| 陆丰市| 温宿县| 榆中县| 微博| 应用必备| 白沙| 石河子市| 山东| 嵩明县| 井研县| 资溪县| 正镶白旗| 商洛市| 霍山县| 梓潼县| 容城县| 中牟县| 靖宇县| 东方市| 中卫市| 原阳县| 仁怀市| 保山市| 正宁县| 当雄县| 海原县| 禄劝| 潼关县| 土默特左旗| 合肥市| 吕梁市| 五莲县| 浦东新区| 左云县| 铜川市|