Android 自定义日历

好久没来写博客了,这半年多发生了好多的事情,废话不多说,今天在公司里比较闲在,写一篇最近写的公司用到的控件——日历控件。

控件的功能比较少,根据需求只有选择开始时间和结束时间并返回时间段。

效果图如下:

Android 自定义日历Android 自定义日历Android 自定义日历

第一张图是正常状态下,第二张图是选中了一个开始日期的状态 第三张图片是选择了结束日期。该日历记录的是当前时间的前一个月的时间。

下面为代码:

 import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View; import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List; import playrtc.huanhong.com.test.R; public class CustomDateView extends View { private static final float TITLE_HEIGHT = 200;
private float cellWidth;
private float cellHeight;
private int date[][] = new int[6][7];
private int measuredHeight;
private Point mStartDatePoint;
private Point mEndDatePoint;
private Point mShowMonthPoint;
private Point mLastDayPoint; private Paint mNormalTextPaint;
private Paint mTitlePaint;
private Paint mTitleTextPaint;
private Paint mToadyTextPaint;
private Paint mIndicatorPaint;
private Paint mCirclePaintBg;
private Paint mRectPaintBg;
private Paint mCheckedPaint; private float mTitleSize = 60;
private float mDateSize = 50;
private float mIndicatorSize = 30; private int mTitleBackgroundColor = 0xff047dfe;
private int mTitleTextColor = 0xffffffff;
private int mDateTextColor = 0xff000000;
private int mCheckedTextColor = 0xffffffff;
private int mTodayColor = 0xff047dfe;
private int mIndicatorColor = 0xff000000;
private int mCircleColor = 0xFFff8900;
private int mRectColor = 0xFFffedd9; private float mWeekHeight;
private String mDateStr;
private String mCurrentMonthStr; private String[] mWeeks = {"日", "一", "二", "三", "四", "五", "六"};
private String[] mMonths = {"一月", "二月", "三月", "四月", "" +
"五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"};
private int mCurrentYear;
private int mCurrentMonth;
private int mCurrentWeek;
private int mCurrentDay; private OnCheckedListener mOnCheckedListener; public CustomDateView(Context context) {
this(context, null);
} public CustomDateView(Context context, AttributeSet attrs) {
super(context, attrs);
// setCurrentDate(System.currentTimeMillis());
init(attrs);
} private void init(AttributeSet attr) { TypedArray typedArray = getContext().obtainStyledAttributes(attr, R.styleable.CustomDateView); try {
mTitleSize = typedArray.getDimension(R.styleable.CustomDateView_title_text_size, mTitleSize);
mDateSize = typedArray.getDimension(R.styleable.CustomDateView_date_text_size, mDateSize);
mIndicatorSize = typedArray.getDimension(R.styleable.CustomDateView_indicator_text_size, mIndicatorSize); mTitleBackgroundColor = typedArray.getColor(R.styleable.CustomDateView_title_background_color, mTitleBackgroundColor);
mTitleTextColor = typedArray.getColor(R.styleable.CustomDateView_title_text_color, mTitleTextColor);
mDateTextColor = typedArray.getColor(R.styleable.CustomDateView_date_text_color, mDateTextColor);
mCheckedTextColor = typedArray.getColor(R.styleable.CustomDateView_checked_text_color, mCheckedTextColor);
mTodayColor = typedArray.getColor(R.styleable.CustomDateView_today_text_color, mTodayColor);
mIndicatorColor = typedArray.getColor(R.styleable.CustomDateView_indicator_text_color, mIndicatorColor);
mCircleColor = typedArray.getColor(R.styleable.CustomDateView_checked_circle_color, mCircleColor);
mRectColor = typedArray.getColor(R.styleable.CustomDateView_checked_background_color, mRectColor);
} catch (Exception e) {
e.printStackTrace();
} finally {
typedArray.recycle();
} //上部蓝色
mTitlePaint = new Paint();
mTitlePaint.setAntiAlias(true);
mTitlePaint.setColor(mTitleBackgroundColor); //上部文字
mTitleTextPaint = new Paint();
mTitleTextPaint.setColor(mTitleTextColor);
mTitleTextPaint.setAntiAlias(true);
mTitleTextPaint.setTextSize(mTitleSize);
mTitleTextPaint.setTextAlign(Align.CENTER); //其他文字
mNormalTextPaint = new Paint();
mNormalTextPaint.setColor(mDateTextColor);
mNormalTextPaint.setTextAlign(Align.CENTER);
mNormalTextPaint.setAntiAlias(true);
mNormalTextPaint.setTextSize(mDateSize); //选中文字
mCheckedPaint = new Paint();
mCheckedPaint.setColor(mCheckedTextColor);
mCheckedPaint.setTextAlign(Align.CENTER);
mCheckedPaint.setAntiAlias(true);
mCheckedPaint.setTextSize(mDateSize); //今天显示的字体
mToadyTextPaint = new Paint();
mToadyTextPaint.setAntiAlias(true);
mToadyTextPaint.setTextAlign(Align.CENTER);
mToadyTextPaint.setColor(mTodayColor);
mToadyTextPaint.setTextSize(mDateSize); //交界处显示的字体
mIndicatorPaint = new Paint();
mIndicatorPaint.setAntiAlias(true);
mIndicatorPaint.setTextAlign(Align.CENTER);
mIndicatorPaint.setColor(mIndicatorColor);
mIndicatorPaint.setTextSize(mIndicatorSize); //圆形
mCirclePaintBg = new Paint();
mCirclePaintBg.setColor(mCircleColor);
mCirclePaintBg.setAntiAlias(true); //矩形
mRectPaintBg = new Paint();
mRectPaintBg.setColor(mRectColor);
mRectPaintBg.setAntiAlias(true);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
cellWidth = (getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) / date[0].length;
mWeekHeight = cellWidth;
cellHeight = (cellWidth + mIndicatorSize);
measuredHeight = (int) (cellHeight * (date.length + 1) + TITLE_HEIGHT + getPaddingBottom());
setMeasuredDimension(widthMeasureSpec, measuredHeight);
} @Override
protected void onDraw(Canvas canvas) {
drawRect(canvas);
drawCircle(canvas); mTitlePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawRect(0, 0, getMeasuredWidth(), TITLE_HEIGHT, mTitlePaint); Rect rect = new Rect();
Rect rectWeek = new Rect(); if (!TextUtils.isEmpty(mDateStr)) {
mTitleTextPaint.getTextBounds(mDateStr, 0, mDateStr.length(), rect);
canvas.drawText(mDateStr, getMeasuredWidth() / 2, TITLE_HEIGHT / 2 + rect.height() / 2,
mTitleTextPaint);
} drawWeekText(canvas, rectWeek); drawDateText(canvas, rect); if (!TextUtils.isEmpty(mCurrentMonthStr)) {
mIndicatorPaint.getTextBounds(mCurrentMonthStr, 0, mCurrentMonthStr.length(), rect);
} if (mShowMonthPoint != null) {
canvas.drawText(mCurrentMonthStr,
mShowMonthPoint.x * cellWidth + cellWidth / 2 + getPaddingLeft(),
(mShowMonthPoint.y + 1) * cellHeight + mWeekHeight + rect.height() / 4 + TITLE_HEIGHT, mIndicatorPaint);
}
// //竖线
// for (int i = 0; i <= date[0].length; i++) {
// canvas.drawLine(i * cellWidth + getPaddingLeft(), TITLE_HEIGHT, i * cellWidth + getPaddingLeft(), measuredHeight - getPaddingBottom(), mNormalTextPaint);
// }
//
// //横线
// for (int i = 0; i < date.length; i++) {
// canvas.drawLine(getPaddingLeft(), i * cellHeight + TITLE_HEIGHT + mWeekHeight, getMeasuredWidth() - getPaddingRight(),
// i * cellHeight + mWeekHeight + TITLE_HEIGHT, mNormalTextPaint);
// }
} /**
* @param canvas 画布
* @param rectWeek 测量文字长宽矩形
*/
private void drawWeekText(Canvas canvas, Rect rectWeek) {
int maxHeight = 0;
//获得最高的文字高度
for (int k = 0; k < mWeeks.length; k++) {
mNormalTextPaint.getTextBounds(mWeeks[k], 0, mWeeks[k].length(), rectWeek);
if (rectWeek.height() > maxHeight) {
maxHeight = rectWeek.height();
}
} for (int k = 0; k < mWeeks.length; k++) {
canvas.drawText(mWeeks[k],
k * cellWidth + cellWidth / 2 + getPaddingLeft(),
mWeekHeight / 2 + maxHeight / 2 + TITLE_HEIGHT, mNormalTextPaint);
}
} /**
* @param canvas 画布
* @param rect 测量文字长宽矩形
*/
private void drawDateText(Canvas canvas, Rect rect) {
for (int i = 0; i < date.length; i++) {
for (int j = 0; j < date[0].length; j++) { mNormalTextPaint.getTextBounds(date[i][j] + "", 0, String.valueOf(date[i][j]).length(), rect); if (date[i][j] != 0) { if (i == mLastDayPoint.x && j == mLastDayPoint.y) {
canvas.drawText(date[i][j] + "",
j * cellWidth + cellWidth / 2 + getPaddingLeft(),
i * cellHeight + cellHeight / 2 + mWeekHeight + rect.height() / 2 + TITLE_HEIGHT, mToadyTextPaint);
} else { if ((mStartDatePoint != null && i == mStartDatePoint.y && j == mStartDatePoint.x) || (mEndDatePoint != null && i == mEndDatePoint.y && j == mEndDatePoint.x)) {
canvas.drawText(date[i][j] + "",
j * cellWidth + cellWidth / 2 + getPaddingLeft(),
i * cellHeight + cellHeight / 2 + mWeekHeight + rect.height() / 2 + TITLE_HEIGHT, mCheckedPaint);
} else {
canvas.drawText(date[i][j] + "",
j * cellWidth + cellWidth / 2 + getPaddingLeft(),
i * cellHeight + cellHeight / 2 + mWeekHeight + rect.height() / 2 + TITLE_HEIGHT, mNormalTextPaint);
}
}
} }
}
} @Override
public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: if (mStartDatePoint != null && mEndDatePoint != null) {
break;
} if (mStartDatePoint == null) {
if (getPoint(event.getX(), event.getY()) != null) {
mStartDatePoint = getPoint(event.getX(), event.getY());
}
} else {
Point point1 = getPoint(event.getX(), event.getY());
if (point1 != null) {
if (point1.y < mStartDatePoint.y) {
mStartDatePoint = point1;
} else if (point1.x <= mStartDatePoint.x && point1.y == mStartDatePoint.y) {
mStartDatePoint = point1;
} else {
mEndDatePoint = point1; if (mOnCheckedListener != null) {
mOnCheckedListener.onChecked();
} }
}
} invalidate();
break; default:
break;
}
return true;
} private Point getPoint(float x, float y) {
Point point = new Point();
point.x = (int) ((x - getPaddingLeft()) / cellWidth);
point.y = (int) ((y - TITLE_HEIGHT - mWeekHeight) / cellHeight); Log.d("fxxk", "进入getPoint x = " + point.x + "y = " + point.y);
if (point.y < date.length && point.x < date[0].length && point.y >= 0 && point.x >= 0) {
if (date[point.y][point.x] == 0) {
return null;
}
} else {
return null;
} return point; } private void drawCircle(Canvas canvas) {
if (mStartDatePoint != null) {
canvas.drawCircle(mStartDatePoint.x * cellWidth + cellWidth / 2 + getPaddingLeft(), mStartDatePoint.y * cellHeight + cellHeight / 2 + mWeekHeight + TITLE_HEIGHT,
(float) (cellHeight * 0.3), mCirclePaintBg);
} if (mEndDatePoint != null) {
canvas.drawCircle(mEndDatePoint.x * cellWidth + cellWidth / 2 + getPaddingLeft(),
mEndDatePoint.y * cellHeight + cellHeight / 2 + mWeekHeight + TITLE_HEIGHT, (float) (cellHeight * 0.3), mCirclePaintBg);
}
} private void drawRect(Canvas canvas) {
if (mEndDatePoint != null && mStartDatePoint != null) { int total = date[0].length - mStartDatePoint.x + 1 + (mEndDatePoint.y - mStartDatePoint.y) * date[0].length - date[0].length + mEndDatePoint.x;
int beginX = mStartDatePoint.x;
int beginY = mStartDatePoint.y;
for (int i = 0; i < total; i++) { if (i == 0) {
canvas.drawRect(beginX * cellWidth + cellWidth / 2 + getPaddingLeft(), (float) (beginY * cellHeight + TITLE_HEIGHT + mWeekHeight + 0.21 * cellHeight),
beginX * cellWidth + cellWidth + getPaddingLeft(), (float) (beginY * cellHeight + cellHeight + mWeekHeight + TITLE_HEIGHT - 0.21 * cellHeight), mRectPaintBg);
} else if (i == total - 1) {
canvas.drawRect(beginX * cellWidth + getPaddingLeft(), (float) (beginY * cellHeight + TITLE_HEIGHT + mWeekHeight + 0.21 * cellHeight),
beginX * cellWidth + cellWidth / 2 + getPaddingLeft(), (float) (beginY * cellHeight + cellHeight + mWeekHeight + TITLE_HEIGHT - 0.21 * cellHeight),
mRectPaintBg);
} else {
canvas.drawRect(beginX * cellWidth + getPaddingLeft(), (float) (beginY * cellHeight + TITLE_HEIGHT + mWeekHeight + 0.21 * cellHeight),
beginX * cellWidth + cellWidth + getPaddingLeft(), (float) (beginY * cellHeight + cellHeight + mWeekHeight + TITLE_HEIGHT - 0.21 * cellHeight), mRectPaintBg)
;
}
beginX++;
if (beginX % date[0].length == 0) {
beginX = 0;
beginY++;
} }
}
} private int distance(Point point1, Point point2) {
int count = 0; if (point2.y > point1.y) {
count = date.length - point1.x + 1 + (point2.y - point1.y) * date.length - date.length + point2.x;
} else if (point2.y == point1.y) {
count = Math.abs(point2.x - point1.x);
} else if (point2.y < point1.y) {
count = date.length - point2.x + 1 + (point1.y - point2.y) * date.length - date.length + point1.x;
} return count;
} /**
* 设置当前时间
*/
public void setCurrentDate(long time) {
Date date1 = new Date(time); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日");
mDateStr = dateFormat.format(date1); Calendar calendar = Calendar.getInstance();
calendar.setTime(date1);
mCurrentYear = calendar.get(Calendar.YEAR);
mCurrentMonth = calendar.get(Calendar.MONTH);
mCurrentWeek = calendar.get(Calendar.DAY_OF_WEEK);
mCurrentDay = calendar.get(Calendar.DAY_OF_MONTH);
mCurrentMonthStr = mMonths[mCurrentMonth]; if (mCurrentWeek <= 2) {
date = new int[6][7];
} else {
date = new int[5][7];
} int currentNoWeek = date.length; date[currentNoWeek - 1][mCurrentWeek - 1] = mCurrentDay; mLastDayPoint = new Point(currentNoWeek - 1, mCurrentWeek - 1); for (int i = 30; i > 0; i--) {
mCurrentWeek--;
if (mCurrentWeek == 0) {
currentNoWeek--;
mCurrentWeek = 7;
} mCurrentDay--; if (mCurrentDay == 0) { if ((mCurrentYear % 4 == 0 && mCurrentYear % 100 != 0)
|| (mCurrentYear % 400 == 0)) {
switch (mCurrentMonth + 1) {
case 3:
mCurrentDay = 29;
break;
case 5:
case 7:
case 10:
case 12:
mCurrentDay = 30;
break;
case 1:
case 2:
case 4:
case 6:
case 9:
case 8:
case 11:
mCurrentDay = 31;
break;
default:
break;
}
} else {
switch (mCurrentMonth + 1) {
case 3:
mCurrentDay = 28;
break;
case 5:
case 7:
case 10:
case 12:
mCurrentDay = 30;
break;
case 1:
case 2:
case 4:
case 6:
case 9:
case 8:
case 11:
mCurrentDay = 31;
break;
default:
break;
}
}
} if (mCurrentDay == 1) {
mShowMonthPoint = new Point(mCurrentWeek - 1, currentNoWeek - 1);
Log.d("fxxk", (currentNoWeek - 1) + " " + (mCurrentWeek - 1));
} date[currentNoWeek - 1][mCurrentWeek - 1] = mCurrentDay;
} invalidate();
} public Calendar getStartDate() {
Calendar calendar = Calendar.getInstance();
if ((mStartDatePoint.x >= mShowMonthPoint.x && mStartDatePoint.y == mShowMonthPoint.y) || mStartDatePoint.y > mShowMonthPoint.y) {
calendar.set(mCurrentYear, mCurrentMonth, date[mStartDatePoint.y][mStartDatePoint.x]);
} else if ((mStartDatePoint.x < mShowMonthPoint.x && mStartDatePoint.y == mShowMonthPoint.y) || mStartDatePoint.y < mShowMonthPoint.y) {
calendar.set(mCurrentYear, mCurrentMonth - 1, date[mStartDatePoint.y][mStartDatePoint.x]);
} return calendar;
} public Calendar getEndDate() {
Calendar calendar = Calendar.getInstance();
if ((mEndDatePoint.x >= mShowMonthPoint.x && mEndDatePoint.y == mShowMonthPoint.y) || mEndDatePoint.y > mShowMonthPoint.y) {
calendar.set(mCurrentYear, mCurrentMonth, date[mEndDatePoint.y][mEndDatePoint.x]);
} else if ((mEndDatePoint.x < mShowMonthPoint.x && mEndDatePoint.y == mShowMonthPoint.y) || mEndDatePoint.y < mShowMonthPoint.y) {
calendar.set(mCurrentYear, mCurrentMonth - 1, date[mEndDatePoint.y][mEndDatePoint.x]);
} return calendar;
} public List<Integer> getDurationNumbers() { List<Integer> integerList = new ArrayList<>(); if (mEndDatePoint != null && mStartDatePoint != null) { int total = date[0].length - mStartDatePoint.x + 1 + (mEndDatePoint.y - mStartDatePoint.y) * date[0].length - date[0].length + mEndDatePoint.x;
int beginX = mStartDatePoint.x;
int beginY = mStartDatePoint.y;
for (int i = 0; i < total; i++) { integerList.add(date[beginY][beginX]); beginX++;
if (beginX % date[0].length == 0) {
beginX = 0;
beginY++;
} }
} return integerList;
} public void setOnCheckedListener(OnCheckedListener onCheckedListener) {
this.mOnCheckedListener = onCheckedListener;
} public interface OnCheckedListener {
void onChecked();
}
}

主要代码如上,最主要的还是canvas这个画布,以及使用canvas进行绘制。其中还是用了

Paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));这个用于设置相交模式,便于圆角的实现。
下面是自定义属性:
<resources>
<declare-styleable name="CustomDateView">
<attr name="date_text_size" format="dimension" />
<attr name="indicator_text_size" format="dimension" />
<attr name="title_text_size" format="dimension" />
<attr name="checked_circle_color" format="color" />
<attr name="checked_background_color" format="color" />
<attr name="title_background_color" format="color" />
<attr name="title_text_color" format="color" />
<attr name="checked_text_color" format="color" />
<attr name="date_text_color" format="color" />
<attr name="today_text_color" format="color" />
<attr name="indicator_text_color" format="color" />
</declare-styleable>
</resources>

自定义属性主要设置了背景色以及所有字体大小等等属性,这里不进行赘述。

使用方法:

layout布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:orientation="vertical"> <com.test.view.CustomDateView xmlns:date="http://schemas.android.com/apk/res-auto"
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_corner"
android:paddingLeft="10dp"
android:paddingRight="10dp" /> </LinearLayout>

activity:

import android.app.Dialog;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window; import playrtc.huanhong.com.test.R;
import playrtc.huanhong.com.test.view.CustomDateView; /**
* Created by nick on 2016/6/14.
*/
public class DateActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_date); final Dialog dialog = new Dialog(this, R.style.dialog);
View view = View.inflate(this, R.layout.layout_date, null);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(view);
dialog.show(); // Rotate3DAnimation rotate3DAnimation = new Rotate3DAnimation(0, 360);
// rotate3DAnimation.setDuration(3000); final CustomDateView customDateView = (CustomDateView) view.findViewById(R.id.date);
customDateView.setCurrentDate(System.currentTimeMillis());
customDateView.setOnCheckedListener(new CustomDateView.OnCheckedListener() {
@Override
public void onChecked() {
customDateView.postDelayed(new Runnable() {
@Override
public void run() {
dialog.dismiss();
}
}, 1000);
}
}); // customDateView.startAnimation(rotate3DAnimation);
overridePendingTransition(R.anim.activity_in, R.anim.activity_out);
} @Override
public void onBackPressed() {
moveTaskToBack(false);
} @Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
moveTaskToBack(false);
return true;
}
return super.onKeyDown(keyCode, event);
}
}

其中用到了一个dialog的style

<style name="dialog" parent="@android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<!--边框-->
<item name="android:windowIsFloating">true</item>
<!--是否浮现在activity之上-->
<item name="android:windowIsTranslucent">false</item>
<!--半透明-->
<item name="android:windowNoTitle">true</item>
<!--无标题-->
<item name="android:windowBackground">@android:color/transparent</item>
<!--背景透明-->
<item name="android:backgroundDimEnabled">true</item>
<!--模糊-->
</style>

好了,主要就是这样。

上一篇:operator <<”不明确


下一篇:input required