之前一直看QQ的未读消息拖拽消失设计得很好,我一直觉得那个设计很好,他们的UI是真心强,于是,我也一直想写个一样的玩意来玩玩。最近刚好在复习View相关的知识,就拿这个来练手,下面先来看实现的效果图:
这是我希望实现的效果,这个效果的实现在第二个图能看出一点端倪。这里面的曲线绘制,使用的是贝塞尔曲线。下面用几个例子简单介绍下贝塞尔曲线,参考网上大神的文章,我对原文大神的代码做了一点点修改。
什么是贝塞尔曲线?
贝赛尔曲线(Bézier曲线)是电脑图形学中相当重要的参数曲线。更高维度的广泛化贝塞尔曲线就称作贝塞尔曲面,其中贝塞尔三角是一种特殊的实例。贝塞尔曲线于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由Paul de Casteljau于1959年运用de Casteljau算法开发,以稳定数值的方法求出贝塞尔曲线。
接下来从一个简单的二阶贝塞尔曲线开始
接下来从一个简单的二阶贝塞尔曲线开始
package com.example.draftcircleviewtest;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import android.widget.ImageView;
/**
* 贝塞尔曲线的测试
* 蓝色的点是辅助点
* @author Administrator
*
*/
public class MyBezierView extends FrameLayout {
private Paint mPaint;
private Path mPath;
private Point startPoint;
private Point endPoint;
private Point assistPoint;
private Context mContext;
public MyBezierView(Context context ) {
this(context,null);
}
public MyBezierView( Context context ,AttributeSet attrs )
{
this(context, attrs , 0);
}
public MyBezierView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
ImageView tipImageView;
private void init( Context context )
{
this.mContext = context;
this.mPaint = new Paint();
this.mPath = new Path();
startPoint = new Point(100, 300);
endPoint = new Point(1100, 300);
assistPoint = new Point(600, 800);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStrokeWidth(5);
//设置背景颜色,再设置背景为空,这样子是为了能一开始就draw
//否则会发现onDraw不被调用
setBackgroundColor(Color.WHITE);
getBackground().setAlpha(0);
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
mPaint.setColor(Color.BLACK);
mPaint.setStyle( Style.STROKE );
mPaint.setStrokeWidth( 5);
mPath.reset();
Log.e("MyView", "onDraw");
mPath.moveTo(startPoint.x, startPoint.y);
// 重要的就是这句
mPath.quadTo(assistPoint.x, assistPoint.y, endPoint.x, endPoint.y);
// 画路径
canvas.drawPath(mPath, mPaint);
// 画辅助点
mPaint.setColor(Color.BLUE);
mPaint.setStrokeWidth( 20);
canvas.drawPoint(assistPoint.x, assistPoint.y, mPaint);
canvas.drawPoint(startPoint.x, startPoint.y, mPaint);
canvas.drawPoint(endPoint.x, endPoint.y, mPaint);
super.onDraw(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
assistPoint.x = (int) event.getX();
assistPoint.y = (int) event.getY();
invalidate();
break;
default:
break;
}
return true;
}
}
效果:
这样一个二阶贝塞尔曲线需要三个点,起点,终点,一个辅助点,初始化画笔以后,我们就可以在onDraw上绘制曲线,很简单,就是如下代码:
mPath.moveTo(startPoint.x, startPoint.y);
// 重要的就是这句
mPath.quadTo(assistPoint.x, assistPoint.y, endPoint.x, endPoint.y);
// 画路径
canvas.drawPath(mPath, mPaint);
首先我们把画笔移动到起点,然后,在路线上设置为贝塞尔曲线,填入辅助点和终点,最后在canvas上画出就可以了。拖动的效果实现也很简单,我们重写onTouchEvent,捕捉keydown事件和keyup事件,在事件发生的时候,修改assistPoint位置为事件发生所在的位置就好了。
接下来我们绘制一条复杂一点的贝塞尔曲线。
思路
先根据相邻点(P1,P2, P3)计算出相邻点的中点(P4, P5),然后再计算相邻中点的中点(P6)。然后将(P4,P6, P5)组成的线段平移到经过P2的直线(P8,P2,P7)上。接着根据(P4,P6,P5,P2)的坐标计算出(P7,P8)的坐标。最后根据P7,P8等控制点画出三阶贝塞尔曲线。
点和线的解释
黑色点:要经过的点,例如温度 蓝色点:两个黑色点构成线段的中点 黄色点:两个蓝色点构成线段的中点 灰色点:贝塞尔曲线的控制点 红色线:黑色点的折线图 黑色线:黑色点的贝塞尔曲线,也是我们最终想要的效果。
那么我们知道,我们绘制贝塞尔曲线,除了需要知道p1 p2 p3 还需要知道 P8 P7 P9 (P5下面的那个红点当成是P9吧),现在的情况是,我们已经知道了P1,P2,,P3,我们需要知道P7 P8 ,其实P7 P8不是固定值,只要确保P7 P8 P9 不在线上就,那么我们可以这样来计算P7 P8 P9,我们先算中点,得到P4 P5的值,然后,我们取P6 的值,然后,我们知道P4 P5 P6 在同一条直线上,我们平移P4 P5 P6,让P6到P2位置,得到P7 P8 ,这样,只要P1、P2、P3 不在同一直线上,那么P8 也不会和P1 P2 在同一直线上,P7也不会和P2 P3在同一直线上。按照这个思路,我们可以知道:
P8 = P2 - P6 + P4。
实现的代码如下:
package com.example.draftcircleviewtest;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import android.widget.FrameLayout;
public class MultiBezierView extends FrameLayout {
private static final int LINEWIDTH = 5;
private static final int POINTWIDTH = 10;
private Paint mPaint;
private Path mPath;
private Context mContext;
/** 即将要穿越的点集合 */
private ArrayList<Point> mPoints;
/** 中点集合 */
private ArrayList<Point> mMidPoints;
/** 中点的中点集合 */
private ArrayList<Point> mMidMidPoints;
/** 移动后的点集合(控制点) */
private ArrayList<Point> mAssistPoints;
public MultiBezierView(Context context) {
this(context, null);
}
public MultiBezierView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MultiBezierView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
init();
}
private void init()
{
mPaint = new Paint();
mPath = new Path();
mPaint.setStyle( Style.STROKE );
mPaint.setStrokeWidth( 5 );
mPaint.setDither(true);
mPaint.setAntiAlias( true );
getScreenParams();
setBackgroundColor(Color.WHITE);
getBackground().setAlpha(0);
initPoints();
initMidPoints();
initMidMidPoints();
initAssistPoints( mPoints , mMidPoints , mMidMidPoints );
}
private void initPoints()
{
mPoints = new ArrayList<Point>();
int pointWidthSpace = mScreenWidth / 5;
int pointHeightSpace = 100;
for( int i = 0 ; i < 5 ; i ++ )
{
Point point;
// 一高一低五个点
if (i%2 != 0) {
point = new Point((int) (pointWidthSpace*(i + 0.5)), mScreenHeigh/2 - pointHeightSpace);
} else {
point = new Point((int) (pointWidthSpace*(i + 0.5)), mScreenHeigh/2);
}
mPoints.add(point);
}
}
private void initMidPoints()
{
mMidPoints = new ArrayList<Point>();
for( int i = 0 ; i < mPoints.size() -1 ; i ++ )
{
Point midPoint = new Point(( mPoints.get(i).x + mPoints.get( i + 1 ).x )/2,
(mPoints.get(i).y + mPoints.get(i + 1).y)/2);
mMidPoints.add(midPoint);
}
}
private void initMidMidPoints()
{
mMidMidPoints = new ArrayList<Point>();
for( int i = 0 ; i < mMidPoints.size()-1; i ++ )
{
Point midMidPoint = new Point( (mMidPoints.get(i).x + mMidPoints.get(i+1).x )/2,
(mMidPoints.get(i).y + mMidPoints.get(i+1).y )/2);
mMidMidPoints.add(midMidPoint);
}
}
private void initAssistPoints(List<Point> points , List<Point> midPoints , List<Point> midMidPoints )
{
mAssistPoints = new ArrayList<Point>();
for( int i = 0 ; i < points.size() ; i ++ )
{
if( i ==0 || i == points.size()-1 )
{
continue;
}else
{
Point assistBefore = new Point();
assistBefore.x = midPoints.get(i-1).x + (points.get(i).x- mMidMidPoints.get(i-1).x);
assistBefore.y = midPoints.get(i-1).y + (points.get(i).y- mMidMidPoints.get(i-1).y);
Point assistAfter = new Point();
assistAfter.x = midPoints.get(i).x + (points.get(i).x- mMidMidPoints.get(i-1).x);
assistAfter.y = midPoints.get(i).y + (points.get(i).y- mMidMidPoints.get(i-1).y);
mAssistPoints.add(assistBefore);
mAssistPoints.add(assistAfter);
}
}
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
//画原始点
drawPoints(canvas);
//画原始点连接的直线
drawLineForPoints(canvas);
//画中点
drawMidPoints(canvas);
//画中点的中点即图中的P6
drawMidMidPoints(canvas);
//画辅助点
drawAssistPoints( canvas);
drawBezierPath( canvas );
}
private void drawPoints(Canvas canvas)
{
mPaint.setStrokeWidth(POINTWIDTH);
for( Point point : mPoints)
{
canvas.drawPoint(point.x, point.y, mPaint);
}
}
private void drawLineForPoints( Canvas canvas )
{
mPaint.setStrokeWidth(LINEWIDTH);
mPaint.setColor(Color.RED );
mPath.moveTo(mPoints.get(0).x, mPoints.get(0).y);
for( int i = 1 ; i < mPoints.size(); i ++ )
{
mPath.lineTo(mPoints.get(i).x, mPoints.get(i).y);
}
canvas.drawPath(mPath, mPaint);
}
private void drawMidPoints(Canvas canvas)
{
mPaint.setStrokeWidth(POINTWIDTH);
mPaint.setColor(Color.BLUE);
for( Point point : mMidPoints)
{
canvas.drawPoint(point.x, point.y, mPaint);
}
}
private void drawMidMidPoints(Canvas canvas)
{
mPaint.setStrokeWidth(POINTWIDTH);
mPaint.setColor(Color.YELLOW);
for( Point point : mMidMidPoints)
{
canvas.drawPoint(point.x, point.y, mPaint);
}
}
private void drawAssistPoints(Canvas canvas)
{
mPaint.setStrokeWidth(POINTWIDTH);
mPaint.setColor(Color.GRAY);
for( Point point : mAssistPoints)
{
canvas.drawPoint(point.x, point.y, mPaint);
}
}
private void drawBezierPath( Canvas canvas )
{
mPaint.setStrokeWidth(LINEWIDTH);
mPaint.setColor(Color.BLACK);
mPath.reset();
mPath.moveTo(mPoints.get(0).x, mPoints.get(0).y);
for( int i = 0 ; i < mPoints.size()-1 ; i ++ )
{
if( i == 0 )
{
// 第一条为二阶贝塞尔
mPath.quadTo(mAssistPoints.get(i).x, mAssistPoints.get(i).y, mPoints.get(i+1).x, mPoints.get(i+1).y);
}
else//一直到最后一条之前,是三阶贝塞尔曲线
if( i < mPoints.size() -2 )
{
mPath.cubicTo(mAssistPoints.get(i*2-1).x,mAssistPoints.get(i*2-1).y,
mAssistPoints.get(i*2).x, mAssistPoints.get(i*2).y,
mPoints.get(i+1).x, mPoints.get(i+1).y);
}
else//最后一条也是二阶贝塞尔曲线
if( i == mPoints.size() -2 )
{
mPath.quadTo(mAssistPoints.get(mAssistPoints.size()-1).x, mAssistPoints.get(mAssistPoints.size()-1).y, mPoints.get(i+1).x, mPoints.get(i+1).y);
}
}
canvas.drawPath(mPath, mPaint);
}
public int mScreenWidth=-1,mScreenHeigh=-1;
private void getScreenParams( )
{
getWindowHeigh(mContext);
getWindowWidth(mContext);
}
public int getWindowWidth(Context context) {
if( mScreenWidth <= 0 )
{
WindowManager wm = (WindowManager) (context
.getSystemService(Context.WINDOW_SERVICE));
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
mScreenWidth = dm.widthPixels;
}
return mScreenWidth;
}
public int getWindowHeigh(Context context) {
if( mScreenHeigh <=0 )
{
WindowManager wm = (WindowManager) (context
.getSystemService(Context.WINDOW_SERVICE));
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
mScreenHeigh = dm.heightPixels;
}
return mScreenHeigh;
}
}
接下来,我们开始设计粘性的动画,我们想要的效果图如下:
其实,这个可以看成是两个圆+2条二阶贝塞尔曲线,经过分析,我们可以得到如下:
那么,如今的问题就是求垂直于圆心连线的线与圆的交点坐标 PA PB PC PD 这四个点坐标。
我们很容易证明如下:
点PA的圆心角满足如下图:
我们很容易得到如下:
∠α=arctan( (y0-y1)/( x1 - x0 ) ) = -arctan((y1-y0)/(x1-x0))
另γ = -∠α = arctan((y1-y0)/(x1-x0))
令∠β = 270°-∠α ,也就是β = 270°+γ β为点A的圆心角
B的圆心角为90-∠α
xA = x0+ r * cos( 270° - ∠α ) =x0+ r*(-sin∠α )= x0-r*sin∠α = x0+r*sin( -∠α ) = x0 + r* sin( arctan((y1-y0)/(x1-x0)) ) = x0 + r * sin γ
yA = x0 + r*sin(270° - ∠α) = x0 -r*cosa = x0 - r*cosγ
同理可以得到B坐标
xB = x0-r*sinγ
yB = x0 + r*cosγ
所以我们可以得到下面的代码**(完整的项目代码在文章末尾,包括本文所有的代码)**:
/**大圆的半径*/
public static final int BIG_CIRCLE_RADIUS = 40;
/**小圆心,固定不变的*/
private Point smallCircleCenter;
/**大圆心*/
private Point bigCircleCenter;
/**小圆上的两个点,他们的连线垂直两个圆心的连线。*/
private Point smallCirclePoint1,smallCirclePoint2;
/**大圆上的两个点,他们的连线垂直两个圆心的连线。*/
private Point bigCirclePoint1,bigCirclePoint2;
/**当前的圆心距*/
private float centersDistance;
/**当前小圆的半径*/
private int smallCircleRadius = SMALL_CIRCLE_MAX_RADIUS;
private void calculate()
{
if( bigCircleCenter == null )
return ;
centersDistance = (float) Math.sqrt(Math.pow(smallCircleCenter.x-bigCircleCenter.x,2) + Math.pow(smallCircleCenter.y-bigCircleCenter.y,2)) ;
if( centersDistance >= CENTERS_MAX_DISTANCE )
smallDismiss = true;
int smalldx = (int) (centersDistance/CENTERS_MAX_DISTANCE * ( SMALL_CIRCLE_MAX_RADIUS - SMALL_CIRCLE_MIN_RADIUS ));
smallCircleRadius = SMALL_CIRCLE_MAX_RADIUS- smalldx;
double s1 = 90;
if( bigCircleCenter.x != smallCircleCenter.x )
s1 = Math.atan( (bigCircleCenter.y-smallCircleCenter.y)/( bigCircleCenter.x-smallCircleCenter.x));
float smallOffsetX = (float) (smallCircleRadius * Math.sin(s1));
float smallOffsetY = (float) (smallCircleRadius * Math.cos(s1));
smallCirclePoint1 = new Point();
smallCirclePoint1.x = (int) (smallOffsetX + smallCircleCenter.x);
smallCirclePoint1.y = (int) (smallCircleCenter.y - smallOffsetY);
smallCirclePoint2 = new Point();
smallCirclePoint2.x = (int) ( smallCircleCenter.x-smallOffsetX);
smallCirclePoint2.y = (int) (smallCircleCenter.y + smallOffsetY);
float bigOffsetX = (float) (BIG_CIRCLE_RADIUS * Math.sin(s1));
float bigOffsetY = (float) (BIG_CIRCLE_RADIUS * Math.cos(s1));
bigCirclePoint1 = new Point();
bigCirclePoint1.x = (int) (bigOffsetX + bigCircleCenter.x);
bigCirclePoint1.y = (int) (bigCircleCenter.y - bigOffsetY);
bigCirclePoint2 = new Point();
bigCirclePoint2.x = (int) ( bigCircleCenter.x - bigOffsetX);
bigCirclePoint2.y = (int) (bigCircleCenter.y + bigOffsetY);
midCenter = new Point();
midCenter.x = (bigCircleCenter.x + smallCircleCenter.x )/2;
midCenter.y = (bigCircleCenter.y + smallCircleCenter.y )/2;
}
接下来就是绘制贝塞尔曲线,注意绘制的时候, 要使用lineTo,连接圆上的两个点,这样才能形成一个封闭的区间,之后才能填充颜色。
/**
* 绘制贝塞尔曲线
* @param canvas
*/
private void drawBezierPath( Canvas canvas )
{
mPaint.setStrokeWidth(PAINT_LINE_WIDTH);
mPaint.setColor(paintColor);
mPath.reset();
//下面的lineTo不可以少,因为我们要形成一个封闭的空间,这样才能填充
mPath.moveTo( smallCirclePoint1.x, smallCirclePoint1.y);
mPath.quadTo(midCenter.x, midCenter.y, bigCirclePoint1.x, bigCirclePoint1.y);
mPath.lineTo(bigCirclePoint2.x, bigCirclePoint2.y);
mPath.quadTo(midCenter.x, midCenter.y, smallCirclePoint2.x, smallCirclePoint2.y);
mPath.lineTo(smallCirclePoint1.x, smallCirclePoint1.y);
canvas.drawPath(mPath, mPaint);
}
当我们设置Paint的Style为Stroke的时候
上面的代码效果如下:
当我们设置
mPaint.setStyle( Paint.Style.FILL_AND_STORKE);
填充效果如下:
接下来我们画圆以后效果:
再加入动画效果,代码如下:
//动画对象
ObjectAnimator animator;
//是否重复播放
boolean isrepeat = false;
public void startAni( )
{
if( lastf == 0.0f)
{
animator = ObjectAnimator.ofFloat(this, "bigCircleCenter", 0, 2.0f);
animator.setRepeatCount(ValueAnimator.INFINITE);
isrepeat = true;
}else
{
animator = ObjectAnimator.ofFloat(this, "bigCircleCenter", lastf, 2.0f);
isrepeat = false;
}
animator.setDuration( (int)(2000*(( 2.0f - lastf)/2.0f)));
animator.start();
}
//上次播放动画的位置,当lastf<=1.0f执行动画,当lastf>1.0f的时候,播放逆动画
float lastf = 0.0f;
public void setBigCircleCenter( float f )
{
int center =0;
int smalldx = 0;
if( f <= 1.0f){
//计算大圆圆心的位置,这个情况下圆心位置在增大
center = (int) (f * 800 + 400);
//计算小圆半径缩小值,这个时候值在增大
smalldx = (int) ((SMALL_CIRCLE_MAX_RADIUS - SMALL_CIRCLE_MIN_RADIUS)*f);
}else{
//计算大圆圆心的位置,这个值在变小
center = (int)(1200-(f-1.0f)*800);
//计算小圆半径缩小值,这个时候值在变小
smalldx = (int) ((SMALL_CIRCLE_MAX_RADIUS - SMALL_CIRCLE_MIN_RADIUS)-(SMALL_CIRCLE_MAX_RADIUS - SMALL_CIRCLE_MIN_RADIUS)*(f-1.0f));
}
bigCircleCenter.x = center;
bigCircleCenter.y = center;
smallCircleRadius = SMALL_CIRCLE_MAX_RADIUS- smalldx;
lastf = f;
invalidate();
//暂停恢复动画以后,先执行一次动画,动画结束以后,关闭当前动画,
//再启动新的循环动画
if( !isrepeat)
{
if( lastf ==0.0f || lastf == 2.0f )
{
stopAni();
lastf = 0;
startAni();
}
}
}
当Paint的Style为Stroke效果如下:
设置Paint的Style为FILL_AND_STROKE,并且隐藏点,得到效果:
拖拽的实现也很简单,我们重写onTouchEvent事件,在Down和Move的时候,修改大圆心的位置,当移动太远,就隐藏小圆,在ACTION_UP的时候判断圆心距,如果圆心距不在指定范围内,就不显示大圆了,否则,就把大圆回归原位,同时如果小圆还显示着,就执行动画。下面只放出关键的代码:
onTouchEvent实现代码如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if( bigCircleCenter == null )
bigCircleCenter = new Point();
bigCircleCenter.x = (int) event.getX();
bigCircleCenter.y = (int) event.getY();
calculate();
if( centersDistance < SMALL_CIRCLE_MAX_RADIUS + 20 )
invalidate();
else{
bigCircleCenter =null;
return false;
}
break;
case MotionEvent.ACTION_MOVE:
if( bigCircleCenter == null )
bigCircleCenter = new Point();
bigCircleCenter.x = (int) event.getX();
bigCircleCenter.y = (int) event.getY();
invalidate();
break;
case MotionEvent.ACTION_UP:
if( bigCircleCenter == null || ( bigCircleCenter.x == beforeX && bigCircleCenter.y == beforeY) )
{
return false;
}
if( smallDismiss && centersDistance < CENTERS_MIN_DISTANCE )
{
smallDismiss = false;
bigCircleCenter = new Point(beforeX, beforeY);
invalidate();
}else if( !smallDismiss)
{
float f = centersDistance / CENTERS_MAX_DISTANCE ;
animator = ObjectAnimator.ofFloat(this, "bigCircleCenter", 0, 1.0f);
animator.setDuration( (int)(1000 * f));
animator.start();
}else
{
stopAni();
bigCircleCenter = null;
invalidate();
}
break;
default:
break;
}
return true;
}
修改前面的动画执行代码改为如下:
/**
* 松开的时候,执行回复的动画
* @param f
*/
public void setBigCircleCenter( float f )
{
int centerX =0;
int centerY =0;
if( bigCircleCenter.x == 45&&bigCircleCenter.y == 45 )
{
stopAni();
afterAni();
return ;
}
//计算新的中心点
centerX = (int) ( bigCircleCenter.x + (beforeX - bigCircleCenter.x )*f);
centerY = (int) ( bigCircleCenter.y + (beforeY - bigCircleCenter.y )*f);
bigCircleCenter.x = centerX;
bigCircleCenter.y = centerY;
invalidate();
if( f == 1.0f )
{
afterAni();
}
}
下面看下最终的效果:
这个控件还是做的比较简单,像腾讯那样做的话,我们还需要重写onLayout和onSizeChanged方法保存当前控件的布局参数,然后再使用WindowManager在触摸的时候添加到窗口上,并且窗口是整个屏幕大小的,这样就实现气泡拖拽全局的效果。还可以扩展一些事件接口,这样更灵活,这些这边就不实现了,以后有空完善了,再发一份。
代码还有一些BUG,仅供参考。附上项目的源码地址:
代码上的onTouchEvent有问题,请改成博客上的。
http://download.csdn.net/detail/savelove911/9626154