android自定义日历并添加事件

来源:互联网 发布:毕业摄影的数据 编辑:程序博客网 时间:2024/06/08 16:45

前几天闲来无事,变想做一些小工具玩玩。花了一天多的时间,弄出一个简单日历的View。分为月份模式和星期模式。滚动查看,先上图看看:



上面的是显示的是月份的模式。下面是星期的模式:




       CalendarView是一个自定义View,然后通过Viewpager的OnpageChangeListener进行刷新View的数据。Viewpager通过轮回使用View。我默认设置是5个。可以左右无限切换。后面因为我在ViewPager下面加了一个slidingDrawer。打开抽屉就可以直接切换成week的模式。为了使Adapter和OnPageChangeListener两个类可以和CalendarView减少耦合,我增加了一个CalendarViewBuilder类。

CalendarView:

[java] view plaincopy
  1. package com.example.calendar.widget;  
  2.   
  3. import com.example.caledar.util.DateUtil;  
  4. import com.example.calendar.doim.CustomDate;  
  5.   
  6. import android.content.Context;  
  7. import android.graphics.Canvas;  
  8. import android.graphics.Color;  
  9. import android.graphics.Paint;  
  10. import android.util.AttributeSet;  
  11. import android.util.Log;  
  12. import android.view.MotionEvent;  
  13. import android.view.View;  
  14. import android.view.ViewConfiguration;  
  15.   
  16. public class CalendarView extends View {  
  17.   
  18.     private static final String TAG = "CalendarView";  
  19.     /** 
  20.      * 两种模式 (月份和星期) 
  21.      */  
  22.     public static final int MONTH_STYLE = 0;  
  23.     public static final int WEEK_STYLE = 1;  
  24.   
  25.     private static final int TOTAL_COL = 7;  
  26.     private static final int TOTAL_ROW = 6;  
  27.   
  28.     private Paint mCirclePaint;  
  29.     private Paint mTextPaint;  
  30.     private int mViewWidth;  
  31.     private int mViewHight;  
  32.     private int mCellSpace;  
  33.     private Row rows[] = new Row[TOTAL_ROW];  
  34.     private static CustomDate mShowDate;//自定义的日期  包括year month day  
  35.     public static int style = MONTH_STYLE;  
  36.     private static final int WEEK = 7;  
  37.     private CallBack mCallBack;//回调  
  38.     private int touchSlop;  
  39.     private boolean callBackCellSpace;  
  40.   
  41.     public interface CallBack {  
  42.   
  43.         void clickDate(CustomDate date);//回调点击的日期  
  44.   
  45.         void onMesureCellHeight(int cellSpace);//回调cell的高度确定slidingDrawer高度  
  46.   
  47.         void changeDate(CustomDate date);//回调滑动viewPager改变的日期  
  48.     }  
  49.   
  50.     public CalendarView(Context context, AttributeSet attrs, int defStyle) {  
  51.         super(context, attrs, defStyle);  
  52.         init(context);  
  53.   
  54.     }  
  55.   
  56.     public CalendarView(Context context, AttributeSet attrs) {  
  57.         super(context, attrs);  
  58.         init(context);  
  59.   
  60.     }  
  61.   
  62.     public CalendarView(Context context) {  
  63.         super(context);  
  64.         init(context);  
  65.     }  
  66.   
  67.     public CalendarView(Context context, int style, CallBack mCallBack) {  
  68.         super(context);  
  69.         CalendarView.style = style;  
  70.         this.mCallBack = mCallBack;  
  71.         init(context);  
  72.     }  
  73.   
  74.     @Override  
  75.     protected void onDraw(Canvas canvas) {  
  76.         super.onDraw(canvas);  
  77.         for (int i = 0; i < TOTAL_ROW; i++) {  
  78.             if (rows[i] != null)  
  79.                 rows[i].drawCells(canvas);  
  80.         }  
  81.     }  
  82.   
  83.     private void init(Context context) {  
  84.         mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  85.         mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  86.         mCirclePaint.setStyle(Paint.Style.FILL);  
  87.         mCirclePaint.setColor(Color.parseColor("#F24949"));  
  88.         touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();  
  89.         initDate();  
  90.   
  91.     }  
  92.   
  93.     private void initDate() {  
  94.         if (style == MONTH_STYLE) {  
  95.             mShowDate = new CustomDate();  
  96.         } else if(style == WEEK_STYLE ) {  
  97.             mShowDate = DateUtil.getNextSunday();  
  98.         }  
  99.         fillDate();  
  100.     }  
  101.   
  102.     @Override  
  103.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  104.         super.onSizeChanged(w, h, oldw, oldh);  
  105.         mViewWidth = w;  
  106.         mViewHight = h;  
  107.         mCellSpace = Math.min(mViewHight / TOTAL_ROW, mViewWidth / TOTAL_COL);  
  108.         if (!callBackCellSpace) {  
  109.             mCallBack.onMesureCellHeight(mCellSpace);  
  110.             callBackCellSpace = true;  
  111.         }  
  112.         mTextPaint.setTextSize(mCellSpace / 3);  
  113.     }  
  114.   
  115.     private Cell mClickCell;  
  116.     private float mDownX;  
  117.     private float mDownY;  
  118. /* 
  119.  *  
  120.  * 触摸事件为了确定点击的位置日期 
  121.  */  
  122.     @Override  
  123.     public boolean onTouchEvent(MotionEvent event) {  
  124.         switch (event.getAction()) {  
  125.         case MotionEvent.ACTION_DOWN:  
  126.             mDownX = event.getX();  
  127.             mDownY = event.getY();  
  128.             break;  
  129.         case MotionEvent.ACTION_UP:  
  130.             float disX = event.getX() - mDownX;  
  131.             float disY = event.getY() - mDownY;  
  132.             if (Math.abs(disX) < touchSlop && Math.abs(disY) < touchSlop) {  
  133.                 int col = (int) (mDownX / mCellSpace);  
  134.                 int row = (int) (mDownY / mCellSpace);  
  135.                 measureClickCell(col, row);  
  136.             }  
  137.             break;  
  138.         }  
  139.         return true;  
  140.     }  
  141.   
  142.     private void measureClickCell(int col, int row) {  
  143.         if (col >= TOTAL_COL || row >= TOTAL_ROW)  
  144.             return;  
  145.         if (mClickCell != null) {  
  146.             rows[mClickCell.j].cells[mClickCell.i] = mClickCell;  
  147.         }  
  148.         if (rows[row] != null) {  
  149.             mClickCell = new Cell(rows[row].cells[col].date,  
  150.                     rows[row].cells[col].state, rows[row].cells[col].i,  
  151.                     rows[row].cells[col].j);  
  152.             rows[row].cells[col].state = State.CLICK_DAY;  
  153.             CustomDate date = rows[row].cells[col].date;  
  154.             date.week = col;  
  155.             mCallBack.clickDate(date);  
  156.             invalidate();  
  157.         }  
  158.     }  
  159.   
  160.     // 组  
  161.     class Row {  
  162.         public int j;  
  163.   
  164.         Row(int j) {  
  165.             this.j = j;  
  166.         }  
  167.   
  168.         public Cell[] cells = new Cell[TOTAL_COL];  
  169.   
  170.         public void drawCells(Canvas canvas) {  
  171.             for (int i = 0; i < cells.length; i++) {  
  172.                 if (cells[i] != null)  
  173.                     cells[i].drawSelf(canvas);  
  174.             }  
  175.   
  176.         }  
  177.     }  
  178.   
  179.     // 单元格  
  180.     class Cell {  
  181.         public CustomDate date;  
  182.         public State state;  
  183.         public int i;  
  184.         public int j;  
  185.   
  186.         public Cell(CustomDate date, State state, int i, int j) {  
  187.             super();  
  188.             this.date = date;  
  189.             this.state = state;  
  190.             this.i = i;  
  191.             this.j = j;  
  192.         }  
  193.   
  194.   
  195.         // 绘制一个单元格 如果颜色需要自定义可以修改  
  196.         public void drawSelf(Canvas canvas) {  
  197.             switch (state) {  
  198.             case CURRENT_MONTH_DAY:  
  199.                 mTextPaint.setColor(Color.parseColor("#80000000"));  
  200.                 break;  
  201.             case NEXT_MONTH_DAY:  
  202.             case PAST_MONTH_DAY:  
  203.                 mTextPaint.setColor(Color.parseColor("#40000000"));  
  204.                 break;  
  205.             case TODAY:  
  206.                 mTextPaint.setColor(Color.parseColor("#F24949"));  
  207.                 break;  
  208.             case CLICK_DAY:  
  209.                 mTextPaint.setColor(Color.parseColor("#fffffe"));  
  210.                 canvas.drawCircle((float) (mCellSpace * (i + 0.5)),  
  211.                         (float) ((j + 0.5) * mCellSpace), mCellSpace / 2,  
  212.                         mCirclePaint);  
  213.                 break;  
  214.             }  
  215.             // 绘制文字  
  216.             String content = date.day+"";  
  217.             canvas.drawText(content,  
  218.                     (float) ((i+0.5) * mCellSpace - mTextPaint.measureText(content)/2),  
  219.                     (float) ((j + 0.7) * mCellSpace - mTextPaint.measureText(  
  220.                             content, 01) / 2), mTextPaint);  
  221.         }  
  222.     }  
  223. /** 
  224.  *  
  225.  * @author huang 
  226.  * cell的state 
  227.  *当前月日期,过去的月的日期,下个月的日期,今天,点击的日期 
  228.  * 
  229.  */  
  230.     enum State {  
  231.         CURRENT_MONTH_DAY, PAST_MONTH_DAY, NEXT_MONTH_DAY, TODAY, CLICK_DAY;  
  232.     }  
  233.   
  234.     /** 
  235.      * 填充日期的数据 
  236.      */  
  237.     private void fillDate() {  
  238.         if (style == MONTH_STYLE) {  
  239.             fillMonthDate();  
  240.         } else if(style == WEEK_STYLE) {  
  241.             fillWeekDate();  
  242.         }  
  243.         mCallBack.changeDate(mShowDate);  
  244.     }  
  245.   
  246.     /** 
  247.      * 填充星期模式下的数据 默认通过当前日期得到所在星期天的日期,然后依次填充日期 
  248.      */  
  249.     private void fillWeekDate() {  
  250.         int lastMonthDays = DateUtil.getMonthDays(mShowDate.year, mShowDate.month-1);  
  251.         rows[0] = new Row(0);  
  252.         int day = mShowDate.day;  
  253.         for (int i = TOTAL_COL -1; i >= 0 ; i--) {  
  254.             day -= 1;  
  255.             if (day < 1) {  
  256.                 day = lastMonthDays;  
  257.             }  
  258.             CustomDate date = CustomDate.modifiDayForObject(mShowDate, day);  
  259.             if (DateUtil.isToday(date)) {  
  260.                 mClickCell = new Cell(date, State.TODAY, i, 0);  
  261.                 date.week = i;  
  262.                 mCallBack.clickDate(date);  
  263.                 rows[0].cells[i] =  new Cell(date, State.CLICK_DAY, i, 0);  
  264.                 continue;  
  265.             }  
  266.             rows[0].cells[i] = new Cell(date, State.CURRENT_MONTH_DAY,i, 0);  
  267.         }  
  268.     }  
  269.   
  270.     /** 
  271.      * 填充月份模式下数据 通过getWeekDayFromDate得到一个月第一天是星期几就可以算出所有的日期的位置 然后依次填充 
  272.      * 这里最好重构一下 
  273.      */  
  274.     private void fillMonthDate() {  
  275.         int monthDay = DateUtil.getCurrentMonthDay();  
  276.         int lastMonthDays = DateUtil.getMonthDays(mShowDate.year, mShowDate.month - 1);  
  277.         int currentMonthDays = DateUtil.getMonthDays(mShowDate.year, mShowDate.month);  
  278.         int firstDayWeek = DateUtil.getWeekDayFromDate(mShowDate.year, mShowDate.month);  
  279.         boolean isCurrentMonth = false;  
  280.         if (DateUtil.isCurrentMonth(mShowDate)) {  
  281.             isCurrentMonth = true;  
  282.         }  
  283.         int day = 0;  
  284.         for (int j = 0; j < TOTAL_ROW; j++) {  
  285.             rows[j] = new Row(j);  
  286.             for (int i = 0; i < TOTAL_COL; i++) {  
  287.                 int postion = i + j * TOTAL_COL;  
  288.                 if (postion >= firstDayWeek  
  289.                         && postion < firstDayWeek + currentMonthDays) {  
  290.                     day++;  
  291.                     if (isCurrentMonth && day == monthDay) {  
  292.                         CustomDate date = CustomDate.modifiDayForObject(mShowDate, day);  
  293.                         mClickCell = new Cell(date,State.TODAY, i,j);  
  294.                         date.week = i;  
  295.                         mCallBack.clickDate(date);  
  296.                         rows[j].cells[i] = new Cell(date,State.CLICK_DAY, i,j);  
  297.                         continue;  
  298.                     }  
  299.                     rows[j].cells[i] = new Cell(CustomDate.modifiDayForObject(mShowDate, day),  
  300.                             State.CURRENT_MONTH_DAY, i, j);  
  301.                 } else if (postion < firstDayWeek) {  
  302.                     rows[j].cells[i] = new Cell(new CustomDate(mShowDate.year, mShowDate.month-1, lastMonthDays - (firstDayWeek- postion - 1)), State.PAST_MONTH_DAY, i, j);  
  303.                 } else if (postion >= firstDayWeek + currentMonthDays) {  
  304.                     rows[j].cells[i] = new Cell((new CustomDate(mShowDate.year, mShowDate.month+1, postion - firstDayWeek - currentMonthDays + 1)), State.NEXT_MONTH_DAY, i, j);  
  305.                 }  
  306.             }  
  307.         }  
  308.     }  
  309.   
  310.     public void update() {  
  311.         fillDate();  
  312.         invalidate();  
  313.     }  
  314.       
  315.     public void backToday(){  
  316.         initDate();  
  317.         invalidate();  
  318.     }  
  319.     //切换style  
  320.     public void switchStyle(int style) {  
  321.         CalendarView.style = style;  
  322.         if (style == MONTH_STYLE) {  
  323.             update();  
  324.         } else if (style == WEEK_STYLE) {  
  325.             int firstDayWeek = DateUtil.getWeekDayFromDate(mShowDate.year,  
  326.                     mShowDate.month);  
  327.             int day =  1 + WEEK - firstDayWeek;  
  328.             mShowDate.day = day;  
  329.               
  330.             update();  
  331.         }  
  332.           
  333.     }  
  334. //向右滑动  
  335.     public void rightSilde() {  
  336.         if (style == MONTH_STYLE) {  
  337.               
  338.             if (mShowDate.month == 12) {  
  339.                 mShowDate.month = 1;  
  340.                 mShowDate.year += 1;  
  341.             } else {  
  342.                 mShowDate.month += 1;  
  343.             }  
  344.               
  345.         } else if (style == WEEK_STYLE) {  
  346.             int currentMonthDays = DateUtil.getMonthDays(mShowDate.year, mShowDate.month);  
  347.             if (mShowDate.day + WEEK > currentMonthDays) {  
  348.                 if (mShowDate.month == 12) {  
  349.                     mShowDate.month = 1;  
  350.                     mShowDate.year += 1;  
  351.                 } else {  
  352.                     mShowDate.month += 1;  
  353.                 }  
  354.                 mShowDate.day = WEEK - currentMonthDays + mShowDate.day;      
  355.             }else{  
  356.                 mShowDate.day += WEEK;  
  357.               
  358.             }  
  359.         }  
  360.         update();  
  361.     }  
  362. //向左滑动  
  363.     public void leftSilde() {  
  364.           
  365.         if (style == MONTH_STYLE) {  
  366.             if (mShowDate.month == 1) {  
  367.                 mShowDate.month = 12;  
  368.                 mShowDate.year -= 1;  
  369.             } else {  
  370.                 mShowDate.month -= 1;  
  371.             }  
  372.               
  373.         } else if (style == WEEK_STYLE) {  
  374.             int lastMonthDays = DateUtil.getMonthDays(mShowDate.year, mShowDate.month);  
  375.             if (mShowDate.day - WEEK < 1) {  
  376.                 if (mShowDate.month == 1) {  
  377.                     mShowDate.month = 12;  
  378.                     mShowDate.year -= 1;  
  379.                 } else {  
  380.                     mShowDate.month -= 1;  
  381.                 }  
  382.                 mShowDate.day = lastMonthDays - WEEK + mShowDate.day;  
  383.                   
  384.             }else{  
  385.                 mShowDate.day -= WEEK;  
  386.             }  
  387.             Log.i(TAG, "leftSilde"+mShowDate.toString());  
  388.         }  
  389.         update();  
  390.     }  
  391. }  


CalendarViewBuilder:

[java] view plaincopy
  1. package com.example.calendar.doim;  
  2.   
  3. import android.content.Context;  
  4.   
  5. import com.example.calendar.widget.CalendarView;  
  6. import com.example.calendar.widget.CalendarView.CallBack;  
  7. /** 
  8.  * CalendarView的辅助类 
  9.  * @author huang 
  10.  * 
  11.  */  
  12. public class CalendarViewBuilder {  
  13.         private CalendarView[] calendarViews;  
  14.         /** 
  15.          * 生产多个CalendarView 
  16.          * @param context 
  17.          * @param count 
  18.          * @param style 
  19.          * @param callBack 
  20.          * @return 
  21.          */  
  22.         public  CalendarView[] createMassCalendarViews(Context context,int count,int style,CallBack callBack){  
  23.             calendarViews = new CalendarView[count];  
  24.             for(int i = 0; i < count;i++){  
  25.                 calendarViews[i] = new CalendarView(context, style,callBack);  
  26.             }  
  27.             return calendarViews;  
  28.         }  
  29.           
  30.         public  CalendarView[] createMassCalendarViews(Context context,int count,CallBack callBack){  
  31.               
  32.             return createMassCalendarViews(context, count, CalendarView.MONTH_STYLE,callBack);  
  33.         }  
  34.         /** 
  35.          * 切换CandlendarView的样式 
  36.          * @param style 
  37.          */  
  38.         public void swtichCalendarViewsStyle(int style){  
  39.             if(calendarViews != null)  
  40.             for(int i = 0 ;i < calendarViews.length;i++){  
  41.                 calendarViews[i].switchStyle(style);  
  42.             }  
  43.         }  
  44.         /** 
  45.          * CandlendarView回到当前日期 
  46.          */  
  47.           
  48.         public void backTodayCalendarViews(){  
  49.             if(calendarViews != null)  
  50.             for(int i = 0 ;i < calendarViews.length;i++){  
  51.                 calendarViews[i].backToday();  
  52.             }  
  53.         }  
  54. }  


为了Viewpager可以双向无限滑动,我重写了ViewPagerAdapter。

CustomViewPagerAdapter:

[java] view plaincopy
  1. package com.example.calendar.widget;  
  2.   
  3. import android.os.Parcelable;  
  4. import android.support.v4.view.PagerAdapter;  
  5. import android.support.v4.view.ViewPager;  
  6. import android.view.View;  
  7.   
  8. public class CustomViewPagerAdapter<V extends View> extends PagerAdapter {  
  9.       
  10.     private V[] views;  
  11.   
  12.       
  13.     public CustomViewPagerAdapter(V[] views) {  
  14.         super();  
  15.         this.views = views;  
  16.     }  
  17.   
  18.     @Override  
  19.     public void finishUpdate(View arg0) {  
  20.     }  
  21.   
  22.     @Override  
  23.     public void notifyDataSetChanged() {  
  24.         super.notifyDataSetChanged();  
  25.     }  
  26.   
  27.     @Override  
  28.     public int getCount() {  
  29.         return Integer.MAX_VALUE;  
  30.     }  
  31.   
  32.     @Override  
  33.     public Object instantiateItem(View arg0, int arg1) {  
  34.         if (((ViewPager) arg0).getChildCount() == views.length) {  
  35.             ((ViewPager) arg0).removeView(views[arg1 % views.length]);  
  36.         }  
  37.         ((ViewPager) arg0).addView(views[arg1 % views.length], 0);  
  38.   
  39.         return views[arg1 % views.length];  
  40.     }  
  41.   
  42.     @Override  
  43.     public boolean isViewFromObject(View arg0, Object arg1) {  
  44.         return arg0 == (arg1);  
  45.     }  
  46.   
  47.     @Override  
  48.     public Parcelable saveState() {  
  49.         return null;  
  50.     }  
  51.   
  52.     @Override  
  53.     public void destroyItem(View arg0, int arg1, Object arg2) {  
  54.         // TODO Auto-generated method stub  
  55.   
  56.     }  
  57.   
  58.     @Override  
  59.     public void startUpdate(View arg0) {  
  60.     }  
  61.   
  62.       
  63.     public V[] getAllItems() {  
  64.         return views;  
  65.     }  
  66. }  


然后为了实现对CalendarView的滑动时的数据更新,我重写了OnPageChangeListener的方法。这个类还是有一定的耦合,但是如果是项目中大量使用ViewPager。可以增加泛型进行复用。

CalendarViewPagerLisenter:

[java] view plaincopy
  1. package com.example.calendar.widget;  
  2.   
  3. import android.support.v4.view.ViewPager.OnPageChangeListener;  
  4.   
  5. public class CalendarViewPagerLisenter implements OnPageChangeListener {  
  6.   
  7.     private SildeDirection mDirection = SildeDirection.NO_SILDE;  
  8.     int mCurrIndex = 498;  
  9.     private CalendarView[] mShowViews;  
  10.   
  11.     public CalendarViewPagerLisenter(CustomViewPagerAdapter<CalendarView> viewAdapter) {  
  12.         super();  
  13.         this.mShowViews = viewAdapter.getAllItems();  
  14.     }  
  15.   
  16.     @Override  
  17.     public void onPageSelected(int arg0) {  
  18.         measureDirection(arg0);  
  19.         updateCalendarView(arg0);  
  20.     }  
  21.   
  22.     private void updateCalendarView(int arg0) {  
  23.         if(mDirection == SildeDirection.RIGHT){  
  24.             mShowViews[arg0 % mShowViews.length].rightSilde();  
  25.         }else if(mDirection == SildeDirection.LEFT){  
  26.             mShowViews[arg0 % mShowViews.length].leftSilde();  
  27.         }  
  28.         mDirection = SildeDirection.NO_SILDE;  
  29.     }  
  30.   
  31.       
  32.     /** 
  33.      * 判断滑动方向 
  34.      * @param arg0 
  35.      */  
  36.     private void measureDirection(int arg0) {  
  37.   
  38.         if (arg0 > mCurrIndex) {  
  39.             mDirection = SildeDirection.RIGHT;  
  40.   
  41.         } else if (arg0 < mCurrIndex) {  
  42.             mDirection = SildeDirection.LEFT;  
  43.         }  
  44.         mCurrIndex = arg0;  
  45.     }  
  46.   
  47.     @Override  
  48.     public void onPageScrolled(int arg0, float arg1, int arg2) {  
  49.     }  
  50.   
  51.     @Override  
  52.     public void onPageScrollStateChanged(int arg0) {  
  53.     }  
  54.   
  55.   
  56.   
  57.     enum SildeDirection {  
  58.         RIGHT, LEFT, NO_SILDE;  
  59.     }  
  60. }  

这是个简单的demo,但是我把他封装起来,直接调用就可以了。如果有需要的同学,可以直接下载就可以了。已经修改版:支持日期点击事件。最后这个页面功能没有实现。



在这个小demo中我遇到一些问题和思考:

1.如何尽量的减少类之间耦合和增加类内聚。

2.如何使代码易读,好多方法extract Method出来比较烦,涉及参数太多。

3.检查错误。因为少了一个break的原因,我debug了半个小时。(一开始就debug到了,不相信咋会调用这个方法)。

4.如何平衡为了减少new对象使用一些int,还是为了以后增加功能增加一些对象的产生。

5.感觉这只是个demo的命名就感觉很随意,没有约束。


转载网址:http://blog.csdn.net/huangyanbin123/article/details/38350213


0 0
原创粉丝点击