先来看看效果图,看看是不是各位大佬想要的:

特别的功能并不多,重点是讲解简易日历该如何构造,假若是项目着急要用的话,最好还是找一下其它人写好的日历(附加滑动改变日历日期等功能)


---------------------------------------------------------------------------------------华丽的分割线-------------------------------------------------------------------------------------


根据所输入的年份以及月份显示那个月份视图,并且能监听日期的点击,这便是该日历需要实现的功能。

和以往一样,当我们拿到一份需求时,先别着急敲代码,先思考功能的实现分为多少部分,然后再一步步地去实现,在这里,我大致将其分为以下这几个功能:

1、获得某月份的日期分布;

2、将获得的日期分布绘画在画布上;

3、监听日期点击事件。

咋看功能也不多,也就三个,下面将一步步讲解:


(1)获得某月份的日期分布

在实现这个功能时,我选择了Calendar这个类,翻译过来就是日历类,它是个抽象类,但是却提供了以下方法让我们获取一下日历值,例如获取得到下列日历值:

int year = Calendar.getInstance().get(Calendar.YEAR);//获取当前年份int month = Calendar.getInstance().get(Calendar.MONTH) + 1;// +1是因为返回来的值并不是代表月份,而是对应于Calendar.MAY常数的值,// Calendar在月份上的常数值从Calendar.JANUARY开始是0,到Calendar.DECEMBER的11int dayOfMonth = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);// 获取当前的时间为该月的第几天int dayOfWeek = Calendar.getInstance().get(Calendar.DAY_OF_WEEK);//获取当前的时间为该周的第几天(需要注意的是,一周的第一天为周日,值为1)
其实,使用以上这些便足够可以求出我们这部分所需要的功能了。

不过,就是上面的代码都是获得当日的日历类,因此所获得的日期都是本日的,假如我们想看之前的日期或之后的日期呢,这就需要我们去手动改变它了:

Calendar calendar = Calendar.getInstance();calendar.set(year, month, day);

假如我们calendar.set(year, month, day);中的day设置为1,然后便可以通过int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);方式获得该月的第一天是周几,于是第一部分我们想要解决的功能就完成!

嗯?不懂?好像说得有些快,我再仔细说说:

每个月的天数差不多是固定的(除了闰年的二月变成29),都是(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)

因此我们可以很明确的通过月份确定这个月的天数,然后,假若我们知道了这个月的第一天,然后在这天后不断加上天数,直到填满这个月的天数,这样,我们就可以知道这个月的日期排布了。例如:

具体的实现代码,我封装了一下,大家可以参考一下:DateUtil.java

调用public static int[][] getMonthNumFromDate(int year, int month)方法获得日历日期数组,大家可以输出测试看看

package com.xiaoyan.mycalendar;import java.util.Calendar;/** * 用于处理日期工具类 *  * @author xiejinxiong *  */public class DateUtil {/** * 获取当前年份 *  * @return */public static int getYear() {return Calendar.getInstance().get(Calendar.YEAR);}/** * 获取当前月份 *  * @return */public static int getMonth() {return Calendar.getInstance().get(Calendar.MONTH) + 1;// +1是因为返回来的值并不是代表月份,而是对应于Calendar.MAY常数的值,// Calendar在月份上的常数值从Calendar.JANUARY开始是0,到Calendar.DECEMBER的11}/** * 获取当前的时间为该月的第几天 *  * @return */public static int getCurrentMonthDay() {return Calendar.getInstance().get(Calendar.DAY_OF_MONTH);}/** * 获取当前的时间为该周的第几天 *  * @return */public static int getWeekDay() {return Calendar.getInstance().get(Calendar.DAY_OF_WEEK);}/** * 获取当前时间为该天的多少点 *  * @return */public static int getHour() {return Calendar.getInstance().get(Calendar.HOUR_OF_DAY);// Calendar calendar = Calendar.getInstance();// System.out.println(calendar.get(Calendar.HOUR_OF_DAY)); // 24小时制// System.out.println(calendar.get(Calendar.HOUR)); // 12小时制}/** * 获取当前的分钟时间 *  * @return */public static int getMinute() {return Calendar.getInstance().get(Calendar.MINUTE);}/** * 通过获得年份和月份确定该月的日期分布 *  * @param year * @param month * @return */public static int[][] getMonthNumFromDate(int year, int month) {Calendar calendar = Calendar.getInstance();calendar.set(year, month - 1, 1);// -1是因为赋的值并不是代表月份,而是对应于Calendar.MAY常数的值,int days[][] = new int[6][7];// 存储该月的日期分布int firstDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);// 获得该月的第一天位于周几(需要注意的是,一周的第一天为周日,值为1)int monthDaysNum = getMonthDaysNum(year, month);// 获得该月的天数// 获得上个月的天数int lastMonthDaysNum = getLastMonthDaysNum(year, month);// 填充本月的日期int dayNum = 1;int lastDayNum = 1;for (int i = 0; i < days.length; i++) {for (int j = 0; j < days[i].length; j++) {if (i == 0 && j < firstDayOfWeek - 1) {// 填充上个月的剩余部分days[i][j] = lastMonthDaysNum - firstDayOfWeek + 2 + j;} else if (dayNum <= monthDaysNum) {// 填充本月days[i][j] = dayNum++;} else {// 填充下个月的未来部分days[i][j] = lastDayNum++;}}}return days;}/** * 根据年数以及月份数获得上个月的天数 *  * @param year * @param month * @return */public static int getLastMonthDaysNum(int year, int month) {int lastMonthDaysNum = 0;if (month == 1) {lastMonthDaysNum = getMonthDaysNum(year - 1, 12);} else {lastMonthDaysNum = getMonthDaysNum(year, month - 1);}return lastMonthDaysNum;}/** * 根据年数以及月份数获得该月的天数 *  * @param year * @param month * @return 若返回为负一,这说明输入的年数和月数不符合规格 */public static int getMonthDaysNum(int year, int month) {if (year < 0 || month <= 0 || month > 12) {// 对于年份与月份进行简单判断return -1;}int[] array = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };// 一年中,每个月份的天数if (month != 2) {return array[month - 1];} else {if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {// 闰年判断return 29;} else {return 28;}}}}


(2)将获得的日期分布绘画在画布上

首先,我们还是复习一下比较基础的知识:

Paint paintText = new Paint();paintText.setTextSize(30);paintText.setColor(Color.BLACK);canvas.drawText("25", 100, 100, paintText);
从以上代码便可以绘画出一个数字:

但是,这样还是不行的,因为绘画文字的时候,它并不是以绘画的那个中心点为中心绘制,而是在中心点的右上方。(呃,好像这样有些难看出,我加两条直线比较一下:)

Paint paintText = new Paint();paintText.setTextSize(30);paintText.setColor(Color.BLACK);canvas.drawText("25", 100, 100, paintText);canvas.drawLine(0, 100, 200, 100, paintText);canvas.drawLine(100, 0, 100, 200, paintText);

这样就非常明显,假如我们不加改动便把日期数字绘画上去,将会发现日历表格的数字是偏右的,为了杜绝这个问题,我们需要知道所绘制的文字的宽高,已便于对其进行适当的偏移:

a.获得文字的宽度:

paintText.measureText("25");

b.获得文字的高度:

FontMetrics fm = paintText.getFontMetrics();float fontHeight = (float) Math.ceil(fm.descent - fm.top)/2;

由上面这些,我们便可以非常容易的将数字居中显示了:

Paint paintText = new Paint();paintText.setTextSize(30);paintText.setColor(Color.BLACK);FontMetrics fm = paintText.getFontMetrics();float fontHeight = (float) Math.ceil(fm.descent - fm.top)/2;canvas.drawText("25", 100-paintText.measureText("25")/2f, 100+fontHeight/2, paintText);canvas.drawLine(0, 100, 200, 100, paintText);canvas.drawLine(100, 0, 100, 200, paintText);

好了,数字居中的问题解决了,但是,日历表格的大小还未确定,还需要动态获取控件大小以计算出表格大小:

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stubsuper.onMeasure(widthMeasureSpec, heightMeasureSpec);// 获得控件宽度width = getMeasuredWidth();// 计算日历表格宽度dateNumWidth = width / 7.0f;// 计算日历高度height = (int) (dateNumWidth * 6);// 设置控件宽高setMeasuredDimension(width, height);}
ok,所需要的重要东西都拿到了,现在只要根据所获得的日历数组,进行遍历绘制日期数字即可。相同的,我也进行封装,大家也可以参考一下:

package com.example.calendartest;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Paint.FontMetrics;import android.util.AttributeSet;import android.view.View;public class CalendarViewTest extends View {/** * 使用枚举表示日期状态(今天、本月、非本月) *  * @author xiejinxiong *  */public static enum CalendarState {TODAY, CURRENT_MONTH, NO_CURRENT_MONTH}/** 屏幕宽度 */private int width;/** 屏幕高度 */private int height;/** 日历数组 */private int[][] dateNum;/** 日历日期状态数组 */private CalendarState[][] calendarStates;/** 年 */private int year;/** 月 */private int month;/** 绘画类 */private DrawCalendar drawCalendar;/** 日历表格宽度 */private float dateNumWidth;public CalendarViewTest(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);initUI(context);}public CalendarViewTest(Context context, AttributeSet attrs) {super(context, attrs);initUI(context);}public CalendarViewTest(Context context) {super(context);initUI(context);}/** * 初始化UI *  * @param context */private void initUI(Context context) {// 初始化日期year = DateUtil.getYear();month = DateUtil.getMonth();calendarStates = new CalendarState[6][7];drawCalendar = new DrawCalendar(year, month);}/** * 设置日历时间并刷新日历视图 *  * @param year * @param month */public void setYearMonth(int year, int month) {this.year = year;this.month = month;drawCalendar = new DrawCalendar(year, month);invalidate();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stubsuper.onMeasure(widthMeasureSpec, heightMeasureSpec);// 获得控件宽度width = getMeasuredWidth();// 计算日历表格宽度dateNumWidth = width / 7.0f;// 计算日历高度height = (int) (dateNumWidth * 6);// 设置控件宽高setMeasuredDimension(width, height);}@Overrideprotected void onDraw(Canvas canvas) {// TODO Auto-generated method stubsuper.onDraw(canvas);drawCalendar.drawCalendarCanvas(canvas);}/** * 封装绘画日历方法的绘画类 *  * @author xiejinxiong *  */class DrawCalendar {/** 绘画日期画笔 */private Paint mPaintText;/** 绘画本日的蓝圆 背景的画笔 */private Paint mPaintCircle;/** 字体高度 */private float fontHeight;public DrawCalendar(int year, int month) {// 获得月份日期排布数组dateNum = DateUtil.getMonthNumFromDate(year, month);// 初始化绘画文本的画笔mPaintText = new Paint();mPaintText.setTextSize(25);mPaintText.setColor(Color.GRAY);// 设置灰色mPaintText.setAntiAlias(true);// 设置画笔的锯齿效果。// 获得字体高度FontMetrics fm = mPaintText.getFontMetrics();fontHeight = (float) Math.ceil(fm.descent - fm.top) / 2;// 初始化绘画圆圈的画笔mPaintCircle = new Paint();mPaintCircle.setColor(Color.argb(100, 112, 199, 244));// 设置蓝色mPaintCircle.setAntiAlias(true);// 设置画笔的锯齿效果。}/** * 绘画日历 *  * @param canvas */public void drawCalendarCanvas(Canvas canvas) {// canvas.drawCircle(width/2, width/2, width/2, mPaint);// 画圆for (int i = 0; i < dateNum.length; i++) {for (int j = 0; j < dateNum[i].length; j++) {if (i == 0 && dateNum[i][j] > 20) {// 上个月的日期drawCalendarCell(i, j, CalendarState.NO_CURRENT_MONTH,canvas);} else if ((i == 5 || i == 4) && dateNum[i][j] < 20) {// 下个月的日期drawCalendarCell(i, j, CalendarState.NO_CURRENT_MONTH,canvas);} else {// 本月日期if (dateNum[i][j] == DateUtil.getCurrentMonthDay()) {// 是否为今天的日期号if (year == DateUtil.getYear()&& month == DateUtil.getMonth()) {// 是否为今年今月drawCalendarCell(i, j, CalendarState.TODAY,canvas);}drawCalendarCell(i, j, CalendarState.CURRENT_MONTH,canvas);} else {drawCalendarCell(i, j, CalendarState.CURRENT_MONTH,canvas);}}}}}/** * 绘画日历表格 *  * @param i *            横序号 * @param j *            列序号 * @param state *            状态 * @param canvas *            画布 */private void drawCalendarCell(int i, int j, CalendarState state,Canvas canvas) {switch (state) {case TODAY:// 今天calendarStates[i][j] = CalendarState.TODAY;mPaintText.setColor(Color.WHITE);canvas.drawCircle(dateNumWidth * j + dateNumWidth / 2,dateNumWidth * i + dateNumWidth / 2, dateNumWidth / 2,mPaintCircle);break;case CURRENT_MONTH:// 本月calendarStates[i][j] = CalendarState.CURRENT_MONTH;mPaintText.setColor(Color.BLACK);break;case NO_CURRENT_MONTH:// 非本月calendarStates[i][j] = CalendarState.NO_CURRENT_MONTH;mPaintText.setColor(Color.GRAY);break;default:break;}// 绘画日期canvas.drawText(dateNum[i][j] + "", dateNumWidth * j + dateNumWidth/ 2 - mPaintText.measureText(dateNum[i][j] + "") / 2,dateNumWidth * i + dateNumWidth / 2 + fontHeight / 2.0f,mPaintText);}}}
(3)监听日期点击事件

监听日期点击是比较简单的,只要我们监听到所点击的位置(x,y),然后将该位置除以日历表格宽度,便可以得到日历表格数组的i,j下标,由此,便可以轻而易举地获得所点击的日期。

不过,有些需要注意的是,由于手指点下去的位置和手指松开的位置或许有些不同,因此,我们需要设置一个距离,在一个合适的距离内,我们便可以认为是一种点击事件。这设置这个距离时,我使用的是ViewConfiguration.get(context).getScaledTouchSlop(),这便是滑动的最小距离,因为一般日历都具有滑动的功能,使用该距离,可以避免与滑动事件有冲突。

当然,只是监听还是不够的,我们要提供一个回调接口供用户使用,便于执行点击事件的相关代码。

 touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:// 记录点击的坐标touchX = event.getX();touchY = event.getY();break;case MotionEvent.ACTION_UP:float touchLastX = event.getX();float touchLastY = event.getY();if (Math.abs(touchLastX - touchX) < touchSlop&& Math.abs(touchLastY - touchY) < touchSlop) {// 判断是否符合正常点击// 计算出所点击的数组序列int dateNumX = (int) (touchLastX / dateNumWidth);int dateNumY = (int) (touchLastY / dateNumWidth);// 使用回调函数响应点击日历日期onCalendarClickListener.onCalendaeClick(dateNum[dateNumY][dateNumX],calendarStates[dateNumY][dateNumX]);}break;default:break;}return true;}
/** * 日历监听类 *  * @author xiejinxiong *  */public interface OnCalendarClickListener {/** * 日历日期点击监听 *  * @param dateNum *            日期数字 * @param calendarState *            日期状态 */public void onCalendaeClick(int dateNum, CalendarState calendarState);}

通过以上内容,估计你对于日历的制作应该会有着自己的想法,你可以按照这种思路去设计自己想要的自定义日历。

源码:http://download.csdn.net/detail/u011596810/9478962









更多相关文章

  1. android日期选择控件
  2. Android(安卓)日历(EasyCalendar)
  3. Android日期时间格式国际化的实现代码
  4. 【Android(安卓)开发】:UI控件之 DatePicker 输入日期控件的使用
  5. Android(安卓)日历CalendarProvider
  6. Python3原生编写月份计算工具
  7. Android(安卓)自定义 弹框日期选择器 弹框,年月日,时分,
  8. Android(安卓)之 日期时间 时区同步
  9. Android(安卓)自定义日期控件 (仿QQ,IOS7)

随机推荐

  1. Android androidkickstartr maven 出错
  2. 关于android 上传文件
  3. 2012/01/04工作笔记
  4. 你不得不了解的JVM(一)
  5. PX30_Android8.1默认3G移动网络改为默认4
  6. PhoneGap将会颠覆iOS和Android?
  7. 无线管理的帮助类
  8. android 3g状态及信号监测
  9. Android(安卓)Gson使用入门及GsonFormat
  10. IMX6Q核心板Android系统编译笔记