65.9K
CodeProject 正在变化。 阅读更多。
Home

Android&IOS平台自定义绘制日历视图(C/C++混合开发)--第一部分

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.80/5 (3投票s)

2016年11月13日

CPOL

2分钟阅读

viewsIcon

9226

Android&IOS 的自定义绘制 CalendarView

引言

本系列文章将展示如何为IOS和Android平台实现一个自定义绘制的日历视图。

我们将使用C/C++代码来处理日期和日历的逻辑。

对于IOS系统,Objc将调用C/C++函数。

对于Android系统,我们将使用SWIG(Simplified Wrapper and Interface Generator)库,它将生成JNI代码,以便从Java访问C/C++代码。

目录

  1. C/C++中日期和日历操作的需求描述和实现
  2. 如何从Objc调用C/C++代码,并在IOS中创建一个自定义绘制的日历视图
  3. 如何配置Android NDK并使用gradle系统编译C/C++代码
  4. 如何生成JNI代码,以便从Java访问C/C++代码
  5. 更新到android studio 2.2并使用cmake编译C/C++代码

需求描述

  • 第一次点击,选择可选择日期区域中的一个单元格
  • 第二次点击,选择上次选择的单元格和当前单元格之间的所有单元格
  • 重复这些步骤
  • 它将根据滑动手势在UITableView/ListView中滚动日历

为什么在移动开发中使用C/C++?

  1. 我熟悉C/C++
  2. 这个日历视图是在两年前编写的。这是我第一次进行移动开发,并选择了cocos2d-x进行跨平台开发。我发现cocos2d-x更适合游戏,而不是应用程序。cocos2d-x缺乏“脏区域”机制,导致每1/60秒重新绘制整个场景,并且在省电方面表现不佳。最终,我们决定选择原生语言进行逻辑开发,并使用C/C++进行核心操作。
  3. Android的apk很容易反编译。编译后的C/C++二进制代码很难反编译。
  4. 在cocos2d-x中,它使用SWIG(Simplified Wrapper and Interface Generator)生成PInvoke代码,以便从C#访问C/C++代码,在CocosStudio中。我认为这是一种很棒的技术,并且想掌握它。我们可以在android应用程序中使用它。

为什么在这个日历视图(控件)中使用自定义绘制?

无论IOS还是Android,我认为有3种方法可以扩展你自己的视图(控件)

  1. 你可以通过InterfaceBuilder或androidStudio在容器中使用现有的视图。不需要从基视图类继承。你只需要编写事件处理程序。
  2. 你可以从一个视图或容器视图继承,并使用现有的视图。你可以重写一些虚拟方法来扩展你的功能。
  3. 你可以从一个基视图继承,并绘制你所需的所有内容(自定义绘制)。

我们为这个日历视图选择了自定义绘制的方式。

它节省内存并且运行速度快。

 

C/C++基础结构

/*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
/*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

C/C++日期函数

/*
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);
}

C/C++日历函数

/*
 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;
}

注意

这是我第一次用英语写文章。

我已经尽力使自己能够被理解。如有任何问题,请告诉我。

抱歉我的英语不好。

下一篇

    第二部分:如何从Objc调用C/C++代码,并在IOS中创建一个自定义绘制的日历视图

  第二部分将提供IOS源代码。

© . All rights reserved.