上一篇文章中講過的內(nèi)容有兩點需要復習一下:
- 劃分每天占的方格。這個首先是計算本月當前日期總天數(shù)加上第一天是星期幾然后減去1然后對應的上月的天數(shù)即可算出本月贏有多少行,通過行列的分割計算出每天的方格中心坐標點。
- 為顯示的每天設定標記。目前我們做的標記有上一個月、本月、下一個月、周末、特殊日期。
我們將上面兩點獲得的信息全部保存在一個數(shù)組對象中,這個數(shù)組對象就是我們用來繪制的基準。
繪制日歷界面
view的繪制都是在onDraw方法中,在ondraw方法中會傳遞一個Canvas參數(shù),這個就是我們需要繪制的畫布。在這里我們需要繪制出所有的天的狀態(tài)。
在繪制的過程中,我們需要根據(jù)每天的標記(State)來分情況繪制,所以此處要做一個便利循環(huán)來去除出每天的內(nèi)容:
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;
}
}
對于是否選中的狀態(tài)我們采用的是特殊的標記,所以沒有在siwtch中直接使用,而是通過if來細分每個switch的狀態(tài)。在LASTMONTH、NEXTMONTH中我們對畫筆做了透明處理,這樣做的目的是防止在選中的狀態(tài)下選中狀態(tài)是透明的,那么也就不會產(chǎn)生視覺高亮效果。(后面會講如何處理上個月和下個月不被選中)。
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中我們直接繪制了特殊日期顯示的文字。當然此處可以傳任意參數(shù)進來作為繪制文字,在后期的改進中我會將這個作為接口供使用者調(diào)用。
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);
這段代碼是用來繪制日期的數(shù)字和一個半圓的效果。
c.setSelected(false);
這段代碼的作用是每次選中后清除選中狀態(tài),防止下次同時選中兩個或者多個。此處可以延伸,作為網(wǎng)上較流行的選中多日期的狀態(tài)表示。
至此,我們的界面基本完成了??匆幌滦Ч麍D:
本月的繪制完美完成。
設置點擊事件
此處我們需要重寫public boolean onTouchEvent(MotionEvent event)方法。
此處我們假定自定義view的事件不會被viewgroup攔截,則觸摸事件的順序依次為:
MotionEvent.ACTION_DOWN->MotionEvent.ACTION_MOVE->MotionEvent.ACTION_UP
我發(fā)現(xiàn)我特別懶,所以我只計算了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事件中,首先根據(jù)觸摸的位置換算為touchId,也就是在數(shù)組對象cellDays中的索引。然后做了三種狀態(tài)判斷,上滑下滑和點擊事件,其中上滑和下滑事件可以先不管,因為到最后你們都不會用到。講一下點擊事件中的內(nèi)容,首先判斷是不是設置了上月或者下月是否可以點擊,canClickNextOrPreMonth為真,則可以點擊,為假則不可以點擊。為假的時候,我們只需要再設置一個條件,即點擊的坐標點沒有落在上月或下月的索引內(nèi)。
先看如何設置點擊事件:
private void setClickEvent(int touchId) {
CustomDate customDate = cellDays[touchId].getCustomDate();
if (clickCellListener != null) {
clickCellListener.onClickCell(customDate);
}
}
在這個方法中傳進了一個int值,這個值就是cellday數(shù)組的索引值。
只要實現(xiàn)接口即可獲得這個傳出的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坐標的變化,產(chǎn)生一個動畫移動的效果。然后調(diào)用inalidate作為標記,刷新界面。
此時看一下效果圖:
至此,我們?nèi)諝v的點擊、選中狀態(tài)改變、特殊日期、周末等高亮顯示都已經(jīng)完成。
滑動至上一個月和下一個月
我是利用了viewpager來實現(xiàn)滑動,我假定大家都對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年開始計算。然后根據(jù)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監(jiān)聽器。
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());
}
在這里設置顯示的頭的日期。
好了,所有的工作完成了。
講的不是很好,思路基本都是這樣實現(xiàn)的,有問題可以直接聯(lián)系我。