[Android]自定义简易版日历控件

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

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

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

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

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

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的11
		int 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);

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

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.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();
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		// 获得控件宽度
		width = getMeasuredWidth();
		// 计算日历表格宽度
		dateNumWidth = width / 7.0f;
		// 计算日历高度
		height = (int) (dateNumWidth * 6);
		// 设置控件宽高
		setMeasuredDimension(width, height);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// TODO Auto-generated method stub
		super.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();
	@Override
	public 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

时间: 2024-07-29 11:50:09

[Android]自定义简易版日历控件的相关文章

Android自定义组件之日历控件-精美日历实现(内容、样式可扩展)

需求 我们知道,Android系统本身有自带的日历控件,网络上也有很多开源的日历控件资源,但是这些日历控件往往样式较单一,API较多,不易于在实际项目中扩展并实现出符合具体样式风格的,内容可定制的效果.本文通过自定义日历控件,实现了在内容和样式上可高度扩展的精美日历demo,有需要的Android应用开发人员可迅速移植并按需扩展实现. 在某个应用中,需要查询用户的历史考勤记录,根据实际考勤数据在日历中标记出不同的状态(如正常出勤.请假.迟到等),并在页面中显示相应的说明文字. 效果 实现的效果如

android - 自定义(组合)控件 + 自定义控件外观

转载:http://www.cnblogs.com/bill-joy/archive/2012/04/26/2471831.html android - 自定义(组合)控件 + 自定义控件外观 Android自定义View实现很简单 继承View,重写构造函数.onDraw,(onMeasure)等函数. 如果自定义的View需要有自定义的属性,需要在values下建立attrs.xml.在其中定义你的属性. 在使用到自定义View的xml布局文件中需要加入xmlns:前缀="http://sc

Android自定义View之组合控件 ---- LED数字时钟

先上图 LEDView效果如图所示. 之前看到一篇博客使用两个TextView实现了该效果,于是我想用自定义控件的方式实现一个LEDView,使用时即可直接使用该控件. 采用组合控件的方式,将两个TextView叠放在一起,再使用digital-7.ttf字体来显示数据,从而达到LED的效果.代码如下: LEDView.class package ione.zy.demo; import java.io.File; import java.util.Calendar; import java.u

Android自定义流式标签控件

最近总感觉写博客的激情不高,不知道为啥.放上效果图,demo在最下面 图上那个切换按钮的作用呢,就是模拟改变标签的个数动态变化整个控件的高度. 其实这个控件也算很简单的控件了.关键点只有两个 如何控制标签自动换行 切换数据源时动态改变控件的高度 再简单的控件也需要一点一点的码出来,咱就从最基础的属性设置开始. public FlowTagView textColor(int defaultColor, int selectedColor){ this.textColorDefault = def

Android自定义实现循环滚轮控件WheelView

首先呈上效果图 现在很多地方都用到了滚轮布局WheelView,比如在选择生日的时候,风格类似系统提供的DatePickerDialog,开源的控件也有很多,不过大部分都是根据当前项目的需求绘制的界面,因此我就自己写了一款比较符合自己项目的WheelView. 首先这个控件有以下的需求: 1.能够循环滚动,当向上或者向下滑动到临界值的时候,则循环开始滚动 2.中间的一块有一块半透明的选择区,滑动结束时,哪一块在这个选择区,就选择这快. 3.继承自View进行绘制 然后进行一些关键点的讲解: 1.

Android自定义设置圆形图片控件

注:这篇文章是转载alan_biao博主的一篇文章,正好用到,觉得里面代码很精髓,贴出来并给与链接供需要的童鞋下载使用!已贴出核心代码和提供源码地址. Android自定义圆形图片,可设置最多两个的外边框,包括从网络获取图片显示. 1.解决图片锯齿问题. 2.解决图片变形问题. 效果图: 原始图片: 原文地址和源码下载链接:http://blog.csdn.net/alan_biao/article/details/17379925

Android超炫日期日历控件:TimesSquare

先看效果图: 使用说明: 在布局文件中: <com.squareup.timessquare.CalendarPickerView android:id="@+id/calendar_view" android:layout_width="match_parent" android:layout_height="match_parent" /> 在Java代码中: Calendar nextYear = Calendar.getIns

开源日历控件DatePicker源码解析

在一些项目开发中,会使用日历去标识事务,所以根据美工出的效果图,我们可以采用不同的方法去实现.比如通过GridView扣扣你敢.自定义View实现日历控件,这些都是我们解决问题的手段,我也实现过一个自定义日历控件(Android自定义控件之日历控件55993)),由于我只是粗糙的进行实现,并没有进行过多的在控件的可扩展性上进行打磨设计,所以在本篇文章中,我秉着学习的态度分析下爱哥的鼎力巨作DatePicker-DatePicker. DatePicker开源项目地址:[https://githu

Android自定义View(CustomCalendar-定制日历控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeasure 4onDraw 绘制月份 绘制星期 绘制日期及任务 5事件处理 源码下载 ??应项目需求,需要做一个日历控件,效果图如下: ???? ??接到需求后,没有立即查找是否有相关开源日历控件可用.系统日历控件是否能满足 ,第一反应就是这个控件该怎么画?谁叫咱自定义控件技术牛逼呢O(∩_∩)O哈哈~