android自定义日历

来源:互联网 发布:java解压zip文件jar包 编辑:程序博客网 时间:2024/06/06 03:52

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



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




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

CalendarView:

package com.example.calendar.widget;import com.example.caledar.util.DateUtil;import com.example.calendar.doim.CustomDate;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;public class CalendarView extends View {private static final String TAG = "CalendarView";/** * 两种模式 (月份和星期) */public static final int MONTH_STYLE = 0;public static final int WEEK_STYLE = 1;private static final int TOTAL_COL = 7;private static final int TOTAL_ROW = 6;private Paint mCirclePaint;private Paint mTextPaint;private int mViewWidth;private int mViewHight;private int mCellSpace;private Row rows[] = new Row[TOTAL_ROW];private static CustomDate mShowDate;//自定义的日期  包括year month daypublic static int style = MONTH_STYLE;private static final int WEEK = 7;private CallBack mCallBack;//回调private int touchSlop;private boolean callBackCellSpace;public interface CallBack {void clickDate(CustomDate date);//回调点击的日期void onMesureCellHeight(int cellSpace);//回调cell的高度确定slidingDrawer高度void changeDate(CustomDate date);//回调滑动viewPager改变的日期}public CalendarView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init(context);}public CalendarView(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public CalendarView(Context context) {super(context);init(context);}public CalendarView(Context context, int style, CallBack mCallBack) {super(context);CalendarView.style = style;this.mCallBack = mCallBack;init(context);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);for (int i = 0; i < TOTAL_ROW; i++) {if (rows[i] != null)rows[i].drawCells(canvas);}}private void init(Context context) {mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);mCirclePaint.setStyle(Paint.Style.FILL);mCirclePaint.setColor(Color.parseColor("#F24949"));touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();initDate();}private void initDate() {if (style == MONTH_STYLE) {mShowDate = new CustomDate();} else if(style == WEEK_STYLE ) {mShowDate = DateUtil.getNextSunday();}fillDate();}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mViewWidth = w;mViewHight = h;mCellSpace = Math.min(mViewHight / TOTAL_ROW, mViewWidth / TOTAL_COL);if (!callBackCellSpace) {mCallBack.onMesureCellHeight(mCellSpace);callBackCellSpace = true;}mTextPaint.setTextSize(mCellSpace / 3);}private Cell mClickCell;private float mDownX;private float mDownY;/* *  * 触摸事件为了确定点击的位置日期 */@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mDownX = event.getX();mDownY = event.getY();break;case MotionEvent.ACTION_UP:float disX = event.getX() - mDownX;float disY = event.getY() - mDownY;if (Math.abs(disX) < touchSlop && Math.abs(disY) < touchSlop) {int col = (int) (mDownX / mCellSpace);int row = (int) (mDownY / mCellSpace);measureClickCell(col, row);}break;}return true;}private void measureClickCell(int col, int row) {if (col >= TOTAL_COL || row >= TOTAL_ROW)return;if (mClickCell != null) {rows[mClickCell.j].cells[mClickCell.i] = mClickCell;}if (rows[row] != null) {mClickCell = new Cell(rows[row].cells[col].date,rows[row].cells[col].state, rows[row].cells[col].i,rows[row].cells[col].j);rows[row].cells[col].state = State.CLICK_DAY;CustomDate date = rows[row].cells[col].date;date.week = col;mCallBack.clickDate(date);invalidate();}}// 组class Row {public int j;Row(int j) {this.j = j;}public Cell[] cells = new Cell[TOTAL_COL];public void drawCells(Canvas canvas) {for (int i = 0; i < cells.length; i++) {if (cells[i] != null)cells[i].drawSelf(canvas);}}}// 单元格class Cell {public CustomDate date;public State state;public int i;public int j;public Cell(CustomDate date, State state, int i, int j) {super();this.date = date;this.state = state;this.i = i;this.j = j;}// 绘制一个单元格 如果颜色需要自定义可以修改public void drawSelf(Canvas canvas) {switch (state) {case CURRENT_MONTH_DAY:mTextPaint.setColor(Color.parseColor("#80000000"));break;case NEXT_MONTH_DAY:case PAST_MONTH_DAY:mTextPaint.setColor(Color.parseColor("#40000000"));break;case TODAY:mTextPaint.setColor(Color.parseColor("#F24949"));break;case CLICK_DAY:mTextPaint.setColor(Color.parseColor("#fffffe"));canvas.drawCircle((float) (mCellSpace * (i + 0.5)),(float) ((j + 0.5) * mCellSpace), mCellSpace / 2,mCirclePaint);break;}// 绘制文字String content = date.day+"";canvas.drawText(content,(float) ((i+0.5) * mCellSpace - mTextPaint.measureText(content)/2),(float) ((j + 0.7) * mCellSpace - mTextPaint.measureText(content, 0, 1) / 2), mTextPaint);}}/** *  * @author huang * cell的state *当前月日期,过去的月的日期,下个月的日期,今天,点击的日期 * */enum State {CURRENT_MONTH_DAY, PAST_MONTH_DAY, NEXT_MONTH_DAY, TODAY, CLICK_DAY;}/** * 填充日期的数据 */private void fillDate() {if (style == MONTH_STYLE) {fillMonthDate();} else if(style == WEEK_STYLE) {fillWeekDate();}mCallBack.changeDate(mShowDate);}/** * 填充星期模式下的数据 默认通过当前日期得到所在星期天的日期,然后依次填充日期 */private void fillWeekDate() {int lastMonthDays = DateUtil.getMonthDays(mShowDate.year, mShowDate.month-1);rows[0] = new Row(0);int day = mShowDate.day;for (int i = TOTAL_COL -1; i >= 0 ; i--) {day -= 1;if (day < 1) {day = lastMonthDays;}CustomDate date = CustomDate.modifiDayForObject(mShowDate, day);if (DateUtil.isToday(date)) {mClickCell = new Cell(date, State.TODAY, i, 0);date.week = i;mCallBack.clickDate(date);rows[0].cells[i] =  new Cell(date, State.CLICK_DAY, i, 0);continue;}rows[0].cells[i] = new Cell(date, State.CURRENT_MONTH_DAY,i, 0);}}/** * 填充月份模式下数据 通过getWeekDayFromDate得到一个月第一天是星期几就可以算出所有的日期的位置 然后依次填充 * 这里最好重构一下 */private void fillMonthDate() {int monthDay = DateUtil.getCurrentMonthDay();int lastMonthDays = DateUtil.getMonthDays(mShowDate.year, mShowDate.month - 1);int currentMonthDays = DateUtil.getMonthDays(mShowDate.year, mShowDate.month);int firstDayWeek = DateUtil.getWeekDayFromDate(mShowDate.year, mShowDate.month);boolean isCurrentMonth = false;if (DateUtil.isCurrentMonth(mShowDate)) {isCurrentMonth = true;}int day = 0;for (int j = 0; j < TOTAL_ROW; j++) {rows[j] = new Row(j);for (int i = 0; i < TOTAL_COL; i++) {int postion = i + j * TOTAL_COL;if (postion >= firstDayWeek&& postion < firstDayWeek + currentMonthDays) {day++;if (isCurrentMonth && day == monthDay) {CustomDate date = CustomDate.modifiDayForObject(mShowDate, day);mClickCell = new Cell(date,State.TODAY, i,j);date.week = i;mCallBack.clickDate(date);rows[j].cells[i] = new Cell(date,State.CLICK_DAY, i,j);continue;}rows[j].cells[i] = new Cell(CustomDate.modifiDayForObject(mShowDate, day),State.CURRENT_MONTH_DAY, i, j);} else if (postion < firstDayWeek) {rows[j].cells[i] = new Cell(new CustomDate(mShowDate.year, mShowDate.month-1, lastMonthDays - (firstDayWeek- postion - 1)), State.PAST_MONTH_DAY, i, j);} else if (postion >= firstDayWeek + currentMonthDays) {rows[j].cells[i] = new Cell((new CustomDate(mShowDate.year, mShowDate.month+1, postion - firstDayWeek - currentMonthDays + 1)), State.NEXT_MONTH_DAY, i, j);}}}}public void update() {fillDate();invalidate();}public void backToday(){initDate();invalidate();}//切换stylepublic void switchStyle(int style) {CalendarView.style = style;if (style == MONTH_STYLE) {update();} else if (style == WEEK_STYLE) {int firstDayWeek = DateUtil.getWeekDayFromDate(mShowDate.year,mShowDate.month);int day =  1 + WEEK - firstDayWeek;mShowDate.day = day;update();}}//向右滑动public void rightSilde() {if (style == MONTH_STYLE) {if (mShowDate.month == 12) {mShowDate.month = 1;mShowDate.year += 1;} else {mShowDate.month += 1;}} else if (style == WEEK_STYLE) {int currentMonthDays = DateUtil.getMonthDays(mShowDate.year, mShowDate.month);if (mShowDate.day + WEEK > currentMonthDays) {if (mShowDate.month == 12) {mShowDate.month = 1;mShowDate.year += 1;} else {mShowDate.month += 1;}mShowDate.day = WEEK - currentMonthDays + mShowDate.day;}else{mShowDate.day += WEEK;}}update();}//向左滑动public void leftSilde() {if (style == MONTH_STYLE) {if (mShowDate.month == 1) {mShowDate.month = 12;mShowDate.year -= 1;} else {mShowDate.month -= 1;}} else if (style == WEEK_STYLE) {int lastMonthDays = DateUtil.getMonthDays(mShowDate.year, mShowDate.month);if (mShowDate.day - WEEK < 1) {if (mShowDate.month == 1) {mShowDate.month = 12;mShowDate.year -= 1;} else {mShowDate.month -= 1;}mShowDate.day = lastMonthDays - WEEK + mShowDate.day;}else{mShowDate.day -= WEEK;}Log.i(TAG, "leftSilde"+mShowDate.toString());}update();}}


CalendarViewBuilder:

package com.example.calendar.doim;import android.content.Context;import com.example.calendar.widget.CalendarView;import com.example.calendar.widget.CalendarView.CallBack;/** * CalendarView的辅助类 * @author huang * */public class CalendarViewBuilder {private CalendarView[] calendarViews;/** * 生产多个CalendarView * @param context * @param count * @param style * @param callBack * @return */public  CalendarView[] createMassCalendarViews(Context context,int count,int style,CallBack callBack){calendarViews = new CalendarView[count];for(int i = 0; i < count;i++){calendarViews[i] = new CalendarView(context, style,callBack);}return calendarViews;}public  CalendarView[] createMassCalendarViews(Context context,int count,CallBack callBack){return createMassCalendarViews(context, count, CalendarView.MONTH_STYLE,callBack);}/** * 切换CandlendarView的样式 * @param style */public void swtichCalendarViewsStyle(int style){if(calendarViews != null)for(int i = 0 ;i < calendarViews.length;i++){calendarViews[i].switchStyle(style);}}/** * CandlendarView回到当前日期 */public void backTodayCalendarViews(){if(calendarViews != null)for(int i = 0 ;i < calendarViews.length;i++){calendarViews[i].backToday();}}}


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

CustomViewPagerAdapter:

package com.example.calendar.widget;import android.os.Parcelable;import android.support.v4.view.PagerAdapter;import android.support.v4.view.ViewPager;import android.view.View;public class CustomViewPagerAdapter<V extends View> extends PagerAdapter {private V[] views;public CustomViewPagerAdapter(V[] views) {super();this.views = views;}@Overridepublic void finishUpdate(View arg0) {}@Overridepublic void notifyDataSetChanged() {super.notifyDataSetChanged();}@Overridepublic int getCount() {return Integer.MAX_VALUE;}@Overridepublic Object instantiateItem(View arg0, int arg1) {if (((ViewPager) arg0).getChildCount() == views.length) {((ViewPager) arg0).removeView(views[arg1 % views.length]);}((ViewPager) arg0).addView(views[arg1 % views.length], 0);return views[arg1 % views.length];}@Overridepublic boolean isViewFromObject(View arg0, Object arg1) {return arg0 == (arg1);}@Overridepublic Parcelable saveState() {return null;}@Overridepublic void destroyItem(View arg0, int arg1, Object arg2) {// TODO Auto-generated method stub}@Overridepublic void startUpdate(View arg0) {}public V[] getAllItems() {return views;}}


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

CalendarViewPagerLisenter:

package com.example.calendar.widget;import android.support.v4.view.ViewPager.OnPageChangeListener;public class CalendarViewPagerLisenter implements OnPageChangeListener {private SildeDirection mDirection = SildeDirection.NO_SILDE;int mCurrIndex = 498;private CalendarView[] mShowViews;public CalendarViewPagerLisenter(CustomViewPagerAdapter<CalendarView> viewAdapter) {super();this.mShowViews = viewAdapter.getAllItems();}@Overridepublic void onPageSelected(int arg0) {measureDirection(arg0);updateCalendarView(arg0);}private void updateCalendarView(int arg0) {if(mDirection == SildeDirection.RIGHT){mShowViews[arg0 % mShowViews.length].rightSilde();}else if(mDirection == SildeDirection.LEFT){mShowViews[arg0 % mShowViews.length].leftSilde();}mDirection = SildeDirection.NO_SILDE;}/** * 判断滑动方向 * @param arg0 */private void measureDirection(int arg0) {if (arg0 > mCurrIndex) {mDirection = SildeDirection.RIGHT;} else if (arg0 < mCurrIndex) {mDirection = SildeDirection.LEFT;}mCurrIndex = arg0;}@Overridepublic void onPageScrolled(int arg0, float arg1, int arg2) {}@Overridepublic void onPageScrollStateChanged(int arg0) {}enum SildeDirection {RIGHT, LEFT, NO_SILDE;}}

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



下载地址:http://download.csdn.net/detail/huangyanbin123/7723323

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

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

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

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

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

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




11 0