自定義view之實(shí)現(xiàn)日歷界面

現(xiàn)在網(wǎng)上有很多自定義view實(shí)現(xiàn)日歷的demo造挽,今天講一講如何自己實(shí)現(xiàn)這個(gè)自定義view。

看一下最終效果圖:

在這個(gè)自定義view中香罐,我使用了各種奇技淫巧的方法來(lái)實(shí)現(xiàn)這個(gè)日歷肮街,真是費(fèi)盡心思。廢話(huà)少說(shuō)像棘,開(kāi)始進(jìn)坑。

界面分析

頭部是一個(gè)textview诊县,顯示年份和月份讲弄,然后下邊一行是星期幾措左,這兩行可以固定住依痊,不隨月份切換而進(jìn)出屏幕。

再下邊就是我們自定義view 的主角怎披,每個(gè)月的天數(shù)胸嘁。目前規(guī)定是星期日為每星期第一天。上個(gè)月的天數(shù)填充滿(mǎn)第一行凉逛,下個(gè)月的前幾天填充完最后一行性宏,顏色設(shè)置為灰色,本月日期中的周一至周五設(shè)置為紅色状飞,周六周日設(shè)置為青色毫胜,特殊日期設(shè)置為綠色,并且在右上角填充特殊標(biāo)識(shí)符诬辈,用四分之三的圓弧包裹(上個(gè)月和下個(gè)月的日期沒(méi)有)酵使。

此處還有個(gè)小細(xì)節(jié),每月的總行數(shù)會(huì)不斷改變焙糟,但是view的總高度并未改變口渔,所以視覺(jué)效果會(huì)不一樣。

構(gòu)造方法

 public MyCalendar(Context context) {
        super(context);
    }

    public MyCalendar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

    }

主要是實(shí)現(xiàn)上面兩個(gè)構(gòu)造方法穿撮,第一個(gè)是用來(lái)在java代碼中使用的缺脉,第二個(gè)是用來(lái)在xml布局文件中使用的痪欲。

暴露的接口

目前接口共有下面幾個(gè),setDate(CustomDate customDate)攻礼,setWeekendHighLight(boolean b)业踢,setSpecialDay(int[] ints)

其中第一個(gè)是必須要設(shè)置的,否則是不會(huì)顯示任何東西礁扮,第二個(gè)設(shè)置的是否周末高亮陨亡,第三個(gè)設(shè)置的是特殊顯示的日期,第四個(gè)是設(shè)置是否可以點(diǎn)擊前一個(gè)月或者后一個(gè)月的日期深员,默認(rèn)為不設(shè)置负蠕,后期可以根據(jù)自己需求增加其他接口。

    /**
     * 暴露接口倦畅,設(shè)置日期
     *
     * @param customDate
     */
    public void setDate(CustomDate customDate) {
        Log.d(TAG, customDate.toString());
        this.date = customDate;
        firstDayOfWeek = date.getFirstDayOfWeek();
        Log.d(TAG, (date.getMonth() + 1) + "月1號(hào)是星期" + firstDayOfWeek);
        lastDayOfWeek = date.getLastDayOfWeek();
        lineCount = calculateLineNum() + 1;
        lastMonthTotalDays = date.getLastMonthDays();
    }

    /**
     * 暴露接口遮糖,設(shè)置是否周末高亮
     *
     * @param b
     */
    public void setWeekendHighLight(boolean b) {
        this.weekendHighlight = b;
    }

    public void setSpecialDay(int[] ints) {
        this.specialDays = ints;
    }

    /**
     * 暴露接口,設(shè)置是否可以點(diǎn)擊前一個(gè)月和后一個(gè)月的日期
     *
     * @param b
     */
    public void setCanClickNextOrPreMonth(boolean b) {
        this.canClickNextOrPreMonth = b;
    }

在這里說(shuō)明一下計(jì)算顯示行數(shù)的方法,首先要注意我們獲取的星期數(shù)與實(shí)際的星期幾會(huì)有一個(gè)增加一天的問(wèn)題叠赐,也就是當(dāng)前是星期4欲账,那么你獲取的int將會(huì)是5.

 /**
     * 獲得應(yīng)該設(shè)置為多少行
     *
     * @return
     */
    private int calculateLineNum() {
        monthDaySum = date.getTotalDayOfMonth();
        return (firstDayOfWeek - 1 + monthDaySum) / 7;
    }

我們將第一天是星期幾減去一后加上這個(gè)月總共多少天,就可以獲得最后一天是在什么位置芭概,然后除以七取商的整數(shù)部分赛不,然后在進(jìn)一法即可獲得應(yīng)該顯示多少行。

onSizechanged方法

onSizechanged方法中已經(jīng)可以獲得顯示的尺寸了罢洲,此時(shí)我們需要做一些工作:

 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        this.viewWidth = w;
        this.viewHeight = h;
        Log.d(TAG, "onSizeChanged" + w + h);
        cutGrid();
        init();
        setCellDay();
    }

首先是將寬和高引入進(jìn)來(lái)踢故,方便后邊使用。

cutGrid()方法是將區(qū)域分割為行X列的格式惹苗。

init()方法初始化了一些畫(huà)筆殿较。

setCellDay()方法將每月的天對(duì)應(yīng)過(guò)到坐標(biāo)上。

首先看一下cutGrid()方法:

 /**
     * 切分為每天
     */
    private void cutGrid() {
        cellWidth = (float) viewWidth / ROW_COUNT;
        cellHeight = (float) viewHeight / lineCount;
        this.radius = Math.min(cellWidth / 2, cellHeight / 2);
        for (int i = 0; i < lineCount; i++) {
            for (int j = 0; j < ROW_COUNT; j++) {
                points.add(new PointF(cellWidth * j + cellWidth / 2, cellHeight * i + cellHeight / 2));
            }
        }
    }

cellWidth是每天的寬度桩蓉,其中ROW_COUNT是一個(gè)常量7淋纲,表示每周7天;cellHeight是每行的高度院究,linecount是一個(gè)變量洽瞬,需要我們根據(jù)日期計(jì)算,后邊會(huì)說(shuō)到业汰;radius是我們繪制區(qū)域的半徑伙窃,這個(gè)值是我們?nèi)挾群透叨戎休^小的值的一半。然后我們將每個(gè)方格中心坐標(biāo)點(diǎn)利用雙重循環(huán)放入一個(gè)List<Point> points中蔬胯。

整個(gè)view被分割為如上的形狀对供。

下面來(lái)看一下init()方法:

private void init() {
        circlePaint = new Paint();
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setAntiAlias(true);
        circlePaint.setColor(Color.BLUE);
        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(Color.BLACK);
        textPaint.setTextSize(radius / 2);
        selectPaint = new Paint();
        selectPaint.setColor(Color.YELLOW);
        selectPaint.setAlpha(10);
        selectPaint.setAntiAlias(true);
        selectPaint.setStyle(Paint.Style.FILL);
        selectTextPaint = new Paint();
        selectTextPaint.setColor(Color.WHITE);
        selectTextPaint.setAntiAlias(true);
        selectTextPaint.setTextSize(radius / 2);
        selectTextPaint.setStyle(Paint.Style.FILL);
    }

基本都是畫(huà)筆工具。

然后是setAllDays()方法:

 /**
     * 設(shè)置總共顯示多少天,每天的狀態(tài)
     */
    private void setCellDay() {
        cellDays = new CellDay[lineCount * ROW_COUNT];
        for (int i = 0, length = cellDays.length; i < length; i++) {
            cellDays[i] = new CellDay();
            cellDays[i].setPointX(points.get(i).x);
            cellDays[i].setPointY(points.get(i).y);
            if (firstDayOfWeek > 1 && i < firstDayOfWeek - 1) {
                cellDays[i].setDayState(DayState.LASTMONTH);
                cellDays[i].setDate(String.valueOf(lastMonthTotalDays - firstDayOfWeek + i + 2));
                cellDays[i].setCustomDate(new CustomDate(
                        date.getYear(), date.getMonth() - 1, lastMonthTotalDays - firstDayOfWeek + i + 2));
            }
            if (i >= firstDayOfWeek - 1 && i < monthDaySum + firstDayOfWeek - 1) {

                cellDays[i].setDayState(CURRENTMONTH);
                cellDays[i].setDate(String.valueOf(i + 2 - firstDayOfWeek));
                cellDays[i].setCustomDate(new CustomDate(
                        date.getYear(), date.getMonth(), i - firstDayOfWeek + 2));
                //設(shè)置周末高亮
                if (weekendHighlight) {
                    if (i % 7 == 0 || i % 7 == 6) {
                        cellDays[i].setDayState(WEEKEND);
                    }
                }
            }
            if (i >= monthDaySum + firstDayOfWeek - 1) {
                cellDays[i].setDayState(NEXTMONTH);
                cellDays[i].setDate(String.valueOf(i - monthDaySum - firstDayOfWeek + 2));
                cellDays[i].setCustomDate(new CustomDate(
                        date.getYear(), date.getMonth() + 1, i - monthDaySum - firstDayOfWeek + 2));
            }
            for (int j = 0, s = specialDays.length; j < s; j++) {
                if (specialDays[j] + firstDayOfWeek - 2 == i) {
                    cellDays[i].setDayState(SPECIALDAY);
                }
            }
        }
    }

在這里我們用到了一個(gè)自定的類(lèi)-CellDay产场。
CellDay有以下幾個(gè)字段

        private String date;
        private DayState dayState;
        private CustomDate customDate;
        private float pointX;
        private float pointY;
        private boolean isSelected;
  1. String date表示當(dāng)前的日期鹅髓。
  2. dayState是一個(gè)美劇類(lèi)型,定義了天的狀態(tài)值京景。
    LASTMONTH:上個(gè)月的日期
    CURRENTMONTH:本月的日期
    NEXTMONTH: 下個(gè)月的日期
    CURRENTDAY: 今天的日期
    WEEKEND:周末的日期
    SPECIALDAY:用戶(hù)自定義的可以設(shè)置狀態(tài)的日期

其中可以設(shè)置多種狀態(tài)窿冯,用法和SPECIALDAY基本一樣。

  1. cusomedate是我們自己定義的一個(gè)工具類(lèi)确徙,包含項(xiàng)目中需要用到的一系列方法醒串。
  2. pointX是橫坐標(biāo)。
  3. pointY是縱坐標(biāo)鄙皇。
  4. isSelceted表示有沒(méi)有被選中芜赌。

CustomDate工具

public class CustomDate {
    private Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
    private int year;
    private int month;
    private int day;
    private int dayOfWeek;

    public CustomDate() {
    }

    /**
     * 獲取當(dāng)前的日期
     * @return
     */
    public CustomDate getCurrentDate() {
        this.year = calendar.get(Calendar.YEAR);
        this.month = calendar.get(Calendar.MONTH);
        this.day = calendar.get(Calendar.DAY_OF_MONTH);
        this.dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
        return new CustomDate(year, month, day);
    }

    public CustomDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
        calendar.set(year, month, day);
        dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
    }

    /**
     * 獲取上個(gè)月的天數(shù)
     * @return
     */
    public int getLastMonthDays() {
        return this.getDaysOfMonth(this.year, this.month - 1);
    }

    /**
     * 獲取第一天是星期幾
     *
     * @return
     */
    public int getFirstDayOfWeek() {
        calendar.set(this.year, this.month, 1);
        return calendar.get(Calendar.DAY_OF_WEEK);
    }

    /**
     * 獲取最后一天是星期幾
     *
     * @return
     */
    public int getLastDayOfWeek() {
        calendar.set(this.year, this.month, getTotalDayOfMonth());
        return calendar.get(Calendar.DAY_OF_WEEK);
    }

    /**
     * 獲取這個(gè)月總共的天數(shù)
     * @return
     */
    public int getTotalDayOfMonth() {
        return this.getDaysOfMonth(year, month);
    }

    public int getTotalWeekOfMonth() {
        return calendar.getMaximum(Calendar.WEEK_OF_MONTH);
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    public int getDayOfWeek() {
        return dayOfWeek;
    }

    public void setDayOfWeek(int dayOfWeek) {
        this.dayOfWeek = dayOfWeek;
    }

    @Override
    public String toString() {
        return "CustomDate{" +
                "year=" + year +
                ", month=" + (getMonth() + 1) +
                ", day=" + day +
                ", dayOfWeek=" + dayOfWeek +
                '}';
    }

    /**
     * 獲取年中每月的天數(shù)
     * @param year
     * @param month
     * @return
     */
    private int getDaysOfMonth(int year, int month) {
        if (month > 11) {
            month = 0;
            year += 1;
        } else if (month < 0) {
            month = 11;
            year -= 1;
        }

        int[] arr = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        int daysOfMonth = 0;
        if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
            arr[1] = 29;
        }
        daysOfMonth = arr[month];
        return daysOfMonth;
    }
}

注釋中對(duì)每個(gè)方法的說(shuō)明已經(jīng)非常清晰了。

  1. int getLastMonthDays()
    獲取上個(gè)月的天數(shù)是用來(lái)計(jì)算上個(gè)月最后一天是星期幾伴逸,然后以此推導(dǎo)出上個(gè)月在本月中顯示的天數(shù)和對(duì)應(yīng)的星期缠沈。
  2. getFirstDayOfWeek()
    獲取本月第一天是星期幾,然后排序本月的天數(shù)與對(duì)應(yīng)的星期错蝴。
  3. int getTotalDayOfMonth()
    獲取本月總共多少天洲愤。配合第一天是星期幾用來(lái)計(jì)算總共分為幾行,也就是確定linenumber顷锰。

下一節(jié)將介紹如何繪制和分發(fā)touch事件柬赐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市官紫,隨后出現(xiàn)的幾起案子肛宋,更是在濱河造成了極大的恐慌,老刑警劉巖万矾,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悼吱,死亡現(xiàn)場(chǎng)離奇詭異慎框,居然都是意外死亡良狈,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)笨枯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)薪丁,“玉大人,你說(shuō)我怎么就攤上這事馅精⊙鲜龋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵洲敢,是天一觀的道長(zhǎng)漫玄。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么睦优? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任渗常,我火速辦了婚禮,結(jié)果婚禮上汗盘,老公的妹妹穿的比我還像新娘皱碘。我一直安慰自己,他們只是感情好隐孽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布癌椿。 她就那樣靜靜地躺著,像睡著了一般菱阵。 火紅的嫁衣襯著肌膚如雪踢俄。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天晴及,我揣著相機(jī)與錄音褪贵,去河邊找鬼。 笑死抗俄,一個(gè)胖子當(dāng)著我的面吹牛脆丁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播动雹,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼槽卫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了胰蝠?” 一聲冷哼從身側(cè)響起歼培,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎茸塞,沒(méi)想到半個(gè)月后躲庄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钾虐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年噪窘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片效扫。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡倔监,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出菌仁,到底是詐尸還是另有隱情浩习,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布济丘,位于F島的核電站谱秽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疟赊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一辱士、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧听绳,春花似錦颂碘、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鼠证,卻和暖如春峡竣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背量九。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工适掰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荠列。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓类浪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親肌似。 傳聞我的和親對(duì)象是個(gè)殘疾皇子费就,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容