您的位置:首页 > 移动开发 > Android开发

android自定义日历并添加事件

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



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



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

CalendarView:

[java] view
plaincopy

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 day

public 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);

}

@Override

protected 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();

}

@Override

protected 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;

/*

*

* 触摸事件为了确定点击的位置日期

*/

@Override

public 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();

}

//切换style

public 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:

[java] view
plaincopy

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:

[java] view
plaincopy

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;

}

@Override

public void finishUpdate(View arg0) {

}

@Override

public void notifyDataSetChanged() {

super.notifyDataSetChanged();

}

@Override

public int getCount() {

return Integer.MAX_VALUE;

}

@Override

public 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];

}

@Override

public boolean isViewFromObject(View arg0, Object arg1) {

return arg0 == (arg1);

}

@Override

public Parcelable saveState() {

return null;

}

@Override

public void destroyItem(View arg0, int arg1, Object arg2) {

// TODO Auto-generated method stub

}

@Override

public void startUpdate(View arg0) {

}

public V[] getAllItems() {

return views;

}

}

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

CalendarViewPagerLisenter:

[java] view
plaincopy

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();

}

@Override

public 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;

}

@Override

public void onPageScrolled(int arg0, float arg1, int arg2) {

}

@Override

public void onPageScrollStateChanged(int arg0) {

}

enum SildeDirection {

RIGHT, LEFT, NO_SILDE;

}

}

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



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

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

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

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

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

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

转载网址:http://blog.csdn.net/huangyanbin123/article/details/38350213
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐