自定義view之實現日曆界面(二)

首先附上github地址:github.com/rgf456/Anim…
上一篇文章中講過的內容有兩點須要複習一下:javascript

  1. 劃分天天佔的方格。這個首先是計算本月當前日期總天數加上第一天是星期幾而後減去1而後對應的上月的天數便可算出本月贏有多少行,經過行列的分割計算出天天的方格中心座標點。
  2. 爲顯示的天天設定標記。目前咱們作的標記有上一個月、本月、下一個月、週末、特殊日期。

咱們將上面兩點得到的信息所有保存在一個數組對象中,這個數組對象就是咱們用來繪製的基準。java

繪製日曆界面

view的繪製都是在onDraw方法中,在ondraw方法中會傳遞一個Canvas參數,這個就是咱們須要繪製的畫布。在這裏咱們須要繪製出全部的天的狀態。git

在繪製的過程當中,咱們須要根據天天的標記(State)來分狀況繪製,因此此處要作一個便利循環來去除出天天的內容:github

private void drawDays(Canvas canvas, CellDay[] cellDays) {
        for (CellDay c : cellDays) {
            switch (c.getDayState()) {
                case LASTMONTH:
                    if (c.isSelected()) {
                        circlePaint.setColor(Color.TRANSPARENT);
                    } else {
                        circlePaint.setColor(Color.TRANSPARENT);
                        textPaint.setColor(Color.GRAY);
                    }
                    break;
                case CURRENTMONTH:
                    if (c.isSelected()) {
                        circlePaint.setColor(Color.YELLOW);
                        textPaint.setColor(Color.BLACK);
                    } else {
                        circlePaint.setColor(Color.RED);
                        textPaint.setColor(Color.RED);
                        canvas.drawText("班",
                                c.getPointX() + textPaint.measureText(tempDate) / 2,
                                c.getPointY() - textPaint.getTextSize() / 2,
                                textPaint);
                    }
                    break;
                case NEXTMONTH:
                    if (c.isSelected()) {
                        circlePaint.setColor(Color.TRANSPARENT);
                    } else {
                        circlePaint.setColor(Color.TRANSPARENT);
                        textPaint.setColor(Color.GRAY);
                    }
                    break;
                case CURRENTDAY:
                    circlePaint.setColor(Color.YELLOW);
                    textPaint.setColor(Color.BLACK);
                    break;
                case WEEKEND:
                    circlePaint.setColor(Color.CYAN);
                    textPaint.setColor(Color.CYAN);
                    canvas.drawText("休",
                            c.getPointX() + textPaint.measureText(tempDate) / 2,
                            c.getPointY() - textPaint.getTextSize() / 2,
                            textPaint);
                    break;
                case SPECIALDAY:
                    circlePaint.setColor(Color.GREEN);
                    textPaint.setColor(Color.GREEN);
                    canvas.drawText("假",
                            c.getPointX() + textPaint.measureText(tempDate) / 2,
                            c.getPointY() - textPaint.getTextSize() / 2,
                            textPaint);
            }
            canvas.drawCircle(tempPositionX, tempPositionY, radius - 10, selectPaint);
// canvas.drawText(tempDate,
// tempPositionX - textPaint.measureText(tempDate) / 2,
// tempPositionY + textPaint.getTextSize() / 2,
// selectTextPaint);
            canvas.drawText(c.getDate(),
                    c.getPointX() - textPaint.measureText(c.getDate()) / 2,
                    c.getPointY() + textPaint.getTextSize() / 2,
                    textPaint);
// canvas.drawCircle(c.getPointX(), c.getPointY(), radius - 10, circlePaint);
            //這個地方改成rectf能夠向下兼容
            canvas.drawArc(c.getPointX() - radius + 10,
                    c.getPointY() - radius + 10,
                    c.getPointX() + radius - 10,
                    c.getPointY() + radius - 10,
                    0, 270, false, circlePaint);
            c.setSelected(false);
            oldPositionX = newPositionX;
            oldPositionY = newPositionY;
        }
    }複製代碼

對因而否選中的狀態咱們採用的是特殊的標記,因此沒有在siwtch中直接使用,而是經過if來細分每一個switch的狀態。在LASTMONTH、NEXTMONTH中咱們對畫筆作了透明處理,這樣作的目的是防止在選中的狀態下選中狀態是透明的,那麼也就不會產生視覺高亮效果。(後面會講如何處理上個月和下個月不被選中)。canvas

case SPECIALDAY:
                    circlePaint.setColor(Color.GREEN);
                    textPaint.setColor(Color.GREEN);
                    canvas.drawText("假",
                            c.getPointX() + textPaint.measureText(tempDate) / 2,
                            c.getPointY() - textPaint.getTextSize() / 2,
                            textPaint);複製代碼

在SPECIALDAY中咱們直接繪製了特殊日期顯示的文字。固然此處能夠傳任意參數進來做爲繪製文字,在後期的改進中我會將這個做爲接口供使用者調用。數組

canvas.drawText(c.getDate(),
                    c.getPointX() - textPaint.measureText(c.getDate()) / 2,
                    c.getPointY() + textPaint.getTextSize() / 2,
                    textPaint);
// canvas.drawCircle(c.getPointX(), c.getPointY(), radius - 10, circlePaint);
            //這個地方改成rectf能夠向下兼容
            canvas.drawArc(c.getPointX() - radius + 10,
                    c.getPointY() - radius + 10,
                    c.getPointX() + radius - 10,
                    c.getPointY() + radius - 10,
                    0, 270, false, circlePaint);複製代碼

這段代碼是用來繪製日期的數字和一個半圓的效果。app

c.setSelected(false);複製代碼

這段代碼的做用是每次選中後清除選中狀態,防止下次同時選中兩個或者多個。此處能夠延伸,做爲網上較流行的選中多日期的狀態表示。ide

至此,咱們的界面基本完成了。看一下效果圖:post

本月的繪製完美完成。動畫

設置點擊事件

此處咱們須要重寫public boolean onTouchEvent(MotionEvent event)方法。

此處咱們假定自定義view的事件不會被viewgroup攔截,則觸摸事件的順序依次爲:
MotionEvent.ACTION_DOWN->MotionEvent.ACTION_MOVE->MotionEvent.ACTION_UP

我發現我特別懶,因此我只計算了DOWN和UP事件之間移動的距離

case MotionEvent.ACTION_DOWN:
                touchRawX = event.getX();
                touchRawY = event.getY();
                break;複製代碼

記錄原始按下的點的座標位置。

case MotionEvent.ACTION_UP:
                Log.d(TAG, "MotionEvent:ACTION_UP");
                Log.d(TAG, "rawY= " + touchRawY + ",touchY= " + event.getY());
                float touchX = event.getX();
                float touchY = event.getY();
                if (touchRawY - touchY < -200) {
                    //下滑事件
                    Log.d(TAG, "下滑事件");
                    setMaxView();
                    setCellDay();
                    this.clearCanvas = false;
                    invalidate();
                    cutGrid();
                    init();
                    this.layout(0, 0, this.viewWidth, this.viewHeight / 2);
                    setCellDay();
                    invalidate();
                }
                if (touchRawY - touchY > 200) {
                    //上劃事件
                    Log.d(TAG, "上劃事件");
                    setMinView();
                    this.clearCanvas = true;
                    invalidate();
                }
                if (Math.abs(touchRawX - touchX) < 100 && Math.abs(touchY - touchRawY) < 100) {
                    //點擊事件
                    Log.d(TAG, "點擊事件");
                    int touchRow = (int) (touchX / cellWidth);
                    int touchLine = (int) (touchY / cellHeight);
                    final int touchId = touchLine * ROW_COUNT + touchRow;
                    if (canClickNextOrPreMonth) {
                        setClickEvent(touchId);
                    } else {
                        if (touchId > firstDayOfWeek - 2 && touchId < monthDaySum + firstDayOfWeek - 1) {
                            setClickEvent(touchId);
                            tempCellDay = cellDays[touchId];
                            tempState = cellDays[touchId].getDayState();
                            cellDays[touchId].setSelected(true);
                            newPositionX = cellDays[touchId].getPointX();
                            newPositionY = cellDays[touchId].getPointY();
                            setselectAnimator(touchId);
                        }
                    }

                }
               break;複製代碼

在up事件中,首先根據觸摸的位置換算爲touchId,也就是在數組對象cellDays中的索引。而後作了三種狀態判斷,上滑下滑和點擊事件,其中上滑和下滑事件能夠先無論,由於到最後大家都不會用到。講一下點擊事件中的內容,首先判斷是否是設置了上月或者下月是否能夠點擊,canClickNextOrPreMonth爲真,則能夠點擊,爲假則不能夠點擊。爲假的時候,咱們只須要再設置一個條件,即點擊的座標點沒有落在上月或下月的索引內。
先看如何設置點擊事件:

private void setClickEvent(int touchId) {
        CustomDate customDate = cellDays[touchId].getCustomDate();
        if (clickCellListener != null) {
            clickCellListener.onClickCell(customDate);
        }
    }複製代碼

在這個方法中傳進了一個int值,這個值就是cellday數組的索引值。
只要實現接口便可得到這個傳出的customDate。

最後看setSelectAnimator方法:

private void setselectAnimator(final int touchId) {
        selectAnimatorX.removeAllUpdateListeners();
        selectAnimatorX.setFloatValues(oldPositionX, newPositionX);
        selectAnimatorX.setDuration(300);
        selectAnimatorX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                tempPositionX = (float) animation.getAnimatedValue();
            }
        });
        selectAnimatorY.removeAllUpdateListeners();
        selectAnimatorY.setFloatValues(oldPositionY, newPositionY);
        selectAnimatorY.setDuration(300);
        selectAnimatorY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                tempPositionY = (float) animation.getAnimatedValue();   
                postInvalidate();
            }
        });
        animatorSet = new AnimatorSet();
        animatorSet.playTogether(selectAnimatorX, selectAnimatorY);
        animatorSet.start();
    }複製代碼

這個方法看我文章的應該都很熟悉了,作一個valueanimator來做爲世間引擎不斷引發XY座標的變化,產生一個動畫移動的效果。而後調用inalidate做爲標記,刷新界面。

此時看一下效果圖:

至此,咱們日曆的點擊、選中狀態改變、特殊日期、週末等高亮顯示都已經完成。

滑動至上一個月和下一個月

我是利用了viewpager來實現滑動,我假定你們都對viewpager的使用很擅長。此處只看一下代碼便可:

private class CalendarAdapter extends PagerAdapter {
        private MyCalendar calendar;
        private LayoutParams lp;

        public CalendarAdapter(MyCalendar calendar) {
            this.calendar = calendar;
        }

        public CalendarAdapter(MyCalendar calendar, LayoutParams lp) {
            this.calendar = calendar;
            this.lp = lp;
        }

        @Override
        public int getCount() {
            return 1000;
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {

        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            if (myCalendars.size() > position) {
                MyCalendar myCalendar = myCalendars.get(position);
                if (myCalendar != null) {
                    return myCalendar;
                }
            }
            calendar = new MyCalendar(context);
            calendar.setClickCellListener(MyViewPager.this);
            customeDate = new CustomDate(position / 12 + 2000, position % 12, 1);
            calendar.setDate(customeDate);
            calendar.setLayoutParams(lp);
            calendar.setWeekendHighLight(weekendHightLight);
            calendar.setSpecialDay(new int[]{10, 20});
            calendar.setCanClickNextOrPreMonth(false);
            while (myCalendars.size() <= position) {
                myCalendars.add(null);
            }
            myCalendars.set(position, calendar);
            ViewParent vp = calendar.getParent();
            if (vp != null) {
                ViewGroup parent = (ViewGroup) vp;
                parent.removeView(calendar);
            }
            container.addView(calendar);
            return calendar;
        }


    }複製代碼

這裏是前一個取巧的方式,我設定的是1000個月,從2000年開始計算。而後根據position來計算是哪一個月,customeDate = new CustomDate(position / 12 + 2000, position % 12, 1);

就是這段代碼,而後將calendar的接口全都設置了一下。

再來看一下如何使用:

viewPager.setCurrentItem((calendar.get(Calendar.YEAR) - 2000) * 12 + calendar.get(Calendar.MONTH));複製代碼

這個設置當前日期我並未封裝到calendar中,正在不斷改進。

再爲viewpager添加一個ViewPager.OnPageChangeListener監聽器。

public void onPageSelected(int position) {
        StringBuilder builder = new StringBuilder();
        builder.append(position / 12 + 2000);
        builder.append("年");
        builder.append(position % 12 + 1);
        builder.append("月");
        date.setText(builder.toString());
    }複製代碼

在這裏設置顯示的頭的日期。

好了,全部的工做完成了。

講的不是很好,思路基本都是這樣實現的,有問題能夠直接聯繫我。

相關文章
相關標籤/搜索