Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Objective-C

How to implement a custom-draw CalendarView mixed with C/C++ for Android&IOS--part 1

3.80/5 (3 votes)
13 Nov 2016CPOL2 min read 8.6K  
custom-draw CalendarView for Android&IOS

Introduction

This series of articles will show you how to implement a custom-draw calendarView for IOS & Android.

We will write C/C++ code for manipulation of Date and Calendar .

For the IOS sytem, Objc will invoke C/C++ function.

For the Android sytem, We will use lib of SWIG(Simplified Wrapper and Interface Generator) which will generate JNI code for accessing C/C++ code from Java.

Contents

  1. Requirements description and implement of Date&Calendar operation in C/C++
  2. How to invoke C/C++ code from Objc and make a custom-draw CalendarView in IOS
  3. How to config android NDK and compile C/C++ code with gradle system
  4. how  to generate JNI code for accessing C/C++ code from Java
  5. update to android studion 2.2 and use cmake compile C/C++ code

Requirements description

  • First click, select one cell in selectable date area
  • Second click,select all cells between the last selected and this one
  • Repeating these  steps
  • It will scroll calendar in UITableView/ListView accroding to swipe gesture

Why use C/C++ in mobile development?

  1. I am familiar with C/C++
  2. This CalendarView is writed in 2 years aga. It is my first time for mobile development and choose cocos2d-x for cross platform development. I found that cocos2d-x was  suit for game rather than app. cocos2d-x is lack of  a mechanism of "dirty area" which lead to redraw all scene every 1/60 seconds and do a bad job in power saving. finally, we decide to select native language for logical development and use C/C++ for core operation.
  3. Android's apk is very easy to decompile . It is very difficult to decompile binary code compiled by C/C++.
  4. In cocos2d-x , It use SWIG(Simplified Wrapper and Interface Generator) to generate PInvoke code for accessing C/C++ code from C# in CocosStudio. I think this is a great technique and want to master it.we can use it in android app.

Why use custom-draw in this CalendarView(control)?

No matter IOS or Android,I think that There are 3 ways to extend your own view(control):

  1. You can use existed views in a container by InterfaceBuilder or androidStudio. It is not need to inherit from base view class.All you need to do is write event handler.
  2. You can inherit from a view or container view and use existed views.You can override some virtual method to extend your function.
  3. You can inherit from a base view and draw all the things you needs(custom-darw).

We chose the way of custom-draw  for this CalendarView.

It is memory savings and fast in run.

 

C/C++ Base Struct:

<code>/*blf:
      Android ndk is lack of some nesscessary struct,we use ios struct for this view
*/

#define ANDROID_NDK_IMP //in IOS,this code is commited out

#ifdef ANDROID_NDK_IMP 
    typedef struct _CGPoint {    float x;    float y;}CGPoint;
    typedef struct _CGSize  {    float width;    float height;}CGSize;
    typedef struct _CGRect  {    CGPoint origin;    CGSize size;}CGRect;
#endif</code>
<code>/*blf: 
      C++ implement
*/
#ifdef ANDROID_NDK_IMP  
    static float GetRectMaxX(CGRect rc) { return rc.origin.x + rc.size.width;  }  
    static float GetRectMaxY(CGRect rc) { return rc.origin.y + rc.size.height; }  
    static bool CGRectContainsPoint(CGRect rc, CGPoint pt){return(pt.x >= rc.origin.x) && (pt.x <= GetRectMaxX(rc)) && (pt.y >= rc.origin.y) && (pt.y <= GetRectMaxY(rc));}
#endif</code>

C/C++ Date Function:

<code>/*
blf: 

    All the function params passed by pointer.

    You should not use malloc or new operator in function body for memory allocation
    unless you use std::shared_ptr.

    
*/
void date_set(SDate* ret,int year,int month,int day)
{
   assert(ret);
   ret->year = year;
   ret->month = month;
   ret->day = day;
}

/*
blf: Get Current Date (year/month/day)
*/
void date_get_now(SDate* ret)
{
   assert(ret);

   time_t t;
   time(&t);

   //convert time_t to tm as local time
   struct tm* timeInfo;
   timeInfo = localtime(&t);

   //tm->tm_year since 1900,so we need add 1900
   ret->year  =  timeInfo->tm_year + 1900;

   //timeInfo->tm_mon is zero base(0-11),we use 1 base (1-12),add 1
   ret->month =  timeInfo->tm_mon + 1;

   ret->day   =  timeInfo->tm_mday;
}

/*
blf: is equal for two SDate
*/
bool date_is_equal(const SDate* left,const SDate* right)
{
   assert(left&&right);
   return (left->year == right->year &&
           left->month == right->month &&
           left->day == right->day);
}

/*
blf: calc month count between start and end year
*/
int date_get_month_count_from_year_range(int startYear,int endYear)
{
   int diff = endYear - startYear + 1;
   return diff * 12;
}

/*
blf: 
     map a index to year and month 
*/
void date_map_index_to_year_month(SDate* to,int startYear,int idx)
{
   assert(to);

   // use / 
   to->year = startYear + idx / 12;

   // use %
   to->month = idx % 12 + 1;

   //we are not care this value
   to->day = -1;
}

/*
blf: linux mktime implement
reference url: http://blog.csdn.net/axx1611/article/details/1792827
*/
long mymktime (unsigned int year, unsigned int mon,
                      unsigned int day, unsigned int hour,
                      unsigned int min, unsigned int sec)
{
   if (0 >= (int) (mon -= 2)) {    /* 1..12 -> 11,12,1..10 */
      mon += 12;      /* Puts Feb last since it has leap day */
      year -= 1;
   }

   return (((
                    (long) (year/4 - year/100 + year/400 + 367*mon/12 + day) +
                    year*365 - 719499
            )*24 + hour /* now have hours */
           )*60 + min /* now have minutes */
          )*60 + sec; /* finally seconds */
}

/*
blf: There 3 version for mktime:

     1、 use crt mktime, I don't know where is wrong? 
         at the same time point,first invoke mktime and second invoke mktime,get diffent value
         why?
     2、 use IOS's NSCalendar, It is ok ,but not work for Android.
     3、 use mymktime implement , it is work well for IOS and Android

*/

long date_get_time_t(const SDate* d)
{
    assert(d);

    /*
     1、first version:it not work well
    struct tm date;
    //crt函数中year是基于1900年的偏移,因此要减去1900
    date.tm_year = d->year - 1900;

    //crt函数中月份是[0-11]表示的,我们使用[1-12]表示,因此要减去1
    date.tm_mon = d->month - 1;

    date.tm_mday = d->day;
    date.tm_hour = 0;
    date.tm_min = 0;
    date.tm_sec = 1;
    time_t seconds = mktime(&date);

    return (long)seconds;
    */

    /*
     2、second version : ios NSCalendar
     NSDateComponents *components = [[NSDateComponents alloc] init];

     [components setDay:d->day]; // Monday
     [components setMonth:d->month]; // May
     [components setYear:d->year];

     NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

     NSDate *date = [gregorian dateFromComponents:components];

     return (time_t) [date timeIntervalSince1970];
     */

     /*
     3、third version : it is work well
     */
     return mymktime(d->year,d->month,d->day,0,0,1);
}

/*
blf: 
     for example : current =  2015-1,delta = 2,return 2014-11
*/
void date_get_prev_month(SDate* date, int delta)
{
   assert(date);

   if((date->month - delta) < 1)
   {
      date->year--;
      date->month = 12 + date->month - delta;
   }
   else
      date->month = date->month - delta;
}

/*
blf: 
     for example : current = 2015-11,delta = 2,return 2016-1
*/
void date_get_next_month(SDate* date, int delta)
{
   assert(date);
   if((date->month + delta) > 12)
   {
      date->year++;
      date->month = date->month + delta - 12;
   }
   else
      date->month = date->month + delta;
}

/*
blf: return is it a leap year
*/
int date_get_leap(int year)
{
   if(((year % 4 == 0) && (year % 100) != 0) || (year % 400 == 0))
      return 1;
   return 0;
}

/*
blf: calc week day
*/
int date_get_days(const SDate* date)
{
   assert(date);
   int day_table[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
   int i = 0, total = 0;
   for(i = 0; i < date->month; i++)
      total += day_table[i];
   return total + date->day + date_get_leap(date->year);
}

int date_get_week(const SDate* date)
{
   assert(date);
   return ((date->year - 1 + (date->year - 1) / 4 - (date->year - 1) / 100 +
            (date->year - 1) / 400 + date_get_days(date) )% 7);
}


int date_get_month_of_day(int year, int month)
{
   switch(month)
   {
      case 1:
      case 3:
      case 5:
      case 7:
      case 8:
      case 10:
      case 12: return 31;
      case 4:
      case 6:
      case 9:
      case 11: return 30;
   }
   //blf: leap year test
   return 28 + date_get_leap(year);
}</code>

C/C++ Calendar Function:

<code>/*
 blf: calendar dayBeginIdx & dayCount

   0   1   2   3   4   5   6       week section
 ---------------------------
 |   |   |   |   |   |   | 1 |     rowIdx = 0
 ---------------------------
 ---------------------------
 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |     rowIdx = 1
 ---------------------------
 ---------------------------
 | 9 | 10| 11| 12| 13| 14| 15|     rowIdx = 2
 ---------------------------
 ---------------------------
 | 16| 17| 18| 19| 20| 21| 22|     rowIdx = 3
 ---------------------------
 ---------------------------
 | 23| 24| 24| 25| 26| 27| 28|     rowIdx = 4
 ---------------------------
 ---------------------------
 | 30| 31|   |   |   |   |   |     rowIdx = 5
 ---------------------------

 dayBeginIdx = 6
 dayCount    = 31

 */

void calendar_set_year_month(SCalendar* calendar,int year,int month)
{
   assert(calendar);
   //if(calendar->date.year != year || calendar->date.month != month)
   {
      calendar->date.year = year;
      calendar->date.month = month;
      calendar->date.day = 1;

      calendar->dayBeginIdx = date_get_week(&calendar->date);
      calendar->dayCount = date_get_month_of_day(calendar->date.year, calendar->date.month);
   }

}

void calendar_get_year_month(SCalendar* calendar,int* year,int* month)
{
   assert(calendar);
   if(year)
      *year = calendar->date.year;
   if(month)
      *month = calendar->date.month;
}


void calendar_init(SCalendar* calendar,CGSize ownerSize,float yearMonthHeight,float weekHeight)
{
   assert(calendar && calendar);

   //memset(calendar, 0, sizeof(SCalendar));

   calendar->size = ownerSize;
   calendar->yearMonthSectionHeight = yearMonthHeight;
   calendar->weekSectionHegiht = weekHeight;

   calendar->daySectionHeight = ownerSize.height - yearMonthHeight - weekHeight;
 
   assert(calendar->daySectionHeight > 0);

   //blf:初始化时显示本地当前的年月日
   //date_get_now(&calendar->date);

   calendar_set_year_month(calendar, calendar->date.year, calendar->date.month);
}

void calendar_get_year_month_section_rect(const SCalendar* calendar,CGRect* rect)
{
   assert(rect);
   memset(rect,0,sizeof(CGRect));
   rect->size.width = calendar->size.width;
   rect->size.height = calendar->yearMonthSectionHeight;
}


void calendar_get_week_section_rect(const SCalendar* calendar,CGRect* rect)
{
   assert(rect);
   memset(rect,0,sizeof(CGRect));
   rect->origin.y = calendar->yearMonthSectionHeight;
   rect->size.width = calendar->size.width;
   rect->size.height = calendar->weekSectionHegiht;
}


void calendar_get_day_section_rect(const SCalendar* calendar,CGRect* rect)
{
   assert(calendar && rect);
   memset(rect,0,sizeof(CGRect));
   rect->origin.y = calendar->yearMonthSectionHeight + calendar->weekSectionHegiht;
   rect->size.width = calendar->size.width;
   rect->size.height = calendar->daySectionHeight;
}


void calendar_get_week_cell_rect(const SCalendar* calendar,CGRect* rect,int idx)
{
   assert(calendar && rect && idx >= 0 && idx < 7);

   //get week section
   calendar_get_week_section_rect(calendar, rect);

   //calc week cell from week section
   float cellWidth = rect->size.width / 7.0F;

   //calc offset
   rect->origin.x = cellWidth * idx;

   rect->size.width = cellWidth;
}


/*
 blf:

 ---------------------------
 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |     rowIdx = 0
 ---------------------------
 ---------------------------
 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |     rowIdx = 1
 ---------------------------
 ---------------------------
 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |     rowIdx = 2
 ---------------------------
 ---------------------------
 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |     rowIdx = 3
 ---------------------------
 ---------------------------
 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |     rowIdx = 4
 ---------------------------
 ---------------------------
 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |     rowIdx = 5
 ---------------------------
 
      two dimension version : row/colum
 */

void calendar_get_day_cell_rect(const SCalendar* calendar,CGRect* rect,int rowIdx,int columIdx)
{
   assert(calendar && rect && rowIdx >= 0 && rowIdx < 6 && columIdx >= 0 && columIdx < 7 );
   float cellWidth = calendar->size.width / 7.0F;
   float cellHeight = calendar->daySectionHeight / 6.0F;
   rect->origin.x = cellWidth  * columIdx;
   rect->origin.y = cellHeight * rowIdx;
   rect->size.width  = cellWidth;
   rect->size.height = cellHeight;
}

/*
 blf:
      one dimension version
 */
void calendar_get_day_cell_rect_by_index(const SCalendar* calendar,CGRect* rect,int idx)
{
   assert(calendar && rect && idx >= 0 && idx < 42);

   int rowIdx   = (idx / 7); // use / for rowIdx
   int columIdx = (idx % 7); // use % for columIdx

   calendar_get_day_cell_rect(calendar, rect, rowIdx, columIdx);

}

/*
 blf:
 check touchPoint is inside in a cell
 if true ,return idx
 if false,return -1


   0   1   2   3   4   5   6       week section
 ---------------------------
 |   |   |   |   |   |   | 1 |     rowIdx = 0
 ---------------------------
 ---------------------------
 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |     rowIdx = 1
 ---------------------------
 ---------------------------
 | 9 | 10| 11| 12| 13| 14| 15|     rowIdx = 2
 ---------------------------
 ---------------------------
 | 16| 17| 18| 19| 20| 21| 22|     rowIdx = 3
 ---------------------------
 ---------------------------
 | 23| 24| 24| 25| 26| 27| 28|     rowIdx = 4
 ---------------------------
 ---------------------------
 | 30| 31|   |   |   |   |   |     rowIdx = 5
 ---------------------------

 */
int calendar_get_hitted_day_cell_index(const SCalendar* calendar, CGPoint localPt)
{
 
   //optimization 1 : if a localPt is not in calendar area,return -1

   CGRect daySec;
   calendar_get_day_section_rect(calendar, &daySec);

   if(!CGRectContainsPoint(daySec,localPt))
      return -1;

   localPt.y -= daySec.origin.y;


   //optimization 2: avoiding iterator of 6*7=42
   //                this is common algorithm in game development
   float cellWidth  =   daySec.size.width  / 7.0F;
   float cellHeight =   daySec.size.height / 6.0F;
   int   columIdx   =   localPt.x / cellWidth;
   int   rowIdx     =   localPt.y / cellHeight;

 
   int idx  =  rowIdx * 7 + columIdx;
   if(idx < calendar->dayBeginIdx || idx > calendar->dayBeginIdx  + calendar->dayCount - 1)
      return -1;

   return idx;
}</code>

Note:

This is my first time write articles in english.

I have do my best to make me understood. If any question,let me know.

Sorry for my poor english.

Next:

    Part 2 : How to invoke C/C++ code from Objc and make a custom-draw CalendarView in IOS

    IOS source code will be provided in part2 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)