我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android自定义日历控件实例详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android自定义日历控件实例详解

为什么要自定义控件

有时,原生控件不能满足我们对于外观和功能的需求,这时候可以自定义控件来定制外观或功能;有时,原生控件可以通过复杂的编码实现想要的功能,这时候可以自定义控件来提高代码的可复用性。

如何自定义控件

下面我通过我在github上开源的Android-CalendarView项目为例,来介绍一下自定义控件的方法。该项目中自定义的控件类名是CalendarView。这个自定义控件覆盖了一些自定义控件时常需要重写的一些方法。

构造函数

为了支持本控件既能使用xml布局文件声明,也可在java文件中动态创建,实现了三个构造函数。


public CalendarView(Context context, AttributeSet attrs, int defStyle);
public CalendarView(Context context, AttributeSet attrs);
public CalendarView(Context context);

可以在参数列表最长的第一个方法中写上你的初始化代码,下面两个构造函数调用第一个即可。


public CalendarView(Context context, AttributeSet attrs) {
 this(context, attrs, 0);
}
public CalendarView(Context context) {
 this(context, null);
}

那么在构造函数中做了哪些事情呢?

1 读取自定义参数

读取布局文件中可能设置的自定义属性(该日历控件仅自定义了一个mode参数来表示日历的模式)。代码如下。只要在attrs.xml中自定义了属性,就会自动创建一些R.styleable下的变量。

代码如下:TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CalendarView);
mode = typedArray.getInt(R.styleable.CalendarView_mode, Constant.MODE_SHOW_DATA_OF_THIS_MONTH);
然后附上res目录下values目录下的attrs.xml文件,需要在此文件中声明你自定义控件的自定义参数。


<?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="CalendarView">
  <attr name="mode" format="integer" />
 </declare-styleable>
</resources>

2 初始化关于绘制控件的相关参数

如字体的颜色、尺寸,控件各个部分尺寸。

3 初始化关于逻辑的相关参数

对于日历来说,需要能够判断对应于当前的年月,日历中的每个单元格是否合法,以及若合法,其表示的day的值是多少。未设定年月之前先用当前时间来初始化。实现如下。



private void initial() {
 int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
 int monthStart = -1;
 if(dayOfWeek >= 2 && dayOfWeek <= 7){
  monthStart = dayOfWeek - 2;
 }else if(dayOfWeek == 1){
  monthStart = 6;
 }
 curStartIndex = monthStart;
 date[monthStart] = 1;
 int daysOfMonth = daysOfCurrentMonth();
 for (int i = 1; i < daysOfMonth; i++) {
  date[monthStart + i] = i + 1;
 }
 curEndIndex = monthStart + daysOfMonth;
 if(mode == Constant.MODE_SHOW_DATA_OF_THIS_MONTH){
  Calendar tmp = Calendar.getInstance();
  todayIndex = tmp.get(Calendar.DAY_OF_MONTH) + monthStart - 1;
 }
}

其中date[]是一个整型数组,长度为42,因为一个日历最多需要6行来显示(6*7=42),curStartIndex和curEndIndex决定了date[]数组的合法下标区间,即前者表示该月的第一天在date[]数组的下标,后者表示该月的最后一天在date[]数组的下标。

4 绑定了一个OnTouchListener监听器

监听控件的触摸事件。

onMeasure方法

该方法对控件的宽和高进行测量。CalendarView覆盖了View类的onMeasure()方法,因为某个月的第一天可能是星期一到星期日的任何一个,而且每个月的天数不尽相同,因此日历控件的行数会有多变化,也导致控件的高度会有变化。因此需要根据当前的年月计算控件显示的高度(宽度设为屏幕宽度即可)。实现如下。


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(screenWidth, View.MeasureSpec.EXACTLY);
 heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(measureHeight(), View.MeasureSpec.EXACTLY);
 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

其中screenWidth是构造函数中已经获取的屏幕宽度,measureHeight()则是根据年月计算控件所需要的高度。实现如下,已经写了非常详细的注释。



private int measureHeight(){
 
 int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
 
 int daysOfMonth = daysOfCurrentMonth();
 
 int numberOfDaysExceptFirstLine = -1;
 if(dayOfWeek >= 2 && dayOfWeek <= 7){
  numberOfDaysExceptFirstLine = daysOfMonth - (8 - dayOfWeek + 1);
 }else if(dayOfWeek == 1){
  numberOfDaysExceptFirstLine = daysOfMonth - 1;
 }
 int lines = 2 + numberOfDaysExceptFirstLine / 7 + (numberOfDaysExceptFirstLine % 7 == 0 ? 0 : 1);
 return (int) (cellHeight * lines);
}

onDraw方法

该方法实现对控件的绘制。其中drawCircle给定圆心和半径绘制圆,drawText是给定一个坐标x,y绘制文字。



@Override
protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 
 float baseline = RenderUtil.getBaseline(0, cellHeight, weekTextPaint);
 for (int i = 0; i < 7; i++) {
 float weekTextX = RenderUtil.getStartX(cellWidth * i + cellWidth * 0.5f, weekTextPaint, weekText[i]);
 canvas.drawText(weekText[i], weekTextX, baseline, weekTextPaint);
 }
 if(mode == Constant.MODE_CALENDAR){
 for (int i = curStartIndex; i < curEndIndex; i++) {
 drawText(canvas, i, textPaint, "" + date[i]);
 }
 }else if(mode == Constant.MODE_SHOW_DATA_OF_THIS_MONTH){
 for (int i = curStartIndex; i < curEndIndex; i++) {
 if(i < todayIndex){
 if(data[date[i]]){
  drawCircle(canvas, i, bluePaint, cellHeight * 0.37f);
  drawCircle(canvas, i, whitePaint, cellHeight * 0.31f);
  drawCircle(canvas, i, blackPaint, cellHeight * 0.1f);
 }else{
  drawCircle(canvas, i, grayPaint, cellHeight * 0.1f);
 }
 }else if(i == todayIndex){
 if(data[date[i]]){
  drawCircle(canvas, i, bluePaint, cellHeight * 0.37f);
  drawCircle(canvas, i, whitePaint, cellHeight * 0.31f);
  drawCircle(canvas, i, blackPaint, cellHeight * 0.1f);
 }else{
  drawCircle(canvas, i, grayPaint, cellHeight * 0.37f);
  drawCircle(canvas, i, whitePaint, cellHeight * 0.31f);
  drawCircle(canvas, i, blackPaint, cellHeight * 0.1f);
 }
 }else{
 drawText(canvas, i, textPaint, "" + date[i]);
 }
 }
 }
}

需要说明的是,绘制文字时的这个x表示开始位置的x坐标(文字最左端),这个y却不是文字最顶端的y坐标,而应传入文字的baseline。因此若想要将文字绘制在某个区域居中部分,需要经过一番计算。本项目将其封装在了RenderUtil类中。实现如下。



public static float getBaseline(float top, float bottom, Paint paint){
 Paint.FontMetrics fontMetrics = paint.getFontMetrics();
 return (top + bottom - fontMetrics.bottom - fontMetrics.top) / 2;
}

public static float getStartX(float middle, Paint paint, String text){
 return middle - paint.measureText(text) * 0.5f;
}

自定义监听器

控件需要自定义一些监听器,以在控件发生了某种行为或交互时提供一个外部接口来处理一些事情。本项目的CalendarView提供了两个接口,OnRefreshListener和OnItemClickListener,均为自定义的接口。onItemClick只传了day一个参数,年和月可通过CalendarView对象的getYear和getMonth方法获取。


interface OnItemClickListener{
 void onItemClick(int day);
}
interface OnRefreshListener{
 void onRefresh();
}

先介绍一下两种mode,CalendarView提供了两种模式,第一种普通日历模式,日历每个位置简单显示了day这个数字,第二种本月计划完成情况模式,绘制了一些图形来表示本月的某一天是否完成了计划(模仿自悦跑圈,用一个圈表示本日跑了步)。

OnRefreshListener用于刷新日历数据后进行回调。两种模式定义了不同的刷新方法,都对OnRefreshListener进行了回调。refresh0用于第一种模式,refresh1用于第二种模式。



@Override
public void refresh0(int year, int month) {
 if(mode == Constant.MODE_CALENDAR){
 selectedYear = year;
 selectedMonth = month;
 calendar.set(Calendar.YEAR, selectedYear);
 calendar.set(Calendar.MONTH, selectedMonth - 1);
 calendar.set(Calendar.DAY_OF_MONTH, 1);
 initial();
 invalidate();
 if(onRefreshListener != null){
 onRefreshListener.onRefresh();
 }
 }
}

@Override
public void refresh1(boolean[] data) {
 
 if(mode == Constant.MODE_SHOW_DATA_OF_THIS_MONTH){
 calendar = Calendar.getInstance();
 selectedYear = calendar.get(Calendar.YEAR);
 selectedMonth = calendar.get(Calendar.MONTH) + 1;
 calendar.set(Calendar.DAY_OF_MONTH, 1);
 for(int i = 1; i <= daysOfCurrentMonth(); i++){
 if(i < data.length){
 this.data[i] = data[i];
 }else{
 this.data[i] = false;
 }
 }
 initial();
 invalidate();
 if(onRefreshListener != null){
 onRefreshListener.onRefresh();
 }
 }
}

OnItemClickListener用于响应点击了日历上的某一天这个事件。点击的判断在onTouch方法中实现。实现如下。在同一位置依次接收到ACTION_DOWN和ACTION_UP两个事件才认为完成了点击。


@Override
public boolean onTouch(View v, MotionEvent event) {
 float x = event.getX();
 float y = event.getY();
 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN:
 if(coordIsCalendarCell(y)){
 int index = getIndexByCoordinate(x, y);
 if(isLegalIndex(index)) {
  actionDownIndex = index;
 }
 }
 break;
 case MotionEvent.ACTION_UP:
 if(coordIsCalendarCell(y)){
 int actionUpIndex = getIndexByCoordinate(x, y);
 if(isLegalIndex(actionUpIndex)){
  if(actionDownIndex == actionUpIndex){
  actionDownIndex = -1;
  int day = date[actionUpIndex];
  if(onItemClickListener != null){
  onItemClickListener.onItemClick(day);
  }
  }
 }
 }
 break;
 }
 return true;
}

关于该日历控件

日历控件demo效果图如下,分别为普通日历模式和本月计划完成情况模式。

需要说明的是CalendarView控件部分只包括日历头与下面的日历,该控件上方的是其他控件,这里仅用作展示一种使用方法,你完全可以自定义这部分的样式。

此外,日历头的文字支持多种选择,比如周一有四种表示:一、周一、星期一、Mon。此外还有其他一些控制样式的接口,详情见源码:Android-CalendarView。

您可能感兴趣的文章:Android实现可滑动的自定义日历控件Android可签到日历控件的实现方法Android 一个日历控件的实现代码Android实现日历控件示例代码Android学习教程之日历控件使用(7)Android使用GridLayout绘制自定义日历控件Android日历控件的实现方法


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android自定义日历控件实例详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android自定义日历控件实例详解

为什么要自定义控件 有时,原生控件不能满足我们对于外观和功能的需求,这时候可以自定义控件来定制外观或功能;有时,原生控件可以通过复杂的编码实现想要的功能,这时候可以自定义控件来提高代码的可复用性。 如何自定义控件 下面我通过我在github
2022-06-06

Android如何自定义实现日历控件

这篇文章主要介绍Android如何自定义实现日历控件,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!具体内容如下1. Calendar类2. 布局创建calendar_layout.xml
2023-06-25

Android自定义控件样式实例详解

本文实例讲述了Android自定义控件样式的方法。分享给大家供大家参考,具体如下: Android控件样式自定义是用定义在drawable文件夹下的XML文件实现,在布局文件中通过设置控件的background属性达到效果。 一、控件常见状
2022-06-06

Android 自定义控件详解及实例代码

开发自定义控件的步骤:1、了解View的工作原理 2、 编写继承自View的子类 3、 为自定义View类增加属性 4、 绘制控件 5、 响应用户消息 6 、自定义回调函数 一、View结构原理Android系统的视图结构的设计也
2022-06-06

Android实现自定义日历

自定义日历控件,支持旧历、节气、日期标注、点击操作 (参考网络上的日历控件改写) 注:将下面的四张资源图片拷贝到所建包的下一个image目录中,如Calendar.java 所在包为 cc.util.android.view,则需要再创建一
2022-06-06

Android使用GridLayout绘制自定义日历控件

效果图思路:就是先设置Gridlayout的行列数,然后往里面放置一定数目的自定义日历按钮控件,最后实现日历逻辑就可以了。 步骤: 第一步:自定义日历控件(初步) 第二步:实现自定义单个日期按钮控件 第三步:将第二步得到的控件动态添加到第一
2022-06-06

Android自定义控件LinearLayout实例讲解

很多时候Android常用的控件不能满足我们的需求,那么我们就需要自定义一个控件了。今天做了一个自定义控件的实例,来分享下。 首先定义一个layout实现按钮内部布局: <
2022-06-06

Android自定义控件之日期选择控件使用详解

Android日期选择控件效果如下:调用的代码:@OnClick(R.id.btn0) public void btn0() { final AlertDialog dialog = new AlertDialog.Builder(cont
2023-05-31

android自定义view之实现日历界面实例

现在网上有很多自定义view实现日历的demo,今天讲一讲如何自己实现这个自定义view。 看一下最终效果图:在这个自定义view中,我使用了各种奇技淫巧的方法来实现这个日历,真是费尽心思。废话少说,开始进坑。 界面分析 头部是一个text
2022-06-06

详解Android自定义控件属性

在Android开发中,往往要用到自定义的控件来实现我们的需求或效果。在使用自定义 控件时,难免要用到自定义属性,那怎么使用自定义属性呢? 在文件res/values/下新建attrs.xml属性文件,中定义我们所需要的属性。
2022-06-06

Android自定义控件eBook实现翻书效果实例详解

本文实例讲述了Android自定义控件eBook实现翻书效果的方法。分享给大家供大家参考,具体如下: 效果图:Book.java文件:package com.book; import android.app.Activity; import
2022-06-06

android dialog自定义实例详解

本人工作有一个月多了。对于android很多东西,都有了新的了解或者说真正的掌握。为了让更多的像我这样的小白少走弯路,所以我会坚持将我在工作中遇到的一些比较令我印象深刻的知识点整合出来给大家(顺序是按照我工作到现在的时间来制作的,其实也是想
2022-06-06

Android——自定义控件(跑马灯实例)

说到自定义控件,自定义控件不仅能够让自己对控件的掌握更加熟悉,还可以实现很多功能,同时也可以节省出很多的时间! 人生中第一篇博客便是关于TextView跑马灯的实现,但是随着知识的增多,实现一些功能的方式方法也增多了起来,这里只是自定义控件
2022-06-06

Android自定义控件之小说书架实现示例详解

这篇文章主要为大家介绍了Android自定义控件之小说书架示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪方法
2023-05-16

Android自定义日历Calender代码实现

产品要做签到功能,签到功能要基于一个日历来进行,所以就根据 要求自定义了一个日历自定义控件相信做android都知道:(1)首先创建一个类,继承一个容器类或者是一个控件 (2)然后就是你需要设置的属性等的,在attrs文件夹中 (3)然后就
2022-06-06

Android实现自定义轮播图片控件详解

首先上效果图实现原理 要完成一个轮播图片,首先想到的应该是使用ViewPager来实现。ViewPager已经有了滑动的功能,我们只要让它自己滚动。再加上下方的小圆点就行了。所以我们本次的自定义控件就是由ViewPager和LinearLa
2022-06-06

android自定义开关控件-SlideSwitch的实例

iphone上有开关控件,很漂亮,其实android4.0以后也有switch控件,但是只能用在4.0以后的系统中,这就失去了其使用价值,而且我觉得它的界面也不是很好看。最近看到了百度魔拍上面的一个控件,觉得很漂亮啊,然后反编译了下,尽管没
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录