返回顶部
首页 > 资讯 > 移动开发 >Android自定义日历控件实例详解
  • 918
分享到

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

Android 2022-06-06 08:06:18 918人浏览 泡泡鱼
摘要

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

为什么要自定义控件

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

如何自定义控件

下面我通过我在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日历控件的实现方法


--结束END--

本文标题: Android自定义日历控件实例详解

本文链接: https://lsjlt.com/news/24859.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Android自定义日历控件实例详解
    为什么要自定义控件 有时,原生控件不能满足我们对于外观和功能的需求,这时候可以自定义控件来定制外观或功能;有时,原生控件可以通过复杂的编码实现想要的功能,这时候可以自定义控件来...
    99+
    2022-06-06
    Android
  • Android自定义实现日历控件
    本文实例为大家分享了Android自定义实现日历控件的具体代码,供大家参考,具体内容如下 1. Calendar类 2. 布局 创建calendar_layout.xml <...
    99+
    2024-04-02
  • Android如何自定义实现日历控件
    这篇文章主要介绍Android如何自定义实现日历控件,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!具体内容如下1. Calendar类2. 布局创建calendar_layout.xml<LinearLayou...
    99+
    2023-06-25
  • Android自定义控件样式实例详解
    本文实例讲述了Android自定义控件样式的方法。分享给大家供大家参考,具体如下: Android控件样式自定义是用定义在drawable文件夹下的XML文件实现,在布局文件中...
    99+
    2022-06-06
    Android
  • Android 自定义控件详解及实例代码
    开发自定义控件的步骤: 1、了解View的工作原理 2、 编写继承自View的子类 3、 为自定义View类增加属性 4、 绘制控件 5、 响应用户消息 6 、自定义...
    99+
    2022-06-06
    自定义 自定义控件 Android
  • Android实现自定义日历
    自定义日历控件,支持旧历、节气、日期标注、点击操作 (参考网络上的日历控件改写) 注:将下面的四张资源图片拷贝到所建包的下一个image目录中,如Calendar.java 所...
    99+
    2022-06-06
    自定义 Android
  • Android使用GridLayout绘制自定义日历控件
    效果图 思路:就是先设置Gridlayout的行列数,然后往里面放置一定数目的自定义日历按钮控件,最后实现日历逻辑就可以了。 步骤: 第一步:自定义日历控件(初步) 第二步:...
    99+
    2022-06-06
    自定义 gridlayout Android
  • Vue自定义日历小控件使用方法详解
    本文实例为大家分享了Vue自定义日历小控件的具体代码,供大家参考,具体内容如下 废话少说,先上效果图: 可以在效果图中看到,选择不同的月份的时候当月天数与星期几都是一一对应,非当月...
    99+
    2024-04-02
  • Android自定义控件LinearLayout实例讲解
    很多时候Android常用的控件不能满足我们的需求,那么我们就需要自定义一个控件了。今天做了一个自定义控件的实例,来分享下。 首先定义一个layout实现按钮内部布局:&nbs...
    99+
    2022-06-06
    Android
  • Android自定义控件之日期选择控件使用详解
    Android日期选择控件效果如下:调用的代码:@OnClick(R.id.btn0) public void btn0() { final AlertDialog dialog = new AlertDialog.Builder(cont...
    99+
    2023-05-31
    android 日期 控件
  • android自定义view之实现日历界面实例
    现在网上有很多自定义view实现日历的demo,今天讲一讲如何自己实现这个自定义view。 看一下最终效果图: 在这个自定义view中,我使用了各种奇技淫巧的方法来实现这个日...
    99+
    2022-06-06
    view 界面 Android
  • 详解Android自定义控件属性
    在Android开发中,往往要用到自定义的控件来实现我们的需求或效果。在使用自定义 控件时,难免要用到自定义属性,那怎么使用自定义属性呢? 在文件res/values/下新建...
    99+
    2022-06-06
    属性 Android
  • Android自定义日历效果
    要实现Android自定义日历效果,可以按照以下步骤进行:1. 创建一个自定义的CalendarView控件,继承自ViewGrou...
    99+
    2023-08-15
    Android
  • Android自定义控件eBook实现翻书效果实例详解
    本文实例讲述了Android自定义控件eBook实现翻书效果的方法。分享给大家供大家参考,具体如下: 效果图: Book.java文件: package com.book...
    99+
    2022-06-06
    Android
  • Android——自定义控件(跑马灯实例)
    说到自定义控件,自定义控件不仅能够让自己对控件的掌握更加熟悉,还可以实现很多功能,同时也可以节省出很多的时间! 人生中第一篇博客便是关于Text...
    99+
    2022-06-06
    自定义 自定义控件 跑马灯 Android
  • android dialog自定义实例详解
    本人工作有一个月多了。对于android很多东西,都有了新的了解或者说真正的掌握。为了让更多的像我这样的小白少走弯路,所以我会坚持将我在工作中遇到的一些比较令我印象深刻的知识点...
    99+
    2022-06-06
    dialog Android
  • Android自定义日历Calender代码实现
    产品要做签到功能,签到功能要基于一个日历来进行,所以就根据 要求自定义了一个日历 自定义控件相信做android都知道: (1)首先创建一个类,继承一个容器类或者是一个控...
    99+
    2022-06-06
    Android
  • Android Studio简单实现自定义日历
    本文实例为大家分享了Android Studio自定义日历的具体代码,供大家参考,具体内容如下 效果图: 目录树 1.DayBean.java用来存储每天的信息 package ...
    99+
    2024-04-02
  • Android自定义控件之小说书架实现示例详解
    目录前言功能分析代码实现小说的展示小说拖拽BookShelfItemTouchHelper实现小说删除总结前言 在手机看小说的时候,看到一个很有意思的效果,在UC浏览器切换到小说书架...
    99+
    2023-05-16
    Android自定义控件小说书架 Android自定义控件
  • Android实现自定义轮播图片控件详解
    首先上效果图 实现原理 要完成一个轮播图片,首先想到的应该是使用ViewPager来实现。ViewPager已经有了滑动的功能,我们只要让它自己滚动。再加上下方的小圆点就行了...
    99+
    2022-06-06
    轮播图 自定义 图片 轮播 Android
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作