这里分享一个使用canvans绘制小日历的demo。主要的思路就是由于只要显示接下来的15天时间,并增加点击时间。所以想到了,使用cavans的绘制,当然使用gridview的item的显示或者隐藏也是可以的。由于项目需要的只是未来15天的绘制,所以没有做过多的拓展。比如拓展成日历啥的。看效果图:
一、分析
首先我们会想到绘制星期一到星期天这1行7列的数据。然后根据当前时间,推算出明天是星期几,在绘制列数的循环中进行判断,从哪里开始绘制,然后第二行永远是一整行,所以获取星期一的日期进行绘制一整行,最后一行是算出剩余天数,使用15-前面两行用掉的日期。当然我们需要注意的是判断下一天是否到了下个月。所以需要做一个取余数的操作。
if (firstnum > monthNum) {
firstnum = firstnum % monthNum;//取余数
}
代码已经很详细了。就是一个循环,绘制所有行。当然比较笨的方法。后面希望能拓展写出一个真的日历。
二、难点
1、使得绘制的文字居中。我们这里当然采用宽度等分的思想,以及高度多个文字高度/2的行距。
公式:
以绘制星期几为例:
日期标题行:
int startX = mColumSize * i + (mColumSize - fontWidth) / 2;
int startY = (int) (mRowSize * 0 + mRowSize / 2 - (mWeekPaint.ascent() + mWeekPaint.descent()) / 2);
日期第一行:
int startX1 = mColumSize * i + (mColumSize - (int) mDatePaint.measureText(text1)) / 2;
int startY1 = (int) (mRowSize * 1 + mRowSize / 2 - (mDatePaint.ascent() + mDatePaint.descent()) / 2);
日期第二行:
int startX2 = mColumSize * i + (mColumSize - (int) mDatePaint.measureText(t)) / 2;
int startY2 = (int) (mRowSize * 2 + mRowSize / 2 - (mDatePaint.ascent() + mDatePaint.descent()) / 2);
日期第三行:
int startX3 = mColumSize * i + (mColumSize - (int) mDatePaint.measureText(tx)) / 2;
int startY3 = (int) (mRowSize * 3 + mRowSize / 2 - (mDatePaint.ascent() + mDatePaint.descent()) / 2);
2、点击日期的监听事件并绘制实心圆。
就是点击的时候获取坐标。判断是在那个网格的坐标内。进行回调操作,给选中日期mSelDay全局变量赋值,然后调用invalidate();方法,重绘该视图,调用ondraw()方法。思路比较清晰
//判断是否为选中的日期
if (!mSelDay.equals("") && mSelDay.equals(text1)) {
//先绘制背景,不然会覆盖
mSelPaint.setColor(mCircleColor);
mSelPaint.setAntiAlias(true);
mSelPaint.setStyle(Paint.Style.FILL);
drawCircle(1, i, canvas);
//字体
mSelPaint.setColor(Color.WHITE);
mSelPaint.setTextSize(mDateTextSize);
canvas.drawText(text1, startX1, startY1, mSelPaint);
//清空
mSelDay = "";
} else {
canvas.drawText(text1, startX1, startY1, mDatePaint);
}
三、实现
1、自定义view的实现,CalendarCard.java.java:
package com.xxx.xxx.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import com.coofond.carservices.R;
import com.coofond.carservices.utils.DataUtil;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
/**
* @description: 日历小控件显示未来15天的日期并可点击
* @Author zsj on 2017/2/24 14:21.
*/
public class CalendarCard extends View {
/**
* 几行几列
*/
private static final int NUM_COLUMS = 7;
private static final int NUM_ROWS = 4;
/**
* 标题星期的颜色
*/
private int mWeekdayColor;
/**
* 星期标题
*/
private String[] mWeekString = new String[]{"一", "二", "三", "四", "五", "六", "日"};
/**
* 星期画笔
*/
private Paint mWeekPaint;
/**
* 星期文字大小
*/
private int mWeekTextsize;
/**
* 屏幕参数
*/
private DisplayMetrics mDisplayMetrics;
/**
* 日期画笔
*/
private Paint mDatePaint;
/**
* 日期颜色
*/
private int mDateColor;
/**
* 选中字体专属画笔
*/
private Paint mSelPaint;
/**
* 日期文字大小
*/
private int mDateTextSize;
/**
* 选中圆圈颜色
*/
private int mCircleColor;
/**
* 选中圆圈半径
*/
private int mRaduis;
/**
* 当前年份
*/
private int mYear;
/**
* 当前月份
*/
private int mMonth;
/**
* 当天日期
*/
private int mDay;
/**
* 列宽
*/
private int mColumSize;
/**
* 行高
*/
private int mRowSize;
/**
* 记录选中的日期,后面重绘的时候进行判断
*/
private String mSelDay = "";
/**
* 第一列日期
*/
private List<String> dayString1 = new ArrayList<>();//第一列存储日期
/**
* 第二列日期
*/
private List<String> dayString2 = new ArrayList<>();//第二列存储日期
/**
* 第三列日期
*/
private List<String> dayString3 = new ArrayList<>();//第三列存储日期
/**
* 当前日期
*/
private String mToday;
/**
* @return 获取选中的日期
*/
public String getmChooseday() {
return mChooseday;
}
/**
* @param mChooseday 设置选中的日期
*/
public void setmChooseday(String mChooseday) {
this.mChooseday = mChooseday;
mSelDay = DataUtil.getDay(mChooseday, new SimpleDateFormat("yyyy-MM-dd"));//设置选中的日期
}
/**
* 选中的日期
*/
private String mChooseday;
/**
* 日期点击事件
*/
private DateClick dateClick;
public CalendarCard(Context context) {
this(context, null);
}
public CalendarCard(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CalendarCard(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
/**
* 获取我们的自定义属性
*/
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CalendarCard, defStyleAttr, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.CalendarCard_mWeekdayColor:
mWeekdayColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.CalendarCard_mWeekTextsize:
mWeekTextsize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12,
getResources().getDisplayMetrics()));
break;
case R.styleable.CalendarCard_mDateColor:
mDateColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.CalendarCard_mDateTextSize:
mDateTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12,
getResources().getDisplayMetrics()));
break;
case R.styleable.CalendarCard_mCircleColor:
mCircleColor = a.getColor(attr, Color.RED);
break;
case R.styleable.CalendarCard_mCaRaduis:
mRaduis = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15,
getResources().getDisplayMetrics()));
break;
case R.styleable.CalendarCard_mToday:
mToday = a.getString(attr);
break;
case R.styleable.CalendarCard_mChooseday:
mChooseday = a.getString(attr);
break;
}
}
a.recycle();
mDisplayMetrics = getResources().getDisplayMetrics();
mWeekPaint = new Paint();//标题画笔
mDatePaint = new Paint();//日期画笔
mSelPaint = new Paint();//选中的画笔
}
@Override
protected void onDraw(Canvas canvas) {
initSize();
mWeekPaint.setStyle(Paint.Style.FILL);
mWeekPaint.setAntiAlias(true);//去锯齿
//适配,*屏幕密度 * mDisplayMetrics.scaledDensity加上这个字体会好大。。。
mWeekPaint.setTextSize(mWeekTextsize);
mWeekPaint.setColor(mWeekdayColor);
mDatePaint.setStyle(Paint.Style.FILL);
mDatePaint.setAntiAlias(true);
mDatePaint.setTextSize(mDateTextSize);
mDatePaint.setColor(mDateColor);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
//获取当天的星期数以及当月最大天数
List<Integer> listInt = DataUtil.getWeekAndWeekCount(mToday, simpleDateFormat);
int weekIndex = listInt.get(1);//明天星期几下标
int monthNum = listInt.get(2);//当月最大天数
int firstnum = listInt.get(0);//第一个星期下标。注意是当前日期+1
int secondnum = listInt.get(0) + (7 - weekIndex + 1);//第二个星期下标
int thirdnum = listInt.get(0) + (7 - weekIndex + 1) + 7;//第三个星期下标
int thirdRelease = 15 - 7 - (7 - weekIndex + 1);//剩余天数
//初始化
dayString1.clear();
dayString2.clear();
dayString3.clear();
//遍历进行绘制标题星期几
for (int i = 0; i < mWeekString.length; i++) {
String text = mWeekString[i];
int fontWidth = (int) mWeekPaint.measureText(text);//获取到文本宽度
int startX = mColumSize * i + (mColumSize - fontWidth) / 2;
int startY = (int) (mRowSize * 0 + mRowSize / 2 - (mWeekPaint.ascent() + mWeekPaint.descent()) / 2);
//绘制文字
canvas.drawText(text, startX, startY, mWeekPaint);
//判断是否为星期六,日。最后两次循环改变画笔颜色
if (i == 5 || i == 6) {
mDatePaint.setColor(mCircleColor);//修改一次就可以了
}
/**
* 绘制日期第一行
* 绘制从明天开始
*/
if (i + 1 >= weekIndex) {
if (firstnum > monthNum) {
firstnum = firstnum % monthNum;//取余数
}
String text1 = String.valueOf(firstnum);
int startX1 = mColumSize * i + (mColumSize - (int) mDatePaint.measureText(text1)) / 2;
int startY1 = (int) (mRowSize * 1 + mRowSize / 2 - (mDatePaint.ascent() + mDatePaint.descent()) / 2);
//判断是否为选中的日期
if (!mSelDay.equals("") && mSelDay.equals(text1)) {
//先绘制背景,不然会覆盖
mSelPaint.setColor(mCircleColor);
mSelPaint.setAntiAlias(true);
mSelPaint.setStyle(Paint.Style.FILL);
drawCircle(1, i, canvas);
//字体
mSelPaint.setColor(Color.WHITE);
mSelPaint.setTextSize(mDateTextSize);
canvas.drawText(text1, startX1, startY1, mSelPaint);
//清空
mSelDay = "";
} else {
canvas.drawText(text1, startX1, startY1, mDatePaint);
}
dayString1.add(text1);
firstnum++;
} else {
dayString1.add("");//如果不是就添加空,保持下标一致
}
/**
* 绘制日期第二行
*/
if (secondnum > monthNum) {
secondnum = secondnum % monthNum;//取余数
}
String t = String.valueOf(secondnum);
int startX2 = mColumSize * i + (mColumSize - (int) mDatePaint.measureText(t)) / 2;
int startY2 = (int) (mRowSize * 2 + mRowSize / 2 - (mDatePaint.ascent() + mDatePaint.descent()) / 2);
if (!mSelDay.equals("") && mSelDay.equals(t)) {
//先绘制背景,不然会覆盖
mSelPaint.setColor(mCircleColor);
mSelPaint.setStyle(Paint.Style.FILL);
drawCircle(2, i, canvas);
//字体
mSelPaint.setColor(Color.WHITE);
mSelPaint.setAntiAlias(true);
mSelPaint.setTextSize(mDateTextSize);
canvas.drawText(t, startX2, startY2, mSelPaint);
//清空
mSelDay = "";
} else {
canvas.drawText(t, startX2, startY2, mDatePaint);
}
dayString2.add(t);
secondnum++;
/**
* 绘制日期第三行
*/
//剩余循环次数
if (thirdRelease > 0) {
if (thirdnum > monthNum) {
thirdnum = thirdnum % monthNum;//取余数
}
String tx = String.valueOf(thirdnum);
int startX3 = mColumSize * i + (mColumSize - (int) mDatePaint.measureText(tx)) / 2;
int startY3 = (int) (mRowSize * 3 + mRowSize / 2 - (mDatePaint.ascent() + mDatePaint.descent()) / 2);
if (!mSelDay.equals("") && mSelDay.equals(tx)) {
//先绘制背景,不然会覆盖
mSelPaint.setColor(mCircleColor);
mSelPaint.setAntiAlias(true);
mSelPaint.setStyle(Paint.Style.FILL);
drawCircle(3, i, canvas);
//字体
mSelPaint.setColor(Color.WHITE);
mSelPaint.setTextSize(mDateTextSize);
canvas.drawText(tx, startX3, startY3, mSelPaint);
//清空
mSelDay = "";
} else {
canvas.drawText(tx, startX3, startY3, mDatePaint);
}
dayString3.add(tx);
thirdnum++;
thirdRelease--;
}
}
}
/**
* 初始化列宽行高
*/
private void initSize() {
mColumSize = getWidth() / NUM_COLUMS;
mRowSize = mRaduis * 2 * 4 / NUM_ROWS;
}
/**
* 监听点击事件
*/
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:
int upX = (int) event.getX();
int upY = (int) event.getY();
//判断点击事件
if (Math.abs(upX - downX) < 10 && Math.abs(upY - downY) < 10) {
performClick();
doClickAction((upX + downX) / 2, (upY + downY) / 2);
}
break;
}
return true;
}
//绘制圆形背景
private void drawCircle(int row, int column, Canvas canvas) {
mSelPaint.setColor(mCircleColor);
float circleX = (float) (mColumSize * column + mColumSize * 0.5);
float circley = (float) (mRowSize * row + mRowSize * 0.5);
canvas.drawCircle(circleX, circley, mRaduis, mSelPaint);
}
//点击之前做的事情
@Override
public boolean performClick() {
return super.performClick();
}
//点击日期
private void doClickAction(int x, int y) {
int row = y / mRowSize;
int column = x / mColumSize;
switch (row) {
case 0:
break;
case 1:
//这里需要注意的是dayString1.get(column)获取的是点击的位置(列数)
if (!dayString1.get(column).equals("")) {
mSelDay = dayString1.get(column);
invalidate();
}
break;
case 2:
mSelDay = dayString2.get(column);
invalidate();
break;
case 3:
if (column < dayString3.size()) {
mSelDay = dayString3.get(column);
invalidate();
}
break;
}
//执行activity发送过来的点击处理事件
if (dateClick != null) {
dateClick.onClickOnDate();
}
}
/**
* 设置日期的点击回调事件
*/
public interface DateClick {
void onClickOnDate();
}
/**
* 设置日期点击事件
*
* @param dateClick
*/
public void setDateClick(DateClick dateClick) {
this.dateClick = dateClick;
}
/**
* 返回选中的日期
*/
public String getmSelDay() {
return mSelDay;
}
}
2、xml中的使用
<com.xxx.xxx.widget.CalendarCard
android:id="@+id/calendar"
android:layout_width="526px"
android:layout_height="120dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="48px"
app:mCaRaduis="15dp"
app:mChooseday="2017-03-23"
app:mCircleColor="@color/orange"
app:mDateColor="@color/textcolordark"
app:mDateTextSize="13sp"
app:mToday="2017-03-18"
app:mWeekTextsize="12sp"
app:mWeekdayColor="@color/textcolorgray" />
3、attrs中的自定义属性
<!--日历控件-->
<declare-styleable name="CalendarCard">
<attr name="mWeekdayColor" format="color" />
<attr name="mWeekTextsize" format="dimension" />
<attr name="mDateColor" format="color" />
<attr name="mDateTextSize" format="dimension" />
<attr name="mCircleColor" format="color" />
<attr name="mCaRaduis" format="dimension" />
<attr name="mToday" format="string" />
<attr name="mChooseday" format="string" />
</declare-styleable>
3、像普通的view一样使用
CalendarCard mCalendar=(CalendarCard )findViewById(R.id.calendar);
//日历点击事件
mCalendar.setDateClick(new CalendarCard.DateClick() {
@Override
public void onClickOnDate() {
ToastUtil.toastCenter2(mContext, mCalendar.getmSelDay());
}
});
四、总结:
代码很详细了。直接拿去用,就不上demo了。纯粹做个总结,给自己当个笔记,如果有一行代码能帮到你,那我也很开心。have a nice day~