上一篇文章中讲过的内容有两点需要复习一下:

划分每天占的方格。这个首先是计算本月当前日期总天数加上第一天是星期几然后减去1然后对应的上月的天数即可算出本月赢有多少行,通过行列的分割计算出每天的方格中心坐标点。

为显示的每天设定标记。目前我们做的标记有上一个月、本月、下一个月、周末、特殊日期。

我们将上面两点获得的信息全部保存在一个数组对象中,这个数组对象就是我们用来绘制的基准。

绘制日历界面

view的绘制都是在onDraw方法中,在ondraw方法中会传递一个Canvas参数,这个就是我们需要绘制的画布。在这里我们需要绘制出所有的天的状态。

在绘制的过程中,我们需要根据每天的标记(State)来分情况绘制,所以此处要做一个便利循环来去除出每天的内容:

private void drawDays(Canvas canvas, CellDay[] cellDays) {

for (CellDay c : cellDays) {

switch (c.getDayState()) {

case LASTMONTH:

if (c.isSelected()) {

circlePaint.setColor(Color.TRANSPARENT);

} else {

circlePaint.setColor(Color.TRANSPARENT);

textPaint.setColor(Color.GRAY);

}

break;

case CURRENTMONTH:

if (c.isSelected()) {

circlePaint.setColor(Color.YELLOW);

textPaint.setColor(Color.BLACK);

} else {

circlePaint.setColor(Color.RED);

textPaint.setColor(Color.RED);

canvas.drawText("班",

c.getPointX() + textPaint.measureText(tempDate) / 2,

c.getPointY() - textPaint.getTextSize() / 2,

textPaint);

}

break;

case NEXTMONTH:

if (c.isSelected()) {

circlePaint.setColor(Color.TRANSPARENT);

} else {

circlePaint.setColor(Color.TRANSPARENT);

textPaint.setColor(Color.GRAY);

}

break;

case CURRENTDAY:

circlePaint.setColor(Color.YELLOW);

textPaint.setColor(Color.BLACK);

break;

case WEEKEND:

circlePaint.setColor(Color.CYAN);

textPaint.setColor(Color.CYAN);

canvas.drawText("休",

c.getPointX() + textPaint.measureText(tempDate) / 2,

c.getPointY() - textPaint.getTextSize() / 2,

textPaint);

break;

case SPECIALDAY:

circlePaint.setColor(Color.GREEN);

textPaint.setColor(Color.GREEN);

canvas.drawText("假",

c.getPointX() + textPaint.measureText(tempDate) / 2,

c.getPointY() - textPaint.getTextSize() / 2,

textPaint);

}

canvas.drawCircle(tempPositionX, tempPositionY, radius - 10, selectPaint);

// canvas.drawText(tempDate,

// tempPositionX - textPaint.measureText(tempDate) / 2,

// tempPositionY + textPaint.getTextSize() / 2,

// selectTextPaint);

canvas.drawText(c.getDate(),

c.getPointX() - textPaint.measureText(c.getDate()) / 2,

c.getPointY() + textPaint.getTextSize() / 2,

textPaint);

// canvas.drawCircle(c.getPointX(), c.getPointY(), radius - 10, circlePaint);

//这个地方改为rectf可以向下兼容

canvas.drawArc(c.getPointX() - radius + 10,

c.getPointY() - radius + 10,

c.getPointX() + radius - 10,

c.getPointY() + radius - 10,

0, 270, false, circlePaint);

c.setSelected(false);

oldPositionX = newPositionX;

oldPositionY = newPositionY;

}

}

对于是否选中的状态我们采用的是特殊的标记,所以没有在siwtch中直接使用,而是通过if来细分每个switch的状态。在LASTMONTH、NEXTMONTH中我们对画笔做了透明处理,这样做的目的是防止在选中的状态下选中状态是透明的,那么也就不会产生视觉高亮效果。(后面会讲如何处理上个月和下个月不被选中)。

case SPECIALDAY:

circlePaint.setColor(Color.GREEN);

textPaint.setColor(Color.GREEN);

canvas.drawText("假",

c.getPointX() + textPaint.measureText(tempDate) / 2,

c.getPointY() - textPaint.getTextSize() / 2,

textPaint);

在SPECIALDAY中我们直接绘制了特殊日期显示的文字。当然此处可以传任意参数进来作为绘制文字,在后期的改进中我会将这个作为接口供使用者调用。

canvas.drawText(c.getDate(),

c.getPointX() - textPaint.measureText(c.getDate()) / 2,

c.getPointY() + textPaint.getTextSize() / 2,

textPaint);

// canvas.drawCircle(c.getPointX(), c.getPointY(), radius - 10, circlePaint);

//这个地方改为rectf可以向下兼容

canvas.drawArc(c.getPointX() - radius + 10,

c.getPointY() - radius + 10,

c.getPointX() + radius - 10,

c.getPointY() + radius - 10,

0, 270, false, circlePaint);

这段代码是用来绘制日期的数字和一个半圆的效果。

c.setSelected(false);

这段代码的作用是每次选中后清除选中状态,防止下次同时选中两个或者多个。此处可以延伸,作为网上较流行的选中多日期的状态表示。

至此,我们的界面基本完成了。看一下效果图:

本月的绘制完美完成。

设置点击事件

此处我们需要重写public boolean onTouchEvent(MotionEvent event)方法。

此处我们假定自定义view的事件不会被viewgroup拦截,则触摸事件的顺序依次为:

MotionEvent.ACTION_DOWN->MotionEvent.ACTION_MOVE->MotionEvent.ACTION_UP

我发现我特别懒,所以我只计算了DOWN和UP事件之间移动的距离

case MotionEvent.ACTION_DOWN:

touchRawX = event.getX();

touchRawY = event.getY();

break;

记录原始按下的点的坐标位置。

case MotionEvent.ACTION_UP:

Log.d(TAG, "MotionEvent:ACTION_UP");

Log.d(TAG, "rawY= " + touchRawY + ",touchY= " + event.getY());

float touchX = event.getX();

float touchY = event.getY();

if (touchRawY - touchY < -200) {

//下滑事件

Log.d(TAG, "下滑事件");

setMaxView();

setCellDay();

this.clearCanvas = false;

invalidate();

cutGrid();

init();

this.layout(0, 0, this.viewWidth, this.viewHeight / 2);

setCellDay();

invalidate();

}

if (touchRawY - touchY > 200) {

//上划事件

Log.d(TAG, "上划事件");

setMinView();

this.clearCanvas = true;

invalidate();

}

if (Math.abs(touchRawX - touchX) < 100 && Math.abs(touchY - touchRawY) < 100) {

//点击事件

Log.d(TAG, "点击事件");

int touchRow = (int) (touchX / cellWidth);

int touchLine = (int) (touchY / cellHeight);

final int touchId = touchLine * ROW_COUNT + touchRow;

if (canClickNextOrPreMonth) {

setClickEvent(touchId);

} else {

if (touchId > firstDayOfWeek - 2 && touchId < monthDaySum + firstDayOfWeek - 1) {

setClickEvent(touchId);

tempCellDay = cellDays[touchId];

tempState = cellDays[touchId].getDayState();

cellDays[touchId].setSelected(true);

newPositionX = cellDays[touchId].getPointX();

newPositionY = cellDays[touchId].getPointY();

setselectAnimator(touchId);

}

}

}

break;

在up事件中,首先根据触摸的位置换算为touchId,也就是在数组对象cellDays中的索引。然后做了三种状态判断,上滑下滑和点击事件,其中上滑和下滑事件可以先不管,因为到最后你们都不会用到。讲一下点击事件中的内容,首先判断是不是设置了上月或者下月是否可以点击,canClickNextOrPreMonth为真,则可以点击,为假则不可以点击。为假的时候,我们只需要再设置一个条件,即点击的坐标点没有落在上月或下月的索引内。

先看如何设置点击事件:

private void setClickEvent(int touchId) {

CustomDate customDate = cellDays[touchId].getCustomDate();

if (clickCellListener != null) {

clickCellListener.onClickCell(customDate);

}

}

在这个方法中传进了一个int值,这个值就是cellday数组的索引值。

只要实现接口即可获得这个传出的customDate。

最后看setSelectAnimator方法:

private void setselectAnimator(final int touchId) {

selectAnimatorX.removeAllUpdateListeners();

selectAnimatorX.setFloatValues(oldPositionX, newPositionX);

selectAnimatorX.setDuration(300);

selectAnimatorX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

tempPositionX = (float) animation.getAnimatedValue();

}

});

selectAnimatorY.removeAllUpdateListeners();

selectAnimatorY.setFloatValues(oldPositionY, newPositionY);

selectAnimatorY.setDuration(300);

selectAnimatorY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

tempPositionY = (float) animation.getAnimatedValue();

postInvalidate();

}

});

animatorSet = new AnimatorSet();

animatorSet.playTogether(selectAnimatorX, selectAnimatorY);

animatorSet.start();

}

这个方法看我文章的应该都很熟悉了,做一个valueanimator来作为世间引擎不断引起XY坐标的变化,产生一个动画移动的效果。然后调用inalidate作为标记,刷新界面。

此时看一下效果图:

至此,我们日历的点击、选中状态改变、特殊日期、周末等高亮显示都已经完成。

滑动至上一个月和下一个月

我是利用了viewpager来实现滑动,我假定大家都对viewpager的使用很擅长。此处只看一下代码即可:

private class CalendarAdapter extends PagerAdapter {

private MyCalendar calendar;

private LayoutParams lp;

public CalendarAdapter(MyCalendar calendar) {

this.calendar = calendar;

}

public CalendarAdapter(MyCalendar calendar, LayoutParams lp) {

this.calendar = calendar;

this.lp = lp;

}

@Override

public int getCount() {

return 1000;

}

@Override

public boolean isViewFromObject(View view, Object object) {

return view == object;

}

@Override

public void destroyItem(ViewGroup container, int position, Object object) {

}

@Override

public Object instantiateItem(ViewGroup container, int position) {

if (myCalendars.size() > position) {

MyCalendar myCalendar = myCalendars.get(position);

if (myCalendar != null) {

return myCalendar;

}

}

calendar = new MyCalendar(context);

calendar.setClickCellListener(MyViewPager.this);

customeDate = new CustomDate(position / 12 + 2000, position % 12, 1);

calendar.setDate(customeDate);

calendar.setLayoutParams(lp);

calendar.setWeekendHighLight(weekendHightLight);

calendar.setSpecialDay(new int[]{10, 20});

calendar.setCanClickNextOrPreMonth(false);

while (myCalendars.size() <= position) {

myCalendars.add(null);

}

myCalendars.set(position, calendar);

ViewParent vp = calendar.getParent();

if (vp != null) {

ViewGroup parent = (ViewGroup) vp;

parent.removeView(calendar);

}

container.addView(calendar);

return calendar;

}

}

这里是前一个取巧的方式,我设定的是1000个月,从2000年开始计算。然后根据position来计算是哪个月,customeDate = new CustomDate(position / 12 + 2000, position % 12, 1);

就是这段代码,然后将calendar的接口全都设置了一下。

再来看一下如何使用:

viewPager.setCurrentItem((calendar.get(Calendar.YEAR) - 2000) * 12 + calendar.get(Calendar.MONTH));

这个设置当前日期我并未封装到calendar中,正在不断改进。

再为viewpager添加一个ViewPager.OnPageChangeListener监听器。

public void onPageSelected(int position) {

StringBuilder builder = new StringBuilder();

builder.append(position / 12 + 2000);

builder.append("年");

builder.append(position % 12 + 1);

builder.append("月");

date.setText(builder.toString());

}

在这里设置显示的头的日期。

好了,所有的工作完成了。

讲的不是很好,思路基本都是这样实现的,有问题可以直接联系我。

android日历界面的实现,自定义view之实现日历界面(二)相关推荐

  1. Android软件开发之盘点自定义View界面大合集(二)

    Android软件开发之盘点自定义View界面大合集(二) - 雨松MOMO的程序世界 - 51CTO技术博客 雨松MOMO带大家盘点Android 中的自定义View界面的绘制 今天我用自己写的一个 ...

  2. Android仿IOS滑动关机-自定义view系列(6)

    Android仿IOS滑动关机-自定义view系列 功能简介 GIf演示 主要实现步骤-具体内容看github项目里的代码 Android技术生活交流 更多其他页面-自定义View-实用功能合集:点击 ...

  3. Android中实现Bitmap在自定义View中的放大与拖动

    一基本实现思路: 基于View类实现自定义View –MyImageView类.在使用View的Activity类中完成OnTouchListener接口,实现对MotionEvent事件的监听与处理 ...

  4. Android 雪花飘落动画效果 自定义View

    在码农的世界里,优美的应用体验,来源于程序员对细节的处理以及自我要求的境界,年轻人也是忙忙碌碌的码农中一员,每天.每周,都会留下一些脚印,就是这些创作的内容,有一种执着,就是不知为什么,如果你迷茫,不 ...

  5. Carson带你学Android:源码解析自定义View Draw过程

    前言 自定义View是Android开发者必须了解的基础 网上有大量关于自定义View原理的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化 等 今天,我将全面总结自定义View ...

  6. android 仿360浮动,Android仿360悬浮小球自定义view实现示例

    Android仿360悬浮小球自定义view实现示例 效果图如下: 实现当前这种类似的效果 和360小球 悬浮桌面差不错类似.这种效果是如何实现的呢.废话不多说 ,直接上代码. 1.新建工程,添加悬浮 ...

  7. android app自动更新界面_Android自定义view之模仿登录界面文本输入框(华为云APP)...

    好久不见!!!!!,最近终于挤出时间来更新文章了,废话不多说,直接开始. 效果图如下: 01 分析 1.组合多个控件完成此输入框静态效果 2.hint值上浮下潜动画 3.一些功能 02 步骤 01 自 ...

  8. Android自定义view之模仿登录界面文本输入框(华为云APP)

    6.其他一些小知识点 1.将光标移到最后 //将光标移到最后 edittext.setSelection(edittext.getText().toString().length()); 2.将键盘中 ...

  9. android 行布局选择器,『自定义View实战』—— 银行种类选择器

    在工作中难免遇到自定义 View 的相关需求,本身这方面比较薄弱,因此做个记录,也是自己学习和成长的积累.自定义View实战 前言 年前的最后一个开发需求,将之前H5开卡界面转变成native.意思就 ...

最新文章

  1. 当root.sh与ORA-15031相遇
  2. vb和vb.net事件机制
  3. 武侠乂服务器位置在哪,武侠乂手游秘境在哪里 地图秘境宝藏分布位置大全
  4. python 加密解密_python加密解密
  5. linux c之通过管道父子进程实现同步通信
  6. github private链接访问_将github配置为图床+PicGo配置
  7. OpenCV-基本图形绘制(圆、矩形、椭圆)
  8. php mysql mysql_set_charset()._PHP:MySQL函数mysql_set_charset()的用法
  9. MUI框架开发HTML5手机APP(一)--搭建第一个手机APP(转)
  10. Hibernate的双向N-N关联(六)
  11. 网页自动填表html,风越网页表单批量自动填写工具
  12. 举例 微积分 拉格朗日方程_(完整word版)拉格朗日方程的应用及举例08讲
  13. python定时备份为知笔记数据
  14. Typescript详解
  15. nopi word to html,C# 基于NPOI+Office COM组件 实现20行代码在线预览文档(word,excel,pdf,txt,png)...
  16. java正整数分解因数_java将一个正整数分解质因数
  17. 初阶数据结构 初识二叉树
  18. 基于Python摄影图片分享系统设计与实现 开题报告
  19. 计算机义务维修活动方案,最新义务维修活动策划书
  20. 预见2019吴晓波年终秀演讲PPT整理

热门文章

  1. 深圳软件测试培训:测试当中用到的性能指标
  2. IBM3650M4实体机安装ESXI7.0
  3. Android修改设置文字转语音输出,默认语速
  4. 安卓那些你不得不收藏的开源库
  5. Codeforces Raif Round 1 (Div. 1 + Div. 2) E. Carrots for Rabbits(优先队列+贪心)
  6. 网狐、6878、名字修改图片文字路径【第四次更新】
  7. Python经典问题——身体指数BMI
  8. 《A Transformer-based joint-encoding for Emotion Recognition andSentiment Analysis》论文翻译
  9. Python 算法训练之摩斯电码
  10. 估计的商是什么意思_《商》字意思读音、组词解释及笔画数 - 新华字典 - 911查询...