自定义view实现日历

最近都在学习自定义view相关的知识,另外工作之余也在独立开发一款金融应用,因为作为程序员的我们,看这个看那个都觉得很简单,谁都会,真正着手时,fuck,都不知道从何下手。动手,writing the fuck code!
言归正传!其实android已经为我们实现了日历的功能,提供了Calendar类。在布局的时候可以直接引入即可。那为何还要自己写呢,因为显得很牛逼啊!哈哈哈!先说说总体思路:

  1. 自定义属性
  2. 将年月日相关信息画出
  3. 响应事件监听,切换年月

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事件时的坐标,对比“<”、”>”的坐标,响应对应的事件。具体的细节都在代码中体现了,特别是画日期的时候,先对对第一行做出处理,剩下的就相应确定了。上两张效果图:

当前日历信息

切换月份后的日历信息

猜你喜欢

转载自blog.csdn.net/Mr_azheng/article/details/78465676