Android 4.0日历(calendar)源码分析之日视图

日视图

相比其他的视图,日视图会显得很“简单”,说简单是因为在日视图下,除了actionbar之外没有任何其他可见的控件,唯一的控件是ViewSwitcher,但是是不可见的。ViewSwitcher里面装着两个显示日信息的自定义View—DayView。这两个View交替着切换,这使得我们能在日视图中一天一天的横向滚动。

ViewSwitcher不说了,这里要说的是这个DayView。

DayView是一个完全自定义的View,直接继承至View类。一般我们都是继承一些现成的控件,比如TextView,但是这里直接是将需要的东西一点一点的画出来,所以要弄懂,可能需要对android canvas绘图方面有比较多的了解。DayView的重点和难点有如下几个:

如何画出每天需要的信息,比如在恰当的位置画出每个小时的数字和横线,以及当天存在的事件。

如何实现在手指水平左右拖动时,能够看到天与天之间的过度效果;如何在垂直滚动时,响应用户的意图,并保证基本不和水平滚动冲突。

第一个问题纯粹是canvas方面的问题,很琐碎,但是逻辑不是那么强。

第二个问题涉及到触摸设备中的手势处理,要做到日历这么人性化的效果,还是比较难的。

DayView中的手势处理结合了比较原始的onTouchEvent()方法和GestureDetector(封装好了常用的几种手势)。onTouchEvent首先捕获触摸事件,将这个事件,传递给GestureDetector,然后交给GestureDetector分析。根据GestureDetector中的数据,我们得到一些有用的数值(比如在scroll滚动事件发生的时候,我们不断获取滚动点的水平偏移量,然后响应的View也移动相同的偏移量,从而实现水平滑动),然后根据这些数据来重绘View-即调用view的onDraw函数。

给出一小段代码让你找到感觉,不必细究每句话的含义,只需明白大致的处理方式,在ondraw开始几行有如下代码:

float yTranslate = -mViewStartY + DAY_HEADER_HEIGHT + mAlldayHeight; 
// offset canvas by the current drag and header position 
canvas.translate(-mViewStartX, yTranslate); 
// clip to everything below the allDay area 
Rect dest = mDestRect; 
dest.top = (int) (mFirstCell - yTranslate); 
dest.bottom = (int) (mViewHeight - yTranslate); 
dest.left = 0; 
dest.right = mViewWidth; 
canvas.save(); 
canvas.clipRect(dest); 
// Draw the movable part of the view 
doDraw(canvas);

其中canvas.translate(-mViewStartX, yTranslate)可以实现view的平移。

手势处理我们先进入捕获触摸事件的onTouchEvent函数:

onScroll()

onScroll函数不多,我就直接将整个函数贴出来了:

@Override 
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 
    if (DEBUG) Log.e(TAG, "GestureDetector.onScroll"); 
    if (mTouchStartedInAlldayArea) { 
        if (Math.abs(distanceX) < Math.abs(distanceY)) { 
            return false; 
        } 
        // don't scroll vertically if this started in the allday area 
        distanceY = 0; 
    } 
              Log.i("scroll", "DayView::onScroll:distanceX="+distanceX+"and distanceY="+distanceY); 
    DayView.this.doScroll(e1, e2, distanceX, distanceY); 
    return true; 
}

onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)有四个参数,这里其实只用了后面两个distanceX,distanceY,字面意思上分别指水平和垂直滚动的距离。

根据我的log信息来看,当我手指向左的时候distanceX为正,手指向右的时候distanceX为负。手指向上的时候distanceY为正,手指向下的时候distanceY为负。