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

现在网上有很多自定义view实现日历的demo,今天讲一讲如何自己实现这个自定义view。

看一下最终效果图:

在这个自定义view中,我使用了各种奇技淫巧的方法来实现这个日历,真是费尽心思。废话少说,开始进坑。

界面分析

头部是一个textview,显示年份和月份,然后下边一行是星期几,这两行可以固定住,不随月份切换而进出屏幕。

再下边就是我们自定义view 的主角,每个月的天数。目前规定是星期日为每星期第一天。上个月的天数填充满第一行,下个月的前几天填充完最后一行,颜色设置为灰色,本月日期中的周一至周五设置为红色,周六周日设置为青色,特殊日期设置为绿色,并且在右上角填充特殊标识符,用四分之三的圆弧包裹(上个月和下个月的日期没有)。

此处还有个小细节,每月的总行数会不断改变,但是view的总高度并未改变,所以视觉效果会不一样。

构造方法

 public MyCalendar(Context context) {
    super(context);
  }

  public MyCalendar(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);

  }

主要是实现上面两个构造方法,第一个是用来在java代码中使用的,第二个是用来在xml布局文件中使用的。

暴露的接口

目前接口共有下面几个,setDate(CustomDate customDate),setWeekendHighLight(boolean b),setSpecialDay(int[] ints)

其中第一个是必须要设置的,否则是不会显示任何东西,第二个设置的是否周末高亮,第三个设置的是特殊显示的日期,第四个是设置是否可以点击前一个月或者后一个月的日期,默认为不设置,后期可以根据自己需求增加其他接口。

  /**
   * 暴露接口,设置日期
   *
   * @param customDate
   */
  public void setDate(CustomDate customDate) {
    Log.d(TAG, customDate.toString());
    this.date = customDate;
    firstDayOfWeek = date.getFirstDayOfWeek();
    Log.d(TAG, (date.getMonth() + 1) + "月1号是星期" + firstDayOfWeek);
    lastDayOfWeek = date.getLastDayOfWeek();
    lineCount = calculateLineNum() + 1;
    lastMonthTotalDays = date.getLastMonthDays();
  }

  /**
   * 暴露接口,设置是否周末高亮
   *
   * @param b
   */
  public void setWeekendHighLight(boolean b) {
    this.weekendHighlight = b;
  }

  public void setSpecialDay(int[] ints) {
    this.specialDays = ints;
  }

  /**
   * 暴露接口,设置是否可以点击前一个月和后一个月的日期
   *
   * @param b
   */
  public void setCanClickNextOrPreMonth(boolean b) {
    this.canClickNextOrPreMonth = b;
  }

在这里说明一下计算显示行数的方法,首先要注意我们获取的星期数与实际的星期几会有一个增加一天的问题,也就是当前是星期4,那么你获取的int将会是5.

 /**
   * 获得应该设置为多少行
   *
   * @return
   */
  private int calculateLineNum() {
    monthDaySum = date.getTotalDayOfMonth();
    return (firstDayOfWeek - 1 + monthDaySum) / 7;
  }

我们将第一天是星期几减去一后加上这个月总共多少天,就可以获得最后一天是在什么位置,然后除以七取商的整数部分,然后在进一法即可获得应该显示多少行。

onSizechanged方法

onSizechanged方法中已经可以获得显示的尺寸了,此时我们需要做一些工作:

 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    this.viewWidth = w;
    this.viewHeight = h;
    Log.d(TAG, "onSizeChanged" + w + h);
    cutGrid();
    init();
    setCellDay();
  }

首先是将宽和高引入进来,方便后边使用。

cutGrid()方法是将区域分割为行X列的格式。

init()方法初始化了一些画笔。

setCellDay()方法将每月的天对应过到坐标上。

首先看一下cutGrid()方法:

 /**
   * 切分为每天
   */
  private void cutGrid() {
    cellWidth = (float) viewWidth / ROW_COUNT;
    cellHeight = (float) viewHeight / lineCount;
    this.radius = Math.min(cellWidth / 2, cellHeight / 2);
    for (int i = 0; i < lineCount; i++) {
      for (int j = 0; j < ROW_COUNT; j++) {
        points.add(new PointF(cellWidth * j + cellWidth / 2, cellHeight * i + cellHeight / 2));
      }
    }
  }

cellWidth是每天的宽度,其中ROW_COUNT是一个常量7,表示每周7天;cellHeight是每行的高度,linecount是一个变量,需要我们根据日期计算,后边会说到;radius是我们绘制区域的半径,这个值是我们取宽度和高度中较小的值的一半。然后我们将每个方格中心坐标点利用双重循环放入一个List<Point> points中。

整个view被分割为如上的形状。

下面来看一下init()方法:

private void init() {
    circlePaint = new Paint();
    circlePaint.setStyle(Paint.Style.STROKE);
    circlePaint.setAntiAlias(true);
    circlePaint.setColor(Color.BLUE);
    textPaint = new Paint();
    textPaint.setAntiAlias(true);
    textPaint.setColor(Color.BLACK);
    textPaint.setTextSize(radius / 2);
    selectPaint = new Paint();
    selectPaint.setColor(Color.YELLOW);
    selectPaint.setAlpha(10);
    selectPaint.setAntiAlias(true);
    selectPaint.setStyle(Paint.Style.FILL);
    selectTextPaint = new Paint();
    selectTextPaint.setColor(Color.WHITE);
    selectTextPaint.setAntiAlias(true);
    selectTextPaint.setTextSize(radius / 2);
    selectTextPaint.setStyle(Paint.Style.FILL);
  }

基本都是画笔工具。

然后是setAllDays()方法:

 /**
   * 设置总共显示多少天,每天的状态
   */
  private void setCellDay() {
    cellDays = new CellDay[lineCount * ROW_COUNT];
    for (int i = 0, length = cellDays.length; i < length; i++) {
      cellDays[i] = new CellDay();
      cellDays[i].setPointX(points.get(i).x);
      cellDays[i].setPointY(points.get(i).y);
      if (firstDayOfWeek > 1 && i < firstDayOfWeek - 1) {
        cellDays[i].setDayState(DayState.LASTMONTH);
        cellDays[i].setDate(String.valueOf(lastMonthTotalDays - firstDayOfWeek + i + 2));
        cellDays[i].setCustomDate(new CustomDate(
            date.getYear(), date.getMonth() - 1, lastMonthTotalDays - firstDayOfWeek + i + 2));
      }
      if (i >= firstDayOfWeek - 1 && i < monthDaySum + firstDayOfWeek - 1) {

        cellDays[i].setDayState(CURRENTMONTH);
        cellDays[i].setDate(String.valueOf(i + 2 - firstDayOfWeek));
        cellDays[i].setCustomDate(new CustomDate(
            date.getYear(), date.getMonth(), i - firstDayOfWeek + 2));
        //设置周末高亮
        if (weekendHighlight) {
          if (i % 7 == 0 || i % 7 == 6) {
            cellDays[i].setDayState(WEEKEND);
          }
        }
      }
      if (i >= monthDaySum + firstDayOfWeek - 1) {
        cellDays[i].setDayState(NEXTMONTH);
        cellDays[i].setDate(String.valueOf(i - monthDaySum - firstDayOfWeek + 2));
        cellDays[i].setCustomDate(new CustomDate(
            date.getYear(), date.getMonth() + 1, i - monthDaySum - firstDayOfWeek + 2));
      }
      for (int j = 0, s = specialDays.length; j < s; j++) {
        if (specialDays[j] + firstDayOfWeek - 2 == i) {
          cellDays[i].setDayState(SPECIALDAY);
        }
      }
    }
  }

在这里我们用到了一个自定的类-CellDay。

CellDay有以下几个字段

    private String date;
    private DayState dayState;
    private CustomDate customDate;
    private float pointX;
    private float pointY;
    private boolean isSelected;

1、String date表示当前的日期。

2、dayState是一个美剧类型,定义了天的状态值。

  • LASTMONTH:上个月的日期
  • CURRENTMONTH:本月的日期
  • NEXTMONTH: 下个月的日期
  • CURRENTDAY: 今天的日期
  • WEEKEND:周末的日期
  • SPECIALDAY:用户自定义的可以设置状态的日期

其中可以设置多种状态,用法和SPECIALDAY基本一样。

  1. cusomedate是我们自己定义的一个工具类,包含项目中需要用到的一系列方法。
  2. pointX是横坐标。
  3. pointY是纵坐标。
  4. isSelceted表示有没有被选中。

CustomDate工具

public class CustomDate {
  private Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
  private int year;
  private int month;
  private int day;
  private int dayOfWeek;

  public CustomDate() {
  }

  /**
   * 获取当前的日期
   * @return
   */
  public CustomDate getCurrentDate() {
    this.year = calendar.get(Calendar.YEAR);
    this.month = calendar.get(Calendar.MONTH);
    this.day = calendar.get(Calendar.DAY_OF_MONTH);
    this.dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
    return new CustomDate(year, month, day);
  }

  public CustomDate(int year, int month, int day) {
    this.year = year;
    this.month = month;
    this.day = day;
    calendar.set(year, month, day);
    dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
  }

  /**
   * 获取上个月的天数
   * @return
   */
  public int getLastMonthDays() {
    return this.getDaysOfMonth(this.year, this.month - 1);
  }

  /**
   * 获取第一天是星期几
   *
   * @return
   */
  public int getFirstDayOfWeek() {
    calendar.set(this.year, this.month, 1);
    return calendar.get(Calendar.DAY_OF_WEEK);
  }

  /**
   * 获取最后一天是星期几
   *
   * @return
   */
  public int getLastDayOfWeek() {
    calendar.set(this.year, this.month, getTotalDayOfMonth());
    return calendar.get(Calendar.DAY_OF_WEEK);
  }

  /**
   * 获取这个月总共的天数
   * @return
   */
  public int getTotalDayOfMonth() {
    return this.getDaysOfMonth(year, month);
  }

  public int getTotalWeekOfMonth() {
    return calendar.getMaximum(Calendar.WEEK_OF_MONTH);
  }

  public int getYear() {
    return year;
  }

  public void setYear(int year) {
    this.year = year;
  }

  public int getMonth() {
    return month;
  }

  public void setMonth(int month) {
    this.month = month;
  }

  public int getDay() {
    return day;
  }

  public void setDay(int day) {
    this.day = day;
  }

  public int getDayOfWeek() {
    return dayOfWeek;
  }

  public void setDayOfWeek(int dayOfWeek) {
    this.dayOfWeek = dayOfWeek;
  }

  @Override
  public String toString() {
    return "CustomDate{" +
        "year=" + year +
        ", month=" + (getMonth() + 1) +
        ", day=" + day +
        ", dayOfWeek=" + dayOfWeek +
        '}';
  }

  /**
   * 获取年中每月的天数
   * @param year
   * @param month
   * @return
   */
  private int getDaysOfMonth(int year, int month) {
    if (month > 11) {
      month = 0;
      year += 1;
    } else if (month < 0) {
      month = 11;
      year -= 1;
    }

    int[] arr = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int daysOfMonth = 0;
    if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
      arr[1] = 29;
    }
    daysOfMonth = arr[month];
    return daysOfMonth;
  }
}

注释中对每个方法的说明已经非常清晰了。

int getLastMonthDays()

获取上个月的天数是用来计算上个月最后一天是星期几,然后以此推导出上个月在本月中显示的天数和对应的星期。

getFirstDayOfWeek()

获取本月第一天是星期几,然后排序本月的天数与对应的星期。

int getTotalDayOfMonth()

获取本月总共多少天。配合第一天是星期几用来计算总共分为几行,也就是确定linenumber。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android实现自定义日历

    自定义日历控件,支持旧历.节气.日期标注.点击操作 (参考网络上的日历控件改写) 注:将下面的四张资源图片拷贝到所建包的下一个image目录中,如Calendar.java 所在包为 cc.util.android.view,则需要再创建一个包cc.util.android.view.image 然后将图片拷贝进去 /****************从此出开始将代码拷贝到一个文件中*******************/ package cc.util.android.view; import

  • Android实现日历控件示例代码

    做的是一个酒店的项目,可以选择入住和离开的日期.声明为了省事在网上找的资料,自己修改的逻辑,希望对需要的朋友有帮助.喜欢的给个好评.谢谢啦!祝生活愉快! 先上图 第一步,搭建布局xml <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_w

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

    为什么要自定义控件 有时,原生控件不能满足我们对于外观和功能的需求,这时候可以自定义控件来定制外观或功能:有时,原生控件可以通过复杂的编码实现想要的功能,这时候可以自定义控件来提高代码的可复用性. 如何自定义控件 下面我通过我在github上开源的Android-CalendarView项目为例,来介绍一下自定义控件的方法.该项目中自定义的控件类名是CalendarView.这个自定义控件覆盖了一些自定义控件时常需要重写的一些方法. 构造函数 为了支持本控件既能使用xml布局文件声明,也可在ja

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

    效果图 思路:就是先设置Gridlayout的行列数,然后往里面放置一定数目的自定义日历按钮控件,最后实现日历逻辑就可以了. 步骤: 第一步:自定义日历控件(初步) 第二步:实现自定义单个日期按钮控件 第三步:将第二步得到的控件动态添加到第一步的布局中,并实现日期逻辑 第四步:编写单个日期点击监听器接口 第一步:自定义日历控件(初步) <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmln

  • Android自定义控件实现可多选课程日历CalendarView

    可多选课程日历CalendarView的效果图 开发环境 IDE版本:AndroidStudio2.0 物理机版本:Win7旗舰版(64位) 前言 最近的项目中用到了一个课程选择的日历View,于是在网上搜了搜自定义日历View,发现基本上都是单选的,不能够满足项目中的需求.于是自己重新造了个轮子,写了个可以被多选的自定义日历View.最后面会给出GitHub地址. 代码实现 package widget; import android.content.Context; import andro

  • android 开发教程之日历项目实践(一)

    前言:决定开始学习 Android 平台下的软件开发,以日历作为实践项目,进行一周后,基本完成. 为了总结及笔记,并给有需要的朋友借鉴,开始整理本教程. 开始之前: 在编写程序之前,需要进行项目设计,因为是练习项目,主要是确定软件 UI 界面,这是已经完成的屏幕截图: 对这个画面,进一步作分解: 这里总共分解为三个 View 文件: 1:activity_main.xml 作为启动的主画面,新建项目时,首先生成. 2:view_calendar_table.xml 定义月历视图,头部固定,其它行

  • java制作android 日历代码分享

    代码很简单,就不多废话了 复制代码 代码如下: //读取日历事件     public static void getCalendarInfo(Activity activity,String tag){         String[] projection = new String[]{CalendarContract.Events._ID,CalendarContract.Events.TITLE};         ContentResolver cr = activity.getCon

  • android 开发教程之日历项目实践(二)

    一.创建 Android Project 在新建对话框中输入 App 属性,SDK版本全部选最新的,不作版本兼容.主题选择 Holo Dark. 下一步,使用默认设置 下一步,使用默认设置 下一步,使用默认配置 下一步,使用默认设置 创建完成后的初始画面 在上面的步骤中,我们选择了创建 MainActivity,ADT 帮我们在 src 目录下生成了 MainActivity.java 文件,在 res/layout/ 目录下生成了 activity_main.xml 文件,并在编辑窗口打开,如

  • Android自定义日历Calender代码实现

    产品要做签到功能,签到功能要基于一个日历来进行,所以就根据 要求自定义了一个日历 自定义控件相信做android都知道: (1)首先创建一个类,继承一个容器类或者是一个控件 (2)然后就是你需要设置的属性等的,在attrs文件夹中 (3)然后就是在类里边进行属性的设置以及布局等等功能的添加 其实自定义一个日历问题都不多,很多人都会想到通过一个gridView然后填充就可以,确实是这样,主要是在显示每个月的第一天的位置以及每个月显示多少天有点绕. 思路:通过判断当前星期几然后进行日历的填充,但是填

  • Android使用GridView实现日历的简单功能

    简单的日历实现,只是显示了每一个月,没有显示当天和记事这些功能 主要是计算月初是周几,月末是周几,然后相应的显示上一月多少天和下一月多少天. 先看一下关于日期的用到的几个工具类 /** * 获取该月天数 */ public static int getCurrentMonthDay(long millSec) { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(millSec); calendar.set(

随机推荐