中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

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

發布時間:2020-07-15 18:04:11 來源:網絡 閱讀:615 作者:jackyBLF 欄目:移動開發


章節:

 1、需求描述以及c/c++實現日期和月歷的基本操作

 2、ios實現自繪日期選擇控件

 3、android實現自繪日期選擇控件

目的:

通過一個相對復雜的自定義自繪控件來分享:

 1、ios以及android自定義自繪控件的開發流程

 2、objc與c/c++混合編程

 3、android ndk的環境配置,android studio ndk的編譯模式,swig在android ndk開發中的作用

一、需求描述以及c/c++實現日期和月歷的基本操作

1、需求描述:

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

    1) 該日期選擇控件主要用于日期過濾查詢,一般用于近n年的數據查詢。

    2) 假設你點擊某個按鈕彈出該日期選擇控件時,自動定位到當前月,例如本月是4月,則4月份顯示在屏幕最下方。

    3) 當你手指向上滑動時,向前n個月滾動(月份例如:4-3-2-1),手指向下滑動時,向后n個月滾動(月份例如:1-2-3-4)。

    4) 日期區分為可選擇區或不可選擇區(圖1),當你第一次點擊可選的日期,該日期會被選中(藍色)。

    5) 當你第二次點擊可選的日期,則會形成一個選區(圖2),該選區會跳過所有不可選的日期。

2、為什么使用c/c++:

  1) 歷史原因:該控件是兩年前為某個項目實現的,當時沒有移動開發經驗,因此最初技術預研時選擇了跨平臺的cocos2d-x來開發,并為其實現了該控件.但是cocos2d-x存在一些bug,并且其基于游戲開發模式,不停的循環繪制,cpu占用高,耗電量大。如果要用cocos2d-x開發一般的app的話,需要為其加入臟區局部刷新的功能,這樣改動量太大。在研究cocos2d-x時,其所見即所得的cocostudio需要使用swig將c/c++代碼wrap成c#供其進行平臺調用。當時覺得swig真是強大無比,可以自動wrap為c#,java,python,lua,js....等進行相互調用。

  2) ios端objc可以非常容易的與c/c++進行相互調用,而android ndk+swig也可以大大減輕c/c++代碼在android端的實現和調用難度(具體我們在第三部分中可以體會到)。這樣我們就能夠重用為cocos2d-x所寫的c/c++代碼。

  3) 基于java的android程序,非常容易進行反編譯,因此使用c/c++編譯成.so后,都是二進制代碼。因此如果對運行速度或代碼安全性有比較高的要求的話,可以使用c/c++進行實現,由android java jni進行調用。

  4) 還有一個重要原因就是想深入了解一下android ndk以及swig的開發方式。

3、為什么選擇自定義自繪控件方式:

不管是android還是ios,自定義控件的實現基本上有三種方式:

 1) 利用androidStudio或xcode interfaceBuilder中的容器控件以及其他控件組合拼裝而成,自定義控件不需要繼承自View或子類。你可以進行一些事件的編寫就可以完成很多需求。

 2) 繼承自View或子類,再用現有的控件組合拼裝而成。

 3) 繼承自View或子類,所有該自定義View的顯示效果由我們來繪制出。

這里我們采取第三種方式。相對來說,這種方式內存消耗要小很多,并且速度上也有一定優勢吧。要知道每個月都是需要42個cell表示日期,并且加上年月和星期這些區塊,都用View組合而成,內存也不算小。不管是ios還是android,每個View的成員變量都不少。而使用自繪控件,只要一個View就解決了。至少內存使用上可以減少40多個View的使用,對吧?

4、c/c++實現細節:
1) android中的一些適配結構和函數:
   因為使用了ios內置的例如CGRect,CGPoint,CGSize等c語言結構,而android ndk中沒有這些結構,因此對于android來說,需要實現這些結構以及在整個程序中用到的一些函數。c/c++中要做到這些,可以使用宏來判斷和切換當前的環境,具體見代碼:

/*blf: 使用ios中的一些基礎數據結構,android中需要移植過來 。  
       ios的話,請將下面 #define ANDROID_NDK_IMP這句代碼注釋掉!
*/
#define ANDROID_NDK_IMP
#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: 使用ios中的一些基礎數據結構,android中需要移植過來  
       下面是實現代碼
*/
#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

2) 日期操作函數:
這些函數都和日期操作相關,具體請參考代碼,注釋應該比較清楚的。

/*
blf: 函數參數都是以指針方式傳入(java或c#中就為傳引用,swig將指針會轉換為類對象,所有類對象在java和c#中都是傳引用的.

     c#支持struct,是值類型

    c#還支持參數的ref和out方式,可以將值類型以傳引用方式輸出到參數中,相當于c中的指針

    經驗之談:除非在c/c++中你使用shared_ptr等智能指針,否則千萬不要在函數或成員方法中malloc或new一個新對象然后return出來。

    比較好的方式還是通過參數傳指針或引用方式來返回更新的數據。
*/
void date_set(SDate* ret,int year,int month,int day)
{
   assert(ret);
   ret->year = year;
   ret->month = month;
   ret->day = day;
}

/*
blf: 獲取當前的年月日
*/
void date_get_now(SDate* ret)
{
   assert(ret);

   //time()此函數會返回從公元 1970 年1 月1 日的UTC 時間從0 時0 分0 秒算起到現在所經過的秒數。
   //記住:是秒數,而不是毫秒數(很多語言返回的是毫秒數,crt中是以秒為單位的)
   //如果t 并非空指針的話,此函數也會將返回值存到t指針所指的內存

   time_t t;
   time(&t);

   //轉換到當前系統的本地時間
   struct tm* timeInfo;
   timeInfo = localtime(&t);

   //tm結構中的年份是從1900開始到今天的年數,因此需要加上1900
   ret->year  =  timeInfo->tm_year + 1900;

   //月份是 0 base的,我們按照1-12的方式來計算,因此加1
   ret->month =  timeInfo->tm_mon + 1;

   ret->day   =  timeInfo->tm_mday;
}

/*
blf: 是否相等
*/
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: 計算兩個年份之間的月數
*/
int date_get_month_count_from_year_range(int startYear,int endYear)
{
   int diff = endYear - startYear + 1;
   return diff * 12;
}

/*
blf: 將一維的數據表示映射成二維表示(年和月)
     startYear表示起始年,例如 2010年
     idx表示相對2010年開始的月份偏移量

     我們會在下面和后面代碼中看到/ 和 %的多次使用
     可以理解為,將一維數據映射成二維行列表示的數據時,都可以使用這種方式

     下面這個函數用于月歷區間選擇控件,例如你有個數據庫查詢需求,可以查詢
     當前年月日----五年前的年1月1號之間的數據,此時在UITabelView或ListView時,就需要調用本函數來顯示年月信息等
*/
void date_map_index_to_year_month(SDate* to,int startYear,int idx)
{
   assert(to);

   //每年有12個月,idx/12你可以看成每12個月進一位,加上startYear基準值,就可以獲得當前年份
   to->year = startYear + idx / 12;

   //每年有12個月,idx%12你可以看成【0-11】之間循環,加1是因為我們的SDate結構是1-12表示的
   to->month = idx % 12 + 1;

   //至于day,這里為-1,我們在map中忽略該值,可以設置任意值
   to->day = -1;
}

/*
blf: 下面函數來源于linux實現,計算從某個時間點(年月日時分秒)到1970年0時0分0秒的時間差
參考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: 下面函數一共實現了三個版本

     第一版: 不知道是我對c的mktime用法錯誤還是有bug(理論上不可能,因為ios和android中都存在問題)
             同一個時間點,例如2016年1月1日0時0分1秒與1970年1月1日0時0分0秒的時間差不一樣。

     第二版: 使用ios自身的 NSCalendar對象計算時間差,這個計算是正確的,但是只能用在ios中

     第三版: http://blog.csdn.net/axx1611/article/details/1792827中的算法,來自于linux源碼,ios/android中運行的很好

     為什么不用time_t而是使用long呢?
     這是因為android中使用swig將c/c++ 代碼轉換成java jni封裝的函數時,time_t被封裝成了對象。
     因為java不認識c的typedef結構,Swig將其轉換為SWITGYPT_p_XXXXX類型的包裝(經典的裝箱/拆箱,每次操作都要進行裝箱拆箱,很麻煩).
     time_t只是64位整型的typedef而已,因此轉換為long后,經Swig轉換后,對應為java的整型,這樣操作起來比較簡單

*/

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

    /*
     1、第一版
    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、第二版 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、網上Linux版本
     */
     return mymktime(d->year,d->month,d->day,0,0,1);
}

/*
blf: 根據delta計算月份,返回值存儲在date結構中
     例如:當前年月為2015年1月份,delta為2,則返回2014年11月
*/
void date_get_prev_month(SDate* date, int delta)
{
   assert(date);

   if((date->month - delta) < 1)
   {
      //條件: 假設為2015年1月,delta = 2
      //因為: 1-2 = -1 < 1
      //所以: 年數 = 2015 - 1 = 2014 月份 = 12 + 1 - 2 = 11
      date->year--;
      date->month = 12 + date->month - delta;
   }
   else
      date->month = date->month - delta;
}

/*
blf: 根據delta計算出月份,返回值存儲在date結構中
     例如:當前年月為2015年11月份,delta為2,則返回2016年1月
*/
void date_get_next_month(SDate* date, int delta)
{
   assert(date);
   if((date->month + delta) > 12)
   {
      //條件: 假設為2015年11月,delta = 2
      //因為: 11 + 2 = 13 > 12
      //所以: 年數 = 2015 + 1 = 2016 月份 = 11 + 2 - 12 = 1
      date->year++;
      date->month = date->month + delta - 12;
   }
   else
      date->month = date->month + delta;
}

/*
blf: 根據輸入年份,判斷是否是閏年
     固定算法:判斷閏年的方法是該年能被4整除并且不能被100整除,或者是可以被400整除
*/
int date_get_leap(int year)
{
   if(((year % 4 == 0) && (year % 100) != 0) || (year % 400 == 0))
      return 1;
   return 0;
}

/*
blf: 輔助函數,用于計算某年某月的某天是星期幾
*/
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);
}

/*
blf: 用于計算某年某月的某天是星期幾,調用上面函數
     這些算法比較固定,具體原理也不需要太了解,因為我也不清楚。
*/
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);
}

/*
blf: 用于計算某個月的天數
*/
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:2月比較特別,要進行閏年判斷
   return 28 + date_get_leap(year);
}

3) 月歷操作函數:

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

 */

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;
      //每個day設置為1號
      calendar->date.day = 1;

      //blf:
      //參考上面圖示,dayBeginIdx獲得的是某個月1號相對日期區塊中的索引,例如本例中1號索引為6
      //而dayCount表示當前月的天數
      //這樣通過偏移與長度,我們可以很容易進行某些重要操作
      //例如在碰撞檢測某個cell是否被點中時,可以跳過沒有日期的cell
      //在繪圖時檢測某個cell是否找范圍之外,如果之外就用灰色現實等等
      //通過偏移量和count,進行范圍判斷
      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;
}

/*
 blf: 初始化一個月歷結構,填充所有成員變量的數據
*/
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;
   //blf:daySectionHeight是計算出來的
   calendar->daySectionHeight = ownerSize.height - yearMonthHeight - weekHeight;
   //blf:錯誤檢測,簡單期間,全部使用assert在debug時候進行中斷
   assert(calendar->daySectionHeight > 0);

   //blf:初始化時顯示本地當前的年月日
   //date_get_now(&calendar->date);

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

/*
 blf: 計算出年月區塊的rect
*/
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;
}

/*
 blf: 計算出星期區塊的rect
*/
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;
}

/*
 blf: 計算出年日期區塊的rect
*/
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;
}

/*
blf: 上面計算出來的是三大整體的區塊(section)
     下面計算出來的都是某個大區快中的子區(cell)
*/

/*
 blf:
 獲取星期區塊中每個索引指向的子區rect位置與尺寸
 輸出數據在rect參數中
 ---------------------------
 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
 ---------------------------
 idx = 0時表示星期日
 用于繪圖
 */

void calendar_get_week_cell_rect(const SCalendar* calendar,CGRect* rect,int idx)
{
   assert(calendar && rect && idx >= 0 && idx < 7);
   //獲取星期區塊
   calendar_get_week_section_rect(calendar, rect);
   //計算出cell的寬度
   float cellWidth = rect->size.width / 7.0F;
   //計算出x偏移量
   rect->origin.x = cellWidth * idx;
   rect->size.width = cellWidth;
}

/*
 blf:
 獲取日期區塊中行列索引指向的子區rect位置與尺寸
 ---------------------------
 | 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
 ---------------------------

 一個月總是在28--31天之間,由于星期要縮進,因此6行7列足夠解決由于星期縮進引起的顯示不全問題
 用于繪圖以及碰撞檢測,共計42個單元格

 以二維方式獲取日期區塊中索引指向的子區的rect位置與尺寸
 */

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:
 以一維方式方式獲取日期區塊中索引指向的子區的rect位置與尺寸
 */
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);
   int columIdx = (idx % 7);
   calendar_get_day_cell_rect(calendar, rect, rowIdx, columIdx);

}

/*
 blf:
 檢測touchPoint是否點擊在日期區塊的某一個cell中
 如果檢測到有cell被點中,返回索引
 否則返回-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
 ---------------------------
注意: 參數localPt是相對于你所繼承的View的左上角[0,0]的偏移量,是定義在View空間坐標系的
 */
int calendar_get_hitted_day_cell_index(const SCalendar* calendar, CGPoint localPt)
{
   //優化1: 如果一個點不在日期區塊中,那么肯定沒點中,立即返回
   CGRect daySec;
   calendar_get_day_section_rect(calendar, &daySec);

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

   localPt.y -= daySec.origin.y;

   //觸摸點肯定會會點中日期區塊中的某個cell

   //優化2: 避免使用循環6*7次遍歷整個cell,檢測是否一點在該cell中,通過下面算法,可以立刻獲得當前點所在的cell行列索引號

   float cellWidth  =   daySec.size.width  / 7.0F;
   float cellHeight =   daySec.size.height / 6.0F;
   int   columIdx   =   localPt.x / cellWidth;
   int   rowIdx     =   localPt.y / cellHeight;

   //檢測當前被點中的cell是否允許被選中,具體原理請參考
   //函數void calendar_set_year_month(SCalendar* calendar,int year,int month)的注釋

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

   //到此說明肯定有點中的cell,返回該cell的索引號
   return idx;
}

第一部分完畢,下一篇我們關注ios的實現。


向AI問一下細節

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

AI

亳州市| 资兴市| 江安县| 开平市| 辉南县| 伊金霍洛旗| 咸丰县| 江北区| 盐亭县| 手游| 保靖县| 阿拉善左旗| 扎兰屯市| 广丰县| 紫阳县| 兴隆县| 邵东县| 柳河县| 集安市| 贞丰县| 兴仁县| 靖西县| 积石山| 宁远县| 祁东县| 遂宁市| 防城港市| 雷山县| 邛崃市| 象州县| 乐平市| 通州市| 屏东市| 红桥区| 保山市| 筠连县| 桐乡市| 临潭县| 宜阳县| 镇雄县| 深州市|