需求
我们知道,Android系统本身有自带的日历控件,网络上也有很多开源的日历控件资源,但是这些日历控件往往样式较单一,API较多,不易于在实际项目中扩展并实现出符合具体样式风格的,内容可定制的效果。本文通过自定义日历控件,实现了在内容和样式上可高度扩展的精美日历demo,有需要的Android应用开发人员可迅速移植并按需扩展实现。
在某个应用中,需要查询用户的历史考勤记录,根据实际考勤数据在日历中标记出不同的状态(如正常出勤、请假、迟到等),并在页面中显示相应的说明文字。
效果
实现的效果如下
附上源码地址:http://download.csdn.net/detail/daijin888888/9020535
(直接导入Eclipse ADT即可,出现乱码请调整项目编码,笔者的是UTF-8编码)
实现方式
首先说明涉及的主要知识点:
- GridView+Adapter
- 日历算法
项目结构图如下:
com.widget.mycalendar 包下是主要的实现部分:
- CalendarGridView.java: 自定义日历GridView,此处可扩展样式,可实现日历中每个网格(对应某一天)的长按效果。
- CalendarGridViewAdapter.java: 日历适配器,对具体的网格赋值和选择性控制样式(实现多样化)。
- CalendarTool.java: 获取日历数据工具类,主要工具类,实现日历算法,包括闰年判断。
- DateEntity.java: 日历实体类,添加的参数在获取日历实体集合的时候设置,可以控制日期属性(比如某一天对应的年、月、日、星期、是否为当前日期、是否为本月日期)
首先看自定义日历GridView,
CalendarGridView.java:自定义日历GridView,此处可扩展样式,可实现日历中每个网格(对应某一天)的长按效果。
package com.widget.mycalendar;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.widget.AdapterView;import android.widget.GridView;/** * @author daij * @version 1.0 自定义日历GridView */public class CalendarGridView extends GridView implements android.widget.AdapterView.OnItemLongClickListener { private Context mContext; public CalendarGridView(Context context) { super(context); this.mContext = context; initGridView(); } public CalendarGridView(Context context, AttributeSet attrs) { super(context, attrs); this.mContext = context; initGridView(); } public CalendarGridView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.mContext = context; initGridView(); } /** * 初始化GirdView * * @param <参数名称> * <参数类型> <参数说明> * @return <返回值类型> * @throws <异常> */ public void initGridView() { } @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { return false; }}
CalendarGridViewAdapter.java:日历适配器,这里提供的是模拟数据,根据不同日期的属性控制不同显示,对具体的网格赋值和选择性控制样式(实现多样化)。
package com.widget.mycalendar;import java.util.List;import android.content.Context;import android.content.res.Resources;import android.text.TextUtils;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.LinearLayout;import android.widget.TextView;import com.example.mycalendar.R;/** * @author daij * @version 1.0 日历适配器 */public class CalendarGridViewAdapter extends BaseAdapter { private Resources mRes; /** 上下文 */ private Context mContext; /** 日期实体集合 */ private List<DateEntity> mDataList; /** 因为position是从0开始的,所以用当做一个中间者,用来加1.以达到判断除数时,为哪个星期 */ private int temp; public CalendarGridViewAdapter(Context context, Resources res) { this.mContext = context; this.mRes = res; } /** 设置日期数据 */ public void setDateList(List<DateEntity> dataList) { this.mDataList = dataList; } @Override public int getCount() { if (mDataList == null) { return 0; } return mDataList.size(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { // 通过传递过来的MenuItem值给每一个item设置数据 LinearLayout layout = (LinearLayout) LayoutInflater.from(mContext) .inflate(R.layout.calendar_item_layout, null); TextView textView = (TextView) layout .findViewById(R.id.calendar_item_tv_day); TextView tv_tip = (TextView) layout.findViewById(R.id.calendar_tip); if (mDataList != null) { textView.setText(mDataList.get(position).day + ""); if ((TextUtils.equals(CalendarTool.SATURDAY, mDataList.get(position).weekDay)) || TextUtils.equals(CalendarTool.SUNDAY, mDataList.get(position).weekDay)) { // 周末背景为白,字体为灰色 textView.setBackgroundColor(mRes.getColor(R.color.white)); textView.setTextColor(mRes.getColor(R.color.weekend_day_txt)); }// TODO 在非周末时候设置颜色 else { if (((mDataList.get(position).year == 2015 && mDataList .get(position).month <= 8) || (mDataList.get(position).year < 2015 && mDataList .get(position).month <= 12)) && mDataList.get(position).isSelfMonthDate) {// 2015.8以前,且日期在当月 if (mDataList.get(position).day > 0 && mDataList.get(position).day <= 20 || mDataList.get(position).day == 25) { tv_tip.setBackgroundColor(mRes .getColor(R.color.tip_normal)); } else if (mDataList.get(position).day == 21 || mDataList.get(position).day == 22) { tv_tip.setBackgroundColor(mRes .getColor(R.color.tip_leave)); } else if (mDataList.get(position).day == 23 || mDataList.get(position).day == 24) { tv_tip.setBackgroundColor(mRes .getColor(R.color.tip_late)); } else { tv_tip.setBackgroundColor(mRes .getColor(R.color.tip_normal)); if (mDataList.get(position).month == 8 && mDataList.get(position).day >= 25) { tv_tip.setBackgroundColor(mRes .getColor(R.color.white)); } } } } if (mDataList.get(position).isNowDate && mDataList.get(position).isSelfMonthDate) { // 如果为当前号数,则设置为白色背景并,字体为蓝色 textView.setBackgroundColor(mRes.getColor(R.color.white)); textView.setTextColor(mRes.getColor(R.color.current_day_txt)); } if (!mDataList.get(position).isSelfMonthDate) {// 是否为本月的号数,不是本月号数显示白色,及不显示 textView.setTextColor(mRes.getColor(R.color.white)); } layout.setTag(mDataList.get(position));// 把当前日历实体放入GridView 的Item中 } return layout; }}
CalendarTool.java: 获取日历数据工具类,实现日历算法,包括闰年判断,配有详细注释可参考
package com.widget.mycalendar;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.util.List;import android.content.Context;import android.graphics.Point;import android.util.Log;/** * @author daij * @version 1.0 获取日历数据工具类 */public class CalendarTool { public static final String MONDAY = "周一"; public static final String TUESDAY = "周二"; public static final String WEDNESDAY = "周三"; public static final String THURSDAY = "周四"; public static final String FRIDAY = "周五"; public static final String SATURDAY = "周六"; public static final String SUNDAY = "周日"; public static final String[] weekDayRow = { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }; private List<DateEntity> mDataList = new ArrayList<DateEntity>(); private DateEntity mDateEntity; private int mYear; private int mMonth; private int mDay; private int mDays; /** 系统当前年月日 */ private int mCurrenYear; private int mCurrenMonth; private int mCurrenDay; private Context mContext; /** 用于算法的变量 */ /** 已过去的年份总天数 */ int mGoneYearDays = 0; /** 本年包括今天的过去天数 */ int thisYearDays = 0; /** 是否为闰年 */ boolean isLeapYear = false; /** 平年月天数数组 */ int commonYearMonthDay[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; /** 闰年月天数数组 */ int leapYearMonthDay[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; public CalendarTool(Context context) { this.mContext = context; initNowDate(); } /** 获取当前日历的年月 x为年,y为月 */ public Point getNowCalendar() { Point p = new Point(mYear, mMonth); return p; } /** 通过年月获取日期实体集合 集合大小为7*6=42,星期为7,6行以显示足够数量日期 */ public List<DateEntity> getDateEntityList(int year, int month) { mDataList.clear(); int dayOfNowCalendar = 42;// 当前日历板的日期总数 7*6个数 int dayOfWeek = 0;// 得到当前年月的每一天为星期几 int selfDaysEndWeek = 0;// 本月的最后一天是星期几 int startDate = 0;// 当前月的上一个月在本日历的开始日期 int days = 0;// 得到本月的总共天数 /** 修改部分 */ int endDate = 0;// 得到上一个月的天数,作为上一个月在本日历的结束日期 if ((year - 1) == this.mYear || month == 1) {// 说明向前翻了一年,那么上个月的天数就应该是上一年的12月的天数,或者到翻到一月份的时候,那么上一个月的天数也是上一年的12月份的天数 endDate = this.getDays(year - 1, 12); } else {// 得到上一个月的天数,作为上一个月在本日历的结束日期 endDate = this.getDays(year, month - 1); } /** 修改部分结束 */ this.mYear = year;// 当前日历上显示的年 this.mMonth = month;// 当前日历上显示的月 int previoursMonthDays = 0;// 上一个月在本月显示的天数 int nextMonthDays = 0;// 下一个月在本月显示的天数 days = this.getDays(year, month); dayOfWeek = this.getWeekDay(year, month); if (dayOfWeek == 0) { startDate = endDate - 7 + 1; } else { startDate = endDate - dayOfWeek + 1; } previoursMonthDays = endDate - startDate + 1; nextMonthDays = dayOfNowCalendar - days - previoursMonthDays; /** 先添加前面不属于本月的 */ for (int i = startDate, j = 0; i <= endDate; i++, j++) { mDateEntity = new DateEntity(); mDateEntity.day = i; mDateEntity.isSelfMonthDate = false; mDateEntity.year = year; mDateEntity.month = month - 1; mDateEntity.weekDay = weekDayRow[j]; mDataList.add(mDateEntity); } /** 添加本月的 */ for (int i = 1, j = dayOfWeek; i <= days; i++, j++) { mDateEntity = new DateEntity(); mDateEntity.day = i; mDateEntity.isSelfMonthDate = true; mDateEntity.year = year; mDateEntity.month = month; if (j >= 7) { j = 0; } selfDaysEndWeek = j; mDateEntity.weekDay = weekDayRow[j]; if ((year == mCurrenYear) && (month == mCurrenMonth) && i == mCurrenDay) { mDateEntity.isNowDate = true; } mDataList.add(mDateEntity); } /*** 添加后面下一个月的 */ for (int i = 1, j = selfDaysEndWeek + 1; i <= nextMonthDays; i++, j++) { mDateEntity = new DateEntity(); mDateEntity.day = i; mDateEntity.isSelfMonthDate = false; mDateEntity.year = year; mDateEntity.month = month + 1; if (j >= 7) { j = 0; } mDateEntity.weekDay = weekDayRow[j]; mDataList.add(mDateEntity); } return mDataList; } /** 通过年月,获取这个月一共有多少天 */ public int getDays(int year, int month) { int days = 0; if ((year % 4 == 0 && (year % 100 != 0)) || (year % 400 == 0)) { if (month > 0 && month <= 12) { days = leapYearMonthDay[month - 1]; } } else { if (month > 0 && month <= 12) { days = commonYearMonthDay[month - 1]; } } return days; } /** 获取星期的排列 */ public String[] getWeekDayRow() { return weekDayRow; } /** 初始化当前系统的日期 */ public void initNowDate() { Date date = new Date(); SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-M-d"); String currentDate = simpleFormat.format(date); // 当期日期 mCurrenYear = Integer.parseInt(currentDate.split("-")[0]); mCurrenMonth = Integer.parseInt(currentDate.split("-")[1]); mCurrenDay = Integer.parseInt(currentDate.split("-")[2]); this.mYear = mCurrenYear; this.mMonth = mCurrenMonth; } /** 通过年,月获取当前月的第一天1日为星期几 ,返回0是星期天,1是星期一,依次类推 */ public int getWeekDay(int year, int month) { int dayOfWeek; int goneYearDays = 0; int thisYearDays = 0; boolean isLeapYear = false; int commonYearMonthDay[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; int leapYearMonthDay[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; for (int i = 1900; i < year; i++) {// 从1900年开始算起,1900年1月1日为星期一 if ((i % 4 == 0 && (i % 100 != 0)) || (i % 400 == 0)) { goneYearDays = goneYearDays + 366; } else { goneYearDays = goneYearDays + 365; } } if ((year % 4 == 0 && (year % 100 != 0)) || (year % 400 == 0)) { isLeapYear = true; for (int i = 0; i < month - 1; i++) { thisYearDays = thisYearDays + leapYearMonthDay[i]; } } else { isLeapYear = false; for (int i = 0; i < month - 1; i++) { thisYearDays = thisYearDays + commonYearMonthDay[i]; } } dayOfWeek = (goneYearDays + thisYearDays + 1) % 7; Log.d(this.getClass().getName(), "从1990到现在有" + (goneYearDays + thisYearDays + 1) + "天"); Log.d(this.getClass().getName(), year + "年" + month + "月" + 1 + "日是星期" + dayOfWeek); return dayOfWeek; }}
DateEntity.java:实体类,比较简单
package com.widget.mycalendar;import java.io.Serializable;/** * @author daij * @version 1.0 日历实体类,添加的参数在获取日历实体集合的时候设置 */public class DateEntity implements Serializable { private static final long serialVersionUID = -6053739977785155088L; /** 年 */ public int year; /** 月 */ public int month; /** 日 */ public int day; /** 星期 */ public String weekDay; /** 是否为当前日期 */ public boolean isNowDate; /** 是否为本月日期 */ public boolean isSelfMonthDate;}
MainActivity.java:主Activity,用于提供模拟数据和实现交互,代码如下:
package com.example.mycalendar;import java.util.List;import android.app.Activity;import android.graphics.Color;import android.graphics.Point;import android.graphics.drawable.ColorDrawable;import android.os.Bundle;import android.text.TextUtils;import android.view.View;import android.view.View.OnClickListener;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.ImageView;import android.widget.TextView;import com.widget.mycalendar.CalendarGridView;import com.widget.mycalendar.CalendarGridViewAdapter;import com.widget.mycalendar.CalendarTool;import com.widget.mycalendar.DateEntity;/** * @author daij * @version 1.0 日历:考勤记录 */public class MainActivity extends Activity { private CalendarGridViewAdapter mAdapter; private CalendarTool mCalendarTool; private CalendarGridView mGridView; private List<DateEntity> mDateEntityList; private Point mNowCalendarPoint; private ImageView mPrevioursIv; private ImageView mNextIv; private ImageView ivBack; private TextView mCalendarTv; private TextView tvDetail; @Override protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); super.onCreate(savedInstanceState); initView();// 初始化View setListeners(); // requestXmas();//请求数据 } /** 初始化view */ public void initView() { mPrevioursIv = (ImageView) findViewById(R.id.calendar_bar_iv_previours); mNextIv = (ImageView) findViewById(R.id.calendar_bar_iv_next); ivBack = (ImageView) findViewById(R.id.iv_back); mCalendarTv = (TextView) findViewById(R.id.calendar_bar_tv_date); tvDetail = (TextView) findViewById(R.id.tv_detail); mGridView = (CalendarGridView) findViewById(R.id.calendar_gridview); mGridView.setSelector(new ColorDrawable(Color.TRANSPARENT)); mGridView.setOnItemClickListener(new CalendarItemClickListener()); mPrevioursIv.setOnClickListener(new ImageViewClickListener()); mNextIv.setOnClickListener(new ImageViewClickListener()); mCalendarTool = new CalendarTool(this); mNowCalendarPoint = mCalendarTool.getNowCalendar(); mCalendarTv.setText(mNowCalendarPoint.x + "年" + mNowCalendarPoint.y + "月"); mDateEntityList = mCalendarTool.getDateEntityList(mNowCalendarPoint.x, mNowCalendarPoint.y); mAdapter = new CalendarGridViewAdapter(this, getResources()); mAdapter.setDateList(mDateEntityList); mGridView.setAdapter(mAdapter); } /** 日历监听类 */ class CalendarItemClickListener implements OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // TODO 模拟数据 DateEntity itemDate = (DateEntity) view.getTag(); if (!((TextUtils.equals(CalendarTool.SATURDAY, itemDate.weekDay)) || TextUtils .equals(CalendarTool.SUNDAY, itemDate.weekDay))) {// 非周末 if (((itemDate.year == 2015 && itemDate.month <= 8) || (itemDate.year < 2015 && itemDate.month <= 12)) && itemDate.isSelfMonthDate) {// 2015.8以前,且日期在当月 if (itemDate.day > 0 && itemDate.day <= 20 || itemDate.day == 25) {// 正常出勤 tvDetail.setText("备注:" + "\n" + " " + itemDate.year + "年" + itemDate.month + "月" + itemDate.day + "日" + "--" + itemDate.weekDay + "\n" + "正常出勤。"); } else if (itemDate.day == 21 || itemDate.day == 22) {// 请假 tvDetail.setText("备注:" + "\n" + " " + itemDate.year + "年" + itemDate.month + "月" + itemDate.day + "日" + "--" + itemDate.weekDay + "\n" + "请假两天,从" + itemDate.month + "月21日到" + itemDate.month + "月22日"); } else if (itemDate.day == 23 || itemDate.day == 24) {// 迟到、早退 if (itemDate.day == 23) { tvDetail.setText("备注:" + "\n" + " " + itemDate.year + "年" + itemDate.month + "月" + itemDate.day + "日" + "--" + itemDate.weekDay + "\n" + "上午8:36打卡,下午5:40打卡" + "\n" + "上午迟到6分钟"); } else if (itemDate.day == 24) { tvDetail.setText("备注:" + "\n" + " " + itemDate.year + "年" + itemDate.month + "月" + itemDate.day + "日" + "--" + itemDate.weekDay + "\n" + "上午8:25打卡,下午5:29打卡" + "\n" + "下午早退1分钟"); } } else { if (itemDate.month == 8 && itemDate.day > 25) { return; } tvDetail.setText("备注:" + "\n" + " " + itemDate.year + "年" + itemDate.month + "月" + itemDate.day + "日" + "--" + itemDate.weekDay + "\n" + "正常出勤。"); } } } // Toast.makeText( // RecordCalendar.this, // "选中的是" + itemDate.year + "年" + itemDate.month + "月" // + itemDate.day + "日" + "--" + itemDate.weekDay, // Toast.LENGTH_SHORT).show(); } } /** 按钮 */ class ImageViewClickListener implements OnClickListener { @Override public void onClick(View v) { switch (v.getId()) { case R.id.calendar_bar_iv_previours:// 上月 mDateEntityList.clear(); mNowCalendarPoint = mCalendarTool.getNowCalendar(); if (mNowCalendarPoint.x >= 1990 && mNowCalendarPoint.x < 2038) { if (mNowCalendarPoint.y - 1 <= 0) { mDateEntityList = mCalendarTool.getDateEntityList( mNowCalendarPoint.x - 1, 12); } else { mDateEntityList = mCalendarTool.getDateEntityList( mNowCalendarPoint.x, mNowCalendarPoint.y - 1); } mAdapter.setDateList(mDateEntityList); mAdapter.notifyDataSetChanged(); mNowCalendarPoint = mCalendarTool.getNowCalendar(); mCalendarTv.setText(mNowCalendarPoint.x + "年" + mNowCalendarPoint.y + "月"); } break; case R.id.calendar_bar_iv_next:// 下月 mNowCalendarPoint = mCalendarTool.getNowCalendar(); mDateEntityList.clear(); if (mNowCalendarPoint.x >= 1990 && mNowCalendarPoint.x < 2038) { if (mNowCalendarPoint.y + 1 > 12) { mDateEntityList = mCalendarTool.getDateEntityList( mNowCalendarPoint.x + 1, 1); } else { mDateEntityList = mCalendarTool.getDateEntityList( mNowCalendarPoint.x, mNowCalendarPoint.y + 1); } mAdapter.setDateList(mDateEntityList); mAdapter.notifyDataSetChanged(); mNowCalendarPoint = mCalendarTool.getNowCalendar(); mCalendarTv.setText(mNowCalendarPoint.x + "年" + mNowCalendarPoint.y + "月"); } break; } } } private void setListeners() { ivBack.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { MainActivity.this.finish(); } }); } @Override public void onDestroy() { super.onDestroy(); }}
模拟数据说明:在2015年8月25日(包含这天)前均有打卡记录,模拟每月21、22号请假,23、24号迟到或早退,其他工作日正常出勤。模拟数据仅作展示效果之用。
代码是最好的语言,再加上详细的注释,相信有一定Java编程基础和Android应用开发经验的人都能看懂。因为需要自己按照项目去扩展,本文只提供解决思路,希望读者不要懒惰,只有读懂本文的代码逻辑才能扩展出你所需的代码。
其他布局和图片样式等资源文件均在项目内可获取,附上源码地址:http://download.csdn.net/detail/daijin888888/9020535
下载需要资源分1分,整理不容易,还望理解。
请尊重原创,转载请附上原文地址,谢谢!
原文地址:http://blog.csdn.net/daijin888888/article/details/47752723
版权声明:本文为博主原创文章,未经博主允许不得转载。