Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義Calendar實現簽到功能

自定義Calendar實現簽到功能

編輯:關於Android編程

前言

這篇文章沒有什麼可看性,主要是源碼注釋太多,推薦自己看源碼,更容易理解些,在這裡主要介紹,其運作流程,貼代碼片段。

自定義View要重寫三個方法:onMeasureonLayoutonDraw,這三個方法各有個的作用,onMeasure是對組件的寬高進行測量,onLayout是對子控件的位置進行擺放,onDraw是對自定義控件進行繪制,已經對onMeasure,onLayout方法進行了運用,那個源碼注釋也很多,如果有興趣的可以去看看,本章是對onDraw方法進行使用,順帶使用Path對象。

好了,先談談為什麼我要重復造輪子,要做一個有簽到功能的日歷,由於自己對自定義的組件ondraw方法還沒怎麼用過,所以重復造輪子咯,是不是理由不是很充分,沒關系,開心就好。

先來張效果圖
這裡寫圖片描述

這個CalendarView的API

    String clickLeftMonth();    //上一個月 return String(年-月)
    String clickRightMonth();   //下一個月 return String(年-月)
    Surface getSurface();       //獲取整個組件畫圖對象,可進行設置字體顏色等 return Surface
    String getYearAndmonth();   // 獲得當前應該顯示的年月 return String(年-月)
    boolean isSelectMore();     //返回是否多選
    setSelectMore(boolean flag);//設置是否多選
    setFlagData(String[] flags);//設置要進行標記的數據
    setOnItemClickListener(OnItemClickListener); //點擊一個日期的回調事件
    setWritingFlag(String str); //設置標記字符,默認為簽到

OK,先來簡述下這個組件跑起來的流程,
1.初始化數據。
2.測量組件大小,即調用了OnMeasure方法
3.調用onDraw方法。

步驟是不是很簡單呀?OK,通過源碼簡單的跑一下流程。

初始化數據

    public CalendarView(Context context) {
        super(context);
        // 初始化數據
        init();
    }

    public CalendarView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 初始化數據
        init();
    }

    /**
     * 初始化數據 ,初始化事件對象 ,初始化日期格式類對象 ,Surface布局對象初始化 ,獲取屏幕密度比例 ,設置View背景 ,設置觸摸事件
     */
    private void init() {
        // 創建一個Date對象並將引用給顯示的月,選擇開始,選擇結束,今天的日期
        curDate = selectedStartDate = selectedEndDate = today = new Date();
        // 獲取一個日期類對象
        calendar = Calendar.getInstance();
        // 設置日期
        calendar.setTime(curDate);
        // 創建一個布局路徑
        surface = new Surface(this);
        // 獲取屏幕密度比例
        surface.density = getResources().getDisplayMetrics().density;
        // 給整個控件設置觸摸事件
        setOnTouchListener(this);
    }

這一塊看出,在組件進行實例化的時候調用了init方法,然後看見了new Surface() 創建了一個Surface對象。ok來看下這個Surface類,其他的應該都知道是什麼。(像我注釋這麼密的看不懂才怪(*\^__^*))。

    public void init() {
        float temp = height / 7f;// 將整個視圖分成了7份,每份所占的高度
        monthHeight = 0;// (float) ((temp + temp * 0.3f) * 0.6);
        weekHeight = (float) ((temp + temp * 0.3f) * 0.5);
        cellHeight = (height - monthHeight - weekHeight) / 6f;
        cellWidth = width / 7f;
        // 創建一個邊框的畫筆並設置其屬性
        borderPaint = new Paint();
        borderPaint.setColor(cellBorderColor);
        borderPaint.setStyle(Paint.Style.STROKE);
        // 邊框的寬度
        borderWidth = (float) (0.5 * density);
        borderWidth = borderWidth < 1 ? 1 : borderWidth;
        borderPaint.setStrokeWidth(borderWidth);

        // 創建星期畫筆並設置其屬性
        weekPaint = new Paint();
        weekPaint.setColor(textWeekColor);
        weekPaint.setAntiAlias(true);
        float weekTextSize = weekHeight * 0.6f;
        weekPaint.setTextSize(weekTextSize);
        weekPaint.setTypeface(Typeface.DEFAULT_BOLD);

        // 創建時間畫筆並設置其屬性
        datePaint = new Paint();
        datePaint.setAntiAlias(true);
        float cellTextSize = cellHeight * 0.3f;
        datePaint.setTextSize(cellTextSize);
        datePaint.setTypeface(Typeface.DEFAULT_BOLD);

        // 創建一個Path對象用於記錄畫筆所畫的路徑
        boxPath = new Path();
        // 畫第一行,現在起點是0,0
        boxPath.rLineTo(width, 0);
        // 將起點向下移動一個星期格子的高度
        boxPath.moveTo(0, monthHeight + weekHeight);
        // 畫第二行
        boxPath.rLineTo(width, 0);

        // 循環畫縱線和號數的橫線
        for (int i = 1; i < 7; i++) {
            // 縱線
            boxPath.moveTo(i * cellWidth, monthHeight);
            boxPath.rLineTo(0, height - monthHeight);
            // 橫線
            boxPath.moveTo(0, monthHeight + weekHeight + i * cellHeight);
            boxPath.rLineTo(width, 0);
        }

        // 表格被選擇後使用的畫筆
        cellBgPaint = new Paint();
        cellBgPaint.setAntiAlias(true);
        cellBgPaint.setStyle(Paint.Style.FILL);
        cellBgPaint.setColor(cellSelectBgColor);
    }

其實這個類也沒做什麼,就一個init方法就是初始化各種畫筆,然後動態計算各種高度和寬度。這裡面的那個for循環裡面的boxPath就是通過path對象記錄繪制的表格路徑。

ok回到CalendarView類,這個組件被實例化了,就開始進行調用onMeasure方法了。這方法裡面沒啥可說的就是測量這個組件的大小,確定這個組件需要的寬高是多少如果

onMeasure和onLayout會被執行兩次,然後才執行onDraw方法,看下這個onDraw方法。

首先調用了這個calculateDate方法。這個方法是動態計算日期的。

/**
     * 計算日期,計算出上月,這月下月的日期裝入到一個數組裡面進行保存
     */
    private void calculateDate() {
        calendar.setTime(curDate);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        int dayInWeek = calendar.get(Calendar.DAY_OF_WEEK);
        Log.d(TAG, "day in week:" + dayInWeek);
        int monthStart = dayInWeek;
        monthStart -= 1; // 以日為開頭-1,以星期一為開頭-2
        curStartIndex = monthStart;
        date[monthStart] = 1;
        // last month
        if (monthStart > 0) {
            calendar.set(Calendar.DAY_OF_MONTH, 0);
            int dayInmonth = calendar.get(Calendar.DAY_OF_MONTH);
            for (int i = monthStart - 1; i >= 0; i--) {
                date[i] = dayInmonth;
                dayInmonth--;
            }
            calendar.set(Calendar.DAY_OF_MONTH, date[0]);
        }
        showFirstDate = calendar.getTime();

        // this month
        calendar.setTime(curDate);
        calendar.add(Calendar.MONTH, 1);
        calendar.set(Calendar.DAY_OF_MONTH, 0);
        int monthDay = calendar.get(Calendar.DAY_OF_MONTH);
        for (int i = 1; i < monthDay; i++) {
            date[monthStart + i] = i + 1;
        }
        curEndIndex = monthStart + monthDay;

        // next month
        for (int i = monthStart + monthDay; i < 42; i++) {
            date[i] = i - (monthStart + monthDay) + 1;
        }
        if (curEndIndex < 42) {
            // 顯示了下一月的
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }
        calendar.set(Calendar.DAY_OF_MONTH, date[41]);
        showLastDate = calendar.getTime();
    }

這個方法動態計算日期,顯示計算上個月所剩下的日期裝入數組date裡面,然後裝當前月份的,最後裝下個月開頭部分日期。

為什麼會在這個onDraw方法裡面調用呢,因為如果在構造方法裡面執行一次就沒法執行了,如果我點擊下一個月那數據就不變了,onMeasure和onLayout都執行兩遍所以不行。因此只能在onDraw方法繪制一次,計算一下。

往下看,這段代碼是繪制星期天的。

        // 畫用於分隔顯示號數的表格框
        canvas.drawPath(surface.boxPath, surface.borderPaint);
        // 星期計算
        float weekTextY = surface.monthHeight + surface.weekHeight * 3 / 4f;
        // 繪制星期1.2.3等字體
        for (int i = 0; i < surface.weekText.length; i++) {
            float weekTextX = i
                    * surface.cellWidth
                    + (surface.cellWidth - surface.weekPaint
                    .measureText(surface.weekText[i])) / 2f;
            canvas.drawText(surface.weekText[i], weekTextX, weekTextY,
                    surface.weekPaint);
        }

動態計算星期1-7的位置然後在所處位置繪制文字。

再下面就是繪制選擇格子的背景顏色,默認是當前月的當前號數。

    /**
     * @param canvas
     */
    private void drawDownOrSelectedBg(Canvas canvas) {
        // down and not up
        if (downDate != null) {
            drawCellBg(canvas, downIndex, surface.cellDownBgColor);
        }
        // selected bg color
        if (!selectedEndDate.before(showFirstDate)
                && !selectedStartDate.after(showLastDate)) {
            int[] section = new int[]{-1, -1};
            calendar.setTime(curDate);
            calendar.add(Calendar.MONTH, -1);
            findSelectedIndex(0, curStartIndex, calendar, section);
            if (section[1] == -1) {
                calendar.setTime(curDate);
                findSelectedIndex(curStartIndex, curEndIndex, calendar, section);
            }
            if (section[1] == -1) {
                calendar.setTime(curDate);
                calendar.add(Calendar.MONTH, 1);
                findSelectedIndex(curEndIndex, 42, calendar, section);
            }
            if (section[0] == -1) {
                section[0] = 0;
            }
            if (section[1] == -1) {
                section[1] = 41;
            }
            for (int i = section[0]; i <= section[1]; i++) {
                drawCellBg(canvas, i, surface.cellSelectBgColor);
            }
        }
    }

後面就是開始繪制日期,即將畫出來的表格填充數字。

        for (int i = 0; i < num; i++) {
            // 這個月的字體顏色
            int color = surface.textInstantColor;
            if (isLastMonth(i)) {
                // 上個月字體顏色
                color = surface.textOtherColor;
            } else if (isNextMonth(i)) {
                // 下個月字體顏色
                color = surface.textOtherColor;
            } else if (todayIndex != -1) {
                // 循環為簽到的日期加標記
                int flagLen = flagData == null ? 0 : flagData.length;
                for (int j = 0; j < flagLen; j++) {
                    if ((date[i] + "").equals(flagData[j]))
                        drawCellFlag(canvas, i, surface.textFlagBgColor,
                                surface.textFlagColor);
                }
                // 如果todayIndex不等於-1且等於今天
                if (i == todayIndex) {
                    // 今天字體顏色
                    color = surface.textTodayColor;
                }
            }
            drawCellText(canvas, i, date[i] + "", color);
        }

在這值得一提的就是這個添加簽到標簽的方法drawCellFlag。

    /**
     * 在格子的右上角進行繪制標簽
     *
     * @param canvas     畫布
     * @param index     下標
     * @param bgcolor   背景顏色
     * @param textcolor 字體顏色
     */
    private void drawCellFlag(Canvas canvas, int index, int bgcolor,
                              int textcolor) {
        int x = getXByIndex(index);
        int y = getYByIndex(index);
        // 計算一個方格子的上下左右距離組件邊框的距離,以此來推出其坐標
        float left = surface.cellWidth * (x - 1) + surface.borderWidth;
        float top = surface.monthHeight + surface.weekHeight + (y - 1)
                * surface.cellHeight - surface.borderWidth;
        float right = left + surface.cellWidth + surface.borderWidth;
        float botton = top + surface.cellHeight - surface.borderWidth;

        surface.cellBgPaint.setColor(bgcolor);
        // 通過Path來記錄路徑,畫一個梯形圖
        Path path = new Path();
        path.moveTo(right - surface.cellWidth * 2 / 3, top);
        path.lineTo(right - surface.cellWidth / 4, top);
        path.lineTo(right, botton - surface.cellHeight * 3 / 4);
        path.lineTo(right, botton - surface.cellHeight / 3);
        canvas.drawPath(path, surface.cellBgPaint);

        // 因為下面的繪制的文字將要進行旋轉因此我將以上Canvas繪制的圖案進行保存,這樣就不會被旋轉給影響到了
        canvas.save();
        // 將字體進行旋轉40度,以文字開始繪制的坐標點進行旋轉
        canvas.rotate((float) 45, right - surface.cellWidth * 3 / 7, botton
                - surface.cellHeight * 5 / 6);
        surface.cellBgPaint.setColor(textcolor);
        // 動態的計算字體大小
        float a = surface.cellWidth / 4;
        float b = surface.cellHeight / 4;
        float c = (float) Math.sqrt(a * a + b * b);
        surface.cellBgPaint.setTextSize(c * 3 / 5);
        surface.cellBgPaint.setTypeface(Typeface.DEFAULT_BOLD);
        // 繪制文字
        canvas.drawText(writingFlag, right - surface.cellWidth * 3 / 7, botton
                - surface.cellHeight * 5 / 6, surface.cellBgPaint);
        // 釋放旋轉狀態,恢復sava時的狀態
        canvas.restore();
    }

這個方法裡面能計算出每個表格的left,right,top,botton的位置,即就可以動態計算梯形四個點,這四個點就是

A(right - surface.cellWidth * 2 / 3, top)
B(right - surface.cellWidth / 4, top)
C(right, botton - surface.cellHeight * 3 / 4)
D(right, botton - surface.cellHeight / 3)

通過Path對象記錄這四個點串起來的路徑然後canvas繪制就ok了。

而這個標簽“簽到”的位置也是這樣給算出來的。

ok,大概流程講完了。詳細的可以去看源碼,裡面注釋多多,你一定能看懂的。(*^__^*)。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved