android爱哥的日历,踏进爱哥的仓库:DatePicker

DatePicker前传

不知什么时候,AigeStudio侵入了我们这群android开发者的内心深处,有着一群不知姓什名谁的人xx(不想用小明指代,比较喜欢xx),不断地发出歇斯底里的叫声:

122412445.png

然而,在某天某时某刻,XX遇到需求如下:

商户可以通过app手动选择指定时间段/一些天数进行统计分析。

这一刻来临了,XX人蒙了,找不到头绪了,开始进各个群展开撒网战术,可怜兮兮的等待着群友的回复,总有那么些乐于助人又是Aige的死忠粉,会回复一句:爱哥开源的DatePicker可以实现,然而xx还想问”DatePicker在哪里啊,谁有下载链接啊“…找到了解决办法,XX终于可以安心睡觉了,此时此刻,我只想对xx说:你对Aige爱得不够深沉

DatePicker实战

效果图:

122412446.gif

依赖导入

compile 'cn.aigestudio.datepicker:DatePicker:2.2.0'

DatePicker直接在xml布局使用,代码进行相关的初始化配置监听设置等。(setData方法必须调用进行初始化)

......

DatePicker datePicker = (DatePicker) findViewById(R.id.datePicker);

datePicker.setDate(mYear, mMonth);

datePicker.setOnDateSelectedListener(new DatePicker.OnDateSelectedListener() {

@Override

public void onDateSelected(List date) {

}

});

......

DatePicker支持多种模式:单选、多选、正常模式,具体定义如下

public enum DPMode {

SINGLE, MULTIPLE, NONE

}

DatePicker不同的模式通过不同的监听回调事件,获取选中的时间值

setOnDateSelectedListener // 多选监听

setOnDatePickedListener // 单选监听

选择后的日期将会以列表(多选模式下)或字符串(单选模式下)的形式返回,日期字符串的格式为:

格式:yyyy-M-d

示例:2015-3-28

DatePicker有一套自己的默认UI,默认的Color配置,如果你使用Module导入工程,可以直接修改默认配置,关联类如下

122412447.png

DatePicker默认天朝的节日背景色

122412448.png

当然你也可以自己修改定制,同时还可以为特殊节日添加浮标,首先添加指定需要修改号数集,添加到缓存到对应map集合

List tmpTR = new ArrayList<>();

tmpTR.add("2015-10-10");

tmpTR.add("2015-10-11");

tmpTR.add("2015-10-12");

tmpTR.add("2015-10-13");

tmpTR.add("2015-10-14");

tmpTR.add("2015-10-15");

tmpTR.add("2015-10-16");

DPCManager.getInstance().setDecorTR(tmpTR);

private static final HashMap> DECOR_CACHE_BG = new HashMap<>();

private static final HashMap> DECOR_CACHE_TL = new HashMap<>();

private static final HashMap> DECOR_CACHE_T = new HashMap<>();

private static final HashMap> DECOR_CACHE_TR = new HashMap<>();

private static final HashMap> DECOR_CACHE_L = new HashMap<>();

private static final HashMap> DECOR_CACHE_R = new HashMap<>();

完成上述步骤后,再通过setDPDecor方法绘制对应号数的浮标

picker.setDPDecor(new DPDecor() {

@Override

public void drawDecorTL(Canvas canvas, Rect rect, Paint paint, String data) {

super.drawDecorTL(canvas, rect, paint, data);

switch (data) {

case "2015-10-5":

case "2015-10-7":

case "2015-10-9":

case "2015-10-11":

paint.setColor(Color.GREEN);

canvas.drawRect(rect, paint);

break;

default:

paint.setColor(Color.RED);

canvas.drawCircle(rect.centerX(), rect.centerY(), rect.width() / 2, paint);

break;

}

}

@Override

public void drawDecorTR(Canvas canvas, Rect rect, Paint paint, String data) {

super.drawDecorTR(canvas, rect, paint, data);

switch (data) {

case "2015-10-10":

case "2015-10-11":

case "2015-10-12":

paint.setColor(Color.BLUE);

canvas.drawCircle(rect.centerX(), rect.centerY(), rect.width() / 2, paint);

break;

default:

paint.setColor(Color.YELLOW);

canvas.drawRect(rect, paint);

break;

}

}

});

绘制的浮标可以是不同形状不同颜色,具体怎么关联到月视图稍后再看,绘制的浮标可以是不同方向,具体使用参考DPDecor类的定义。(具体使用参考上面代码块实例)

122412449.png

绘制浮标效果如下图:

122412450.png

更为详尽的使用请参考Aige官方示例:https://github.com/AigeStudio/DatePicker

DataPicker源码浅析

122412451.png

calendars包内部提供方法都有中文注释,相信都能看懂,关于一些节日号数相关基础数据的获取判断,内部的一些算法,表示无能为力(我算法白痴),DPDecor定义方法上面有提到,具体使用不详解。

122412452.png

抽象类DPLManager是一个语言管理方面的,该库支持中英文的日历,根据CH标示采取不同的实现

122412453.png

theme模块关于UI配置方面的,上面有提到,具体代码设置color,请参考源码,头有中文注释一目了然。

日历数据实体,封装日历绘制时需要的数据(封装按照二维数组,每个月可能存在的行列进行封装)

public class DPInfo {

public String strG, strF;

public boolean isHoliday;

public boolean isToday, isWeekend;

public boolean isSolarTerms, isFestival, isDeferred;

public boolean isDecorBG;

public boolean isDecorTL, isDecorT, isDecorTR, isDecorL, isDecorR;

}

122412454.png

utils包里面就一个dp px的转换和一维数组转二维数组

整个库的UI核心是下面两个类

DatePicker

MonthView

DatePicker自定义的Layout容器,构造函数先添加标题栏显示控件和星期-到星期日的显示视图,最后添加月视图

public DatePicker(Context context, AttributeSet attrs) {

super(context, attrs);

//..............略过些许方法................

mTManager = DPTManager.getInstance();

mLManager = DPLManager.getInstance();

// 设置排列方向为竖向

setOrientation(VERTICAL);

// 标题栏根布局

RelativeLayout rlTitle = new RelativeLayout(context);

// 周视图根布局

LinearLayout llWeek = new LinearLayout(context);

// 标题栏子元素布局参数

RelativeLayout.LayoutParams lpYear =

new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);

// --------------------------------------------------------------------------------标题栏

// 年份显示

tvYear = new TextView(context);

// 月份显示

tvMonth = new TextView(context);

tvMonth.setText("六月");

// 确定显示

tvEnsure = new TextView(context);

tvEnsure.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

if (null != onDateSelectedListener) {

onDateSelectedListener.onDateSelected(monthView.getDateSelected());

}

}

});

rlTitle.addView(tvYear, lpYear);

rlTitle.addView(tvMonth, lpMonth);

rlTitle.addView(tvEnsure, lpEnsure);

addView(rlTitle, llParams);

// --------------------------------------------------------------------------------周视图

for (int i = 0; i < mLManager.titleWeek().length; i++) {

TextView tvWeek = new TextView(context);

tvWeek.setText(mLManager.titleWeek()[i]);

tvWeek.setGravity(Gravity.CENTER);

tvWeek.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);

tvWeek.setTextColor(mTManager.colorTitle());

llWeek.addView(tvWeek, lpWeek);

}

addView(llWeek, llParams);

// ------------------------------------------------------------------------------------月视图

monthView = new MonthView(context);

monthView.setOnDateChangeListener(new MonthView.OnDateChangeListener() {

@Override

public void onMonthChange(int month) {

tvMonth.setText(mLManager.titleMonth()[month - 1]);

}

@Override

public void onYearChange(int year) {

String tmp = String.valueOf(year);

if (tmp.startsWith("-")) {

tmp = tmp.replace("-", mLManager.titleBC());

}

tvYear.setText(tmp);

}

});

addView(monthView, llParams);

}

设置日历选择模式如果支持多选则显示确定按钮

/**

* 设置日期选择模式

*

*@param mode ...

*/

public void setMode(DPMode mode) {

if (mode != DPMode.MULTIPLE) {

tvEnsure.setVisibility(GONE);

}

monthView.setDPMode(mode);

}

提供多个set方法,本质是调用了月视图View进行set,特别是对于DatePicker的监听绑定做了判断。Aige的使用文档是这样说的

122412455.png

MonthView默认多选模式,在没有设置模式的情况下直接设置单选监听会抛出异常,反之同理

/**

* 设置单选监听器

*

*@param onDatePickedListener ...

*/

public void setOnDatePickedListener(OnDatePickedListener onDatePickedListener) {

if (monthView.getDPMode() != DPMode.SINGLE) {

throw new RuntimeException(

"Current DPMode does not SINGLE! Please call setMode set DPMode to SINGLE!");

}

monthView.setOnDatePickedListener(onDatePickedListener);

}

/**

* 设置多选监听器

*

*@param onDateSelectedListener ...

*/

public void setOnDateSelectedListener(OnDateSelectedListener onDateSelectedListener) {

if (monthView.getDPMode() != DPMode.MULTIPLE) {

throw new RuntimeException(

"Current DPMode does not MULTIPLE! Please call setMode set DPMode to MULTIPLE!");

}

this.onDateSelectedListener = onDateSelectedListener;

}

MonthView月视图自定义控件,内部构造函数初始化一个Scroller实例用于辅助滑动,一个缩放动画监听(单选或者多选模式下,选中某一项会有个缩放动画的圆圈背景)

public MonthView(Context context) {

super(context);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {

scaleAnimationListener = new ScaleAnimationListener();

}

mScroller = new Scroller(context);

mPaint.setTextAlign(Paint.Align.CENTER);

}

computeScroll:主要功能是计算拖动的位移量更新UI,重写computeScroll()的原因,调用startScroll()是不会有滚动效果的,只有在computeScroll()获取滚动情况,参考Scroller计算,做出滚动的响应,computeScroll在父控件执行drawChild时,会调用这个方法.要知道计算有没有终止,需要通过mScroller.computeScrollOffset()

@Override

public void computeScroll() {

if (mScroller.computeScrollOffset()) {

scrollTo(mScroller.getCurrX(), mScroller.getCurrY());

invalidate();

} else {

requestLayout()

}

}

onTouch方法内判断滑动事件,如果还在滑动,down时结束滑动,并记录位置,MOVE时判断滑动距离,设置滑动方向mSlideMode (内部定义枚举类型),根据不同的Mode和计算出的距离,调用Scroller.startScroll方法配合computescroll实现滑动视图。而Touch的up事件如果造成月视图切换,可能月份年份的变化,这时候会调用到computeDate方法,该方法判断后根据条件执行回调,而回调函数在DatePicker内部实现了局部更新UI

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

mScroller.forceFinished(true);

mSlideMode = null;

isNewEvent = true;

lastPointX = (int) event.getX();

lastPointY = (int) event.getY();

break;

case MotionEvent.ACTION_MOVE:

if (isNewEvent) {

if (Math.abs(lastPointX - event.getX()) > 100) {

mSlideMode = SlideMode.HOR;

isNewEvent = false;

} else if (Math.abs(lastPointY - event.getY()) > 50) {

mSlideMode = SlideMode.VER;

isNewEvent = false;

}

}

if (mSlideMode == SlideMode.HOR) {

int totalMoveX = (int) (lastPointX - event.getX()) + lastMoveX;

smoothScrollTo(totalMoveX, indexYear * height);

} else if (mSlideMode == SlideMode.VER) {

int totalMoveY = (int) (lastPointY - event.getY()) + lastMoveY;

smoothScrollTo(width * indexMonth, totalMoveY);

}

break;

case MotionEvent.ACTION_UP:

if (mSlideMode == SlideMode.VER) {

if (Math.abs(lastPointY - event.getY()) > 25) {

if (lastPointY < event.getY()) {

if (Math.abs(lastPointY - event.getY()) >= criticalHeight) {

indexYear--;

centerYear = centerYear - 1;

}

} else if (lastPointY > event.getY()) {

if (Math.abs(lastPointY - event.getY()) >= criticalHeight) {

indexYear++;

centerYear = centerYear + 1;

}

}

buildRegion();

computeDate();

smoothScrollTo(width * indexMonth, height * indexYear);

lastMoveY = height * indexYear;

} else {

defineRegion((int) event.getX(), (int) event.getY());

}

} else if (mSlideMode == SlideMode.HOR) {

if (Math.abs(lastPointX - event.getX()) > 25) {

if (lastPointX > event.getX() &&

Math.abs(lastPointX - event.getX()) >= criticalWidth) {

indexMonth++;

centerMonth = (centerMonth + 1) % 13;

if (centerMonth == 0) {

centerMonth = 1;

centerYear++;

}

} else if (lastPointX < event.getX() &&

Math.abs(lastPointX - event.getX()) >= criticalWidth) {

indexMonth--;

centerMonth = (centerMonth - 1) % 12;

if (centerMonth == 0) {

centerMonth = 12;

centerYear--;

}

}

buildRegion();

computeDate();

smoothScrollTo(width * indexMonth, indexYear * height);

lastMoveX = width * indexMonth;

} else {

defineRegion((int) event.getX(), (int) event.getY());

}

} else {

defineRegion((int) event.getX(), (int) event.getY());

}

break;

}

return true;

}

至于上面的defineRegion方法我也只了解一点(计算真心看不懂),基于v11版本区分单选模式和多选模式,执行缩放动画,加减速差值器效果(圆圈背景缩放),动画结束执行对应的回调函数,至于多选的值缓存方式使用的是List,个人认为还有更好的方案,具体参考AbsListView(看了别打我~~(>_

private List dateSelected = new ArrayList<>();

至于圆形背景怎么来,以前博客提过很多次,不再逼逼叨叨了

小结

Aige就是Aige,看完这个库收获良多!今后要加强算法方面的学习了,闰年判断都差不多忘了,时光飞逝,遥想当年学java,一个闰年判断的java的demo分分钟的事,现在还得百度一下!!!

DatePicker番外

一天,群里来了新人,漂亮的MM,在IT行业mm就是国宝啊,更何况是PL妹纸,xx纷纷跳出来要求爆照

妹纸羞答答的说:“你们先告诉我DatePicker怎么选择多个日期,我就发”

xx就开始逼逼叨叨先聊着,都没说到正点,此时一个非常活跃的oo告诉mm:”爱哥知道,你问他吧“

……….

………

………逼逼叨叨结束

妹子来了,Aige献身了:“setMode(DPMode.MULTIPLE);”

而后不再言语,事了拂衣去,深藏身与名,PL妹纸都还不知道Aige就是爱哥!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值