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

可多选课程日历CalendarView的效果图

开发环境

IDE版本:AndroidStudio2.0
物理机版本:Win7旗舰版(64位)

前言

最近的项目中用到了一个课程选择的日历View,于是在网上搜了搜自定义日历View,发现基本上都是单选的,不能够满足项目中的需求。于是自己重新造了个轮子,写了个可以被多选的自定义日历View。最后面会给出GitHub地址。

代码实现

package widget;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;

import com.arisaid.calendarview.R;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

/**
 * Created by zhouyou on 2016/7/25.
 * Class desc:
 *
 * 自定义日历View,可多选
 */
public class CalendarView extends View {

  // 列的数量
  private static final int NUM_COLUMNS  =  7;
  // 行的数量
  private static final int NUM_ROWS    =  6;

  /**
   * 可选日期数据
   */
  private List<String> mOptionalDates;

  /**
   * 以选日期数据
   */
  private List<String> mSelectedDates = new ArrayList<>();

  // 背景颜色
  private int mBgColor = Color.parseColor("#F7F7F7");
  // 天数默认颜色
  private int mDayNormalColor = Color.parseColor("#0070F8");
  // 天数不可选颜色
  private int mDayNotOptColor = Color.parseColor("#CBCBCB");
  // 天数选择后颜色
  private int mDayPressedColor = Color.WHITE;
  // 天数字体大小
  private int mDayTextSize = 14;
  // 是否可以被点击状态
  private boolean mClickable = true;

  private DisplayMetrics mMetrics;
  private Paint mPaint;
  private int mCurYear;
  private int mCurMonth;
  private int mCurDate;

  private int mSelYear;
  private int mSelMonth;
  private int mSelDate;
  private int mColumnSize;
  private int mRowSize;
  private int[][] mDays;

  // 当月一共有多少天
  private int mMonthDays;
  // 当月第一天位于周几
  private int mWeekNumber;
  // 已选中背景Bitmap
  private Bitmap mBgOptBitmap;
  // 未选中背景Bitmap
  private Bitmap mBgNotOptBitmap;

  public CalendarView(Context context) {
    super(context);
    init();
  }

  public CalendarView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }

  public CalendarView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
  }

  private void init() {
    // 获取手机屏幕参数
    mMetrics = getResources().getDisplayMetrics();
    // 创建画笔
    mPaint = new Paint();
    // 获取当前日期
    Calendar calendar = Calendar.getInstance();
    mCurYear  =  calendar.get(Calendar.YEAR);
    mCurMonth  =  calendar.get(Calendar.MONTH);
    mCurDate  =  calendar.get(Calendar.DATE);
    setSelYTD(mCurYear, mCurMonth, mCurDate);

    // 获取背景Bitmap
    mBgOptBitmap  = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_bg_course_optional);
    mBgNotOptBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_bg_course_not_optional);
  }

  @Override
  public void invalidate() {
    // 避免程序过度绘制
    if(hasWindowFocus()) super.invalidate();
  }

  @Override
  protected void onDraw(Canvas canvas) {
    initSize();

    // 绘制背景
    mPaint.setColor(mBgColor);
    canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mPaint);

    mDays = new int[6][7];
    // 设置绘制字体大小
    mPaint.setTextSize(mDayTextSize * mMetrics.scaledDensity);
    // 设置绘制字体颜色

    String dayStr;
    // 获取当月一共有多少天
    mMonthDays = DateUtils.getMonthDays(mSelYear, mSelMonth);
    // 获取当月第一天位于周几
    mWeekNumber = DateUtils.getFirstDayWeek(mSelYear, mSelMonth);

    for(int day = 0; day < mMonthDays; day++){
      dayStr = String.valueOf(day + 1);
      int column = (day + mWeekNumber - 1) % 7;
      int row   = (day + mWeekNumber - 1) / 7;
      mDays[row][column] = day + 1;
      int startX = (int) (mColumnSize * column + (mColumnSize - mPaint.measureText(dayStr)) / 2);
      int startY = (int) (mRowSize * row + mRowSize / 2 - (mPaint.ascent() + mPaint.descent()) / 2);

      // 判断当前天数是否可选
      if(mOptionalDates.contains(getSelData(mSelYear, mSelMonth, mDays[row][column]))){
        // 可选,继续判断是否是点击过的
        if(!mSelectedDates.contains(getSelData(mSelYear, mSelMonth, mDays[row][column]))){
          // 没有点击过,绘制默认背景
          canvas.drawBitmap(mBgNotOptBitmap, startX - 22, startY - 55, mPaint);
          mPaint.setColor(mDayNormalColor);
        }else{
          // 点击过,绘制点击过的背景
          canvas.drawBitmap(mBgOptBitmap, startX - 22, startY - 55, mPaint);
          mPaint.setColor(mDayPressedColor);
        }
        // 绘制天数
        canvas.drawText(dayStr, startX, startY - 10, mPaint);
      }else{
        mPaint.setColor(mDayNotOptColor);
        canvas.drawText(dayStr, startX, startY, mPaint);
      }
    }
  }

  private int downX = 0,downY = 0;

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    int eventCode = event.getAction();
    switch(eventCode){
      case MotionEvent.ACTION_DOWN:
        downX = (int) event.getX();
        downY = (int) event.getY();
        break;
      case MotionEvent.ACTION_MOVE:
        break;
      case MotionEvent.ACTION_UP:
        if(!mClickable) return true;

        int upX = (int) event.getX();
        int upY = (int) event.getY();
        if(Math.abs(upX - downX) < 10 && Math.abs(upY - downY) < 10){
          performClick();
          onClick((upX + downX) / 2, (upY + downY) / 2);
        }
        break;
    }
    return true;
  }

  /**
   * 点击事件
   */
  private void onClick(int x, int y){
    int row = y / mRowSize;
    int column = x / mColumnSize;
    setSelYTD(mSelYear, mSelMonth, mDays[row][column]);

    // 判断是否点击过
    boolean isSelected = mSelectedDates.contains(getSelData(mSelYear, mSelMonth, mSelDate));
    if(isSelected){
      mSelectedDates.remove(getSelData(mSelYear, mSelMonth, mSelDate));
    }else{
      mSelectedDates.add(getSelData(mSelYear, mSelMonth, mSelDate));
    }

    invalidate();
    if(mListener != null){
      // 执行回调
      mListener.onClickDateListener(mSelYear, (mSelMonth + 1), mSelDate);
    }
  }

  /**
   * 初始化列宽和高
   */
  private void initSize() {
    // 初始化每列的大小
    mColumnSize = getWidth() / NUM_COLUMNS;
    // 初始化每行的大小
    mRowSize = getHeight() / NUM_ROWS;
  }

  /**
   * 设置可选择日期
   * @param dates 日期数据
   */
  public void setOptionalDate(List<String> dates){
    this.mOptionalDates = dates;
  }

  /**
   * 设置年月日
   * @param year 年
   * @param month 月
   * @param date 日
   */
  public void setSelYTD(int year, int month, int date){
    this.mSelYear  =  year;
    this.mSelMonth =  month;
    this.mSelDate  =  date;
  }

  /**
   * 设置上一个月日历
   */
  public void setLastMonth(){
    int year  =  mSelYear;
    int month  =  mSelMonth;
    int day   =  mSelDate;
    // 如果是1月份,则变成12月份
    if(month == 0){
      year = mSelYear-1;
      month = 11;
    }else if(DateUtils.getMonthDays(year, month) == day){
      // 如果当前日期为该月最后一点,当向前推的时候,就需要改变选中的日期
      month = month-1;
      day = DateUtils.getMonthDays(year, month);
    }else{
      month = month-1;
    }
    setSelYTD(year,month,day);
    invalidate();
  }

  /**
   * 设置下一个日历
   */
  public void setNextMonth(){
    int year  =  mSelYear;
    int month  =  mSelMonth;
    int day   =  mSelDate;
    // 如果是12月份,则变成1月份
    if(month == 11){
      year = mSelYear+1;
      month = 0;
    }else if(DateUtils.getMonthDays(year, month) == day){
      // 如果当前日期为该月最后一点,当向前推的时候,就需要改变选中的日期
      month = month + 1;
      day = DateUtils.getMonthDays(year, month);
    }else{
      month = month + 1;
    }
    setSelYTD(year,month,day);
    invalidate();
  }

  /**
   * 获取当前展示的年和月份
   * @return 格式:2016-06
   */
  public String getDate(){
    String data;
    if((mSelMonth + 1) < 10){
      data = mSelYear + "-0" + (mSelMonth + 1);
    }else{
      data = mSelYear + "-" + (mSelMonth + 1);
    }
    return data;
  }

  /**
   * 获取当前展示的日期
   * @return 格式:20160606
   */
  private String getSelData(int year, int month, int date){
    String monty, day;
    month = (month + 1);

    // 判断月份是否有非0情况
    if((month) < 10) {
      monty = "0" + month;
    }else{
      monty = String.valueOf(month);
    }

    // 判断天数是否有非0情况
    if((date) < 10){
      day = "0" + (date);
    }else{
      day = String.valueOf(date);
    }
    return year + monty + day;
  }

  /**
   * 获取已选日期数据
   */
  public List<String> getSelectedDates(){
    return mSelectedDates;
  }

  /**
   * 设置已选日期数据
   */
  public void setSelectedDates(List<String> dates){
    this.mSelectedDates = dates;
  }

  /**
   * 设置日历是否可以点击
   */
  @Override
  public void setClickable(boolean clickable) {
    this.mClickable = clickable;
  }

  private OnClickListener mListener;

  public interface OnClickListener{
    void onClickDateListener(int year, int month, int day);
  }

  /**
   * 设置点击回调
   */
  public void setOnClickDate(OnClickListener listener){
    this.mListener = listener;
  }

  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    recyclerBitmap(mBgOptBitmap);
    recyclerBitmap(mBgNotOptBitmap);
  }

  /**
   * 释放Bitmap资源
   */
  private void recyclerBitmap(Bitmap bitmap) {
    if(bitmap != null && !bitmap.isRecycled()){
      bitmap.recycle();
    }
  }
}

使用步骤

1、初始化自定义日历View:

CalendarView mCalendarView = (CalendarView) findViewById(R.id.calendarView);

2、初始化可以被选择的天数数据:

List<String> mDatas = new ArrayList<>();
mDatas.add("20160801");
mDatas.add("20160802");
mDatas.add("20160803");
mDatas.add("20160816");
mDatas.add("20160817");
mDatas.add("20160826");
mDatas.add("20160910");
mDatas.add("20160911");
mDatas.add("20160912");

3、设置给自定义日历View:

// 设置可选日期
mCalendarView.setOptionalDate(mDatas);

设置点击监听

mCalendarView.setOnClickDate(new CalendarView.OnClickListener() {
  @Override
  public void onClickDateListener(int year, int month, int day) {
    Toast.makeText(getApplication(), year + "年" + month + "月" + day + "天", Toast.LENGTH_SHORT).show();

    // 获取已选择日期
    List<String> dates = mCalendarView.getSelectedDates();
    for (String date : dates) {
      Log.e("test", "date: " + date);
    }

  }
});

如果只需要进行数据展示,而不需要点击,可以设置:

// 设置已选日期
mCalendarView.setSelectedDates(mDatas);
// 设置不可以被点击
mCalendarView.setClickable(false);

源码下载:

GitHub地址:https://github.com/Airsaid/CalendarView 欢迎star~!

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

(0)

相关推荐

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

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

  • Android自定义可标记日历效果

    先直接看看效果吧 初始情况 点击一个作为标记 再次点击后删除 3.这里还要感谢前辈的代码作为参考,毕竟以前也没有写过关于日历方面的东西,别人确实写得不错,我在原基础上加入了数据库操作等补充,以完成自己实际需求,作为尊重首先给出原作者的连接 就是这里–>Android自定义控件实现可多选课程日历CalendarView 4.然后贴出来关于数据库操作的代码,给大家作为参考 DatabaseHelper .java 这是关于简单数据库操作的部分 package com.xugongming38.edi

  • Android实现自定义日历

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

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

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

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

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

  • 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 开发教程之日历项目实践(二)

    一.创建 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 开发教程之日历项目实践(一)

    前言:决定开始学习 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

随机推荐