最近都在学习自定义view相关的知识,另外工作之余也在独立开发一款金融应用,因为作为程序员的我们,看这个看那个都觉得很简单,谁都会,真正着手时,fuck,都不知道从何下手。动手,writing the fuck code!
言归正传!其实android已经为我们实现了日历的功能,提供了Calendar类。在布局的时候可以直接引入即可。那为何还要自己写呢,因为显得很牛逼啊!哈哈哈!先说说总体思路:
- 自定义属性
- 将年月日相关信息画出
- 响应事件监听,切换年月
Draw view
第一部分的自定义属性很简单,无非就是添加attrs.xml,在布局文件中赋值,所以这里就略过了。
在日历中,一共有三部分需要显示。年月、星期、日,三者相互对应约束。因为要获取当前的日历信息,以及后续监听事件灵活的改变日历,从而更新view,因此独立一个日历类Tool:
public class Tool {
private static Tool mInStance =null;
private Context mContext;
private Calendar mCalendar;
public static Tool getInStance(Context context,Calendar calendar){
if (mInStance == null){
synchronized (Tool.class){
if (mInStance == null){
mInStance = new Tool(context,calendar);
}
}
}
return mInStance;
}
private Tool(Context context, Calendar calendar) {
this.mContext = context;
this.mCalendar = calendar;
}
/*
* 设置日期
*/
protected void setCalendar(Calendar calendar){
this.mCalendar = calendar;
}
public int getCurYear(){
return mCalendar.get(Calendar.YEAR);
}
public int getCurMonth(){
return mCalendar.get(Calendar.MONTH) + 1;
}
public int getCurDay(){
return mCalendar.get(Calendar.DAY_OF_MONTH);
}
public int getCurWeek(){
return mCalendar.get(Calendar.DAY_OF_WEEK);
}
/**
* 判断是当月第一天为星期几
* @param year
* @return
*/
public int getWeekOfFirstDay(int year,int month) {
mCalendar.set(year, month - 1, 1);
return mCalendar.get(Calendar.DAY_OF_WEEK) - 1;//从星期天开始,因此需要减一
}
/**
* 判断是否为闰年
* @param year
* @return
*/
public boolean isLeap(int year) {
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
return true;
}
return false;
}
/**
* 判断当前月有多少天
* @param year,month
* @return
*/
public int getDayOfMonth(int year,int month) {
int days = 30;
switch (month){
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
days = 31;
break;
case 4:
case 6:
case 9:
case 11:
days = 30;
break;
case 2:
if (isLeap(year)){
days = 29;
} else {
days = 28;
}
}
return days;
}
}
设定全局变量mCalendar,当启动应用时,该变量默认存储的是当前的日历信息。当用户点击改变月份或者年份后,通过setCalendar()方法改变该变量值,那么,通过mCalendar返回的信息也对应改变。
先贴出自定义view的代码:
public class CalView extends View {
private static final String TAG = "CalView";
private int mMonthTextSize;
private int mMonthColor;
private int mWeekTextSize;
private int mWeekColor;
private int mDayTextSize;
private int mDayColor;
private int mUnitith;
private Paint mPaint;
private int mUnitHeight;
private String mMonthString;
private Tool mTool = null;
private Context mContext;
private Calendar mCalendar;
private int mCurYear;
private int mCurMonth;
private int mCurDay;
private int mWeekOfFirstDay;
private int mDaysOfMonth;
public CalView(Context context) {
this(context,null);
}
public CalView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
initData();
}
public CalView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context,attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
mUnitith = width / 7;
int height = MeasureSpec.getSize(heightMeasureSpec);
mUnitHeight = height / 10;
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),mUnitHeight * 8);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawMonth(canvas);
drawWeek(canvas);
drawDays(canvas);
}
private void drawDays(Canvas canvas) {
mPaint.setColor(mDayColor);
mPaint.setTextSize(mDayTextSize);
int firstlineCount = 7 - mWeekOfFirstDay;//第一行的天数
int day = 1;//当前画的天
for (int i = 1;i < 7 ; i++){
int textlenght = (int) (mUnitith - mPaint.measureText(String.valueOf(i))) / 2;
int textHeight = (int) (mPaint.getFontMetrics().descent- mPaint.getFontMetrics().ascent) / 3;
if (i == 1){
for (int j = 1;j < firstlineCount + 1;j++){
day = j;
if (day == mCurDay){
drawCicle(canvas,(mWeekOfFirstDay + j -1) * mUnitith + textlenght + (int) mPaint.measureText(String.valueOf(day)) / 2,
3 * mUnitHeight - textHeight);
}
canvas.drawText(String.valueOf(j),(mWeekOfFirstDay + j -1) * mUnitith + textlenght,3 * mUnitHeight,mPaint);
}
} else {
for (int k = 1; k < 8; k ++){
day = firstlineCount + k + (i - 2) * 7;
if (day == mCurDay){
drawCicle(canvas,(k - 1) * mUnitith + textlenght + (int) mPaint.measureText(String.valueOf(day)) / 2,(i + 2) * mUnitHeight - textHeight);
}
if (day < mDaysOfMonth + 1){
canvas.drawText(String.valueOf(day), (k - 1) * mUnitith + textlenght,(i + 2) * mUnitHeight, mPaint);
}
}
}
}
}
private void drawCicle(Canvas canvas,int x ,int y) {
if (mCurMonth == Calendar.getInstance().get(Calendar.MONTH) + 1 &&
mCurYear == Calendar.getInstance().get(Calendar.YEAR)){
mPaint.setColor(Color.RED);
} else {
mPaint.setColor(Color.GREEN);
}
canvas.drawCircle(x,y,mUnitHeight/3,mPaint);
mPaint.setColor(mDayColor);
}
private void drawWeek(Canvas canvas) {
String[] weeks = {"Sun", "Mon", "Tues", "Wed", "Thur", "Fri", "Sat"};
mPaint.setTextSize(mWeekTextSize);
mPaint.setColor(mWeekColor);
for (int i = 0; i < weeks.length; i++){
int x = (int) (i * mUnitith + (mUnitith - mPaint.measureText(weeks[i])) / 2);
canvas.drawText(weeks[i],x,mUnitHeight * 2,mPaint);
}
}
private void drawMonth(Canvas canvas) {
mMonthString = "< " + mCurYear + "/" + mCurMonth + ">";
mPaint.setTextSize(mMonthTextSize);
mPaint.setColor(mMonthColor);
canvas.drawText(mMonthString, (getWidth() - mPaint.measureText(mMonthString))/2, mUnitHeight,mPaint);
}
private void initView(Context context,AttributeSet attrs) {
TypedArray typearray = context.obtainStyledAttributes(attrs, R.styleable.CalView);
mMonthColor = typearray.getColor(R.styleable.CalView_monthtextcolor,0);
mMonthTextSize = typearray.getDimensionPixelSize(R.styleable.CalView_monthtextsize,0);
mWeekColor = typearray.getColor(R.styleable.CalView_weektextcolor,0);
mWeekTextSize = typearray.getDimensionPixelSize(R.styleable.CalView_weektextsize,0);
mDayColor = typearray.getColor(R.styleable.CalView_daytextcolor,0);
mDayTextSize = typearray.getDimensionPixelSize(R.styleable.CalView_daytextsize,0);
mPaint = new Paint();
mCalendar = Calendar.getInstance();
mTool = Tool.getInStance(context, mCalendar);
this.mContext = context;
}
private void initData(){
mCurYear = mTool.getCurYear();
mCurMonth = mTool.getCurMonth();
mCurDay = mTool.getCurDay();
mDaysOfMonth = mTool.getDayOfMonth(mCurYear,mCurMonth);
mWeekOfFirstDay = mTool.getWeekOfFirstDay(mCurYear,mCurMonth);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG,"x:" + event.getX() + " Y:" + event.getX());
int x = (int) event.getX();
int y = (int) event.getY();
if (y < mUnitHeight && event.getAction() == MotionEvent.ACTION_DOWN){
if ( x > (getWidth() - mPaint.measureText(mMonthString)) / 2 && x < (getWidth() - mPaint.measureText(mMonthString)) / 2 + mPaint.measureText("<")){
changeMonth(false);
} else if ( x > ((getWidth() - mPaint.measureText(mMonthString)) / 2 + mPaint.measureText(mMonthString) - mPaint.measureText(">")) &&
x < (getWidth() - mPaint.measureText(mMonthString)) / 2 + mPaint.measureText(mMonthString)){
changeMonth(true);
}
}
return true;
}
private void changeMonth(boolean b) {
if (b){
//增加月
mCurMonth +=1;
if (mCurMonth > 12){
mCurYear +=1;
mCurMonth = 1;
}
} else {
mCurMonth -=1;
if (mCurMonth < 1){
mCurYear -=1;
mCurMonth = 12;
}
}
mCalendar.set(mCurYear,mCurMonth,mCurDay);
mTool.setCalendar(mCalendar);
mCurDay = mTool.getCurDay();
mWeekOfFirstDay = mTool.getWeekOfFirstDay(mCurYear,mCurMonth);
mDaysOfMonth = mTool.getDayOfMonth(mCurYear,mCurMonth);
invalidate();
}
}
横向上,将屏幕分成七个小单元;纵向上,将屏幕分成十个小单元,当然这十个单元用不完,只用到了8个单元。在onMeasure()时,设置最终的宽高。
最后,就是onTouchEvent()事件了,这里只实现了增加减小月份的功能。通过获取touch事件时的坐标,对比“<”、”>”的坐标,响应对应的事件。具体的细节都在代码中体现了,特别是画日期的时候,先对对第一行做出处理,剩下的就相应确定了。上两张效果图: