五 代码示例
上面讲到的知识点在这个示例都有涉及。另外我这里也只是分析部分重要的代码,更多的知识了解请自行下载代码(文章最下面有地址)并结合公开课一起看。
新建一个single view的工程,然后新增一个视图类,叫FaceView,如下图所示:
然后我们在storyboard里拖进来一个通用的视图控件,作为上面那个视图类对应的视图,如下图所示:
接着要做视图控制器类里增加这个FaceView的oulet以便我们可以操作视图,如下:
开始关注代码了。
我们打算在FaceView画一个笑脸,来反映幸福的程序,一个笑脸由下面几部分组成:
脸的轮廓(一个大圆)
眼睛(两个小圆)
嘴巴(贝塞尔曲线)
好,代码如下:
- (void)drawCircleAtPoint:(CGPoint)p withRadius:(CGFloat)radius inContext:(CGContextRef)context
{
//设置成当前的context
//使用UIKit来进行任意绘图,你会希望保存当前的UIKit上下文,包括所有已经绘制的内容,
//接着切换到一个全新的绘图上下文中
UIGraphicsPushContext(context);
CGContextBeginPath(context);
CGContextAddArc(context, p.x, p.y, radius, 0, 2*M_PI, YES);
CGContextStrokePath(context);
UIGraphicsPopContext();
}
//This method is called when a view is first displayed or when an event occurs that invalidates a visible part of the view
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
//draw face (circle)
//draw eyes (2 circles)
//no nose
//mouth (Bézier curve)
CGPoint midPoint;
//注意这里,因为画圆本身是基于faceView自己,所以计算坐标也是相对于faceView,所以要用bounds下的坐标
//当我们把faceView在storyboard里拉下一些,然后用frame对比看效果就很明显了
midPoint.x = self.bounds.origin.x + self.bounds.size.width/2;
midPoint.y = self.bounds.origin.y + self.bounds.size.height/2;
CGFloat size = self.bounds.size.width/2; //大圆半径
if (self.bounds.size.height < self.bounds.size.width) {
size = self.bounds.size.height/2;
}
size *= self.scale;
//设置线条宽度和颜色等
CGContextSetLineWidth(context,5.0);
[[UIColor blueColor] setStroke];
[self drawCircleAtPoint:midPoint withRadius:size inContext:context];
#define EYE_H 0.35
#define EYE_V 0.35
#define EYE_RADIUS 0.10
CGPoint eyePoint;
eyePoint.x = midPoint.x - size * EYE_H;
eyePoint.y = midPoint.y - size * EYE_V;
[self drawCircleAtPoint:eyePoint withRadius:size*EYE_RADIUS inContext:context];
eyePoint.x += size * EYE_H * 2;
[self drawCircleAtPoint:eyePoint withRadius:size*EYE_RADIUS inContext:context];
#define MOUTH_H 0.45
#define MOUTH_V 0.45
#define MOUTH_SMILE 0.25 //弯曲的比例,微笑的程度
CGPoint mouthStart;
mouthStart.x = midPoint.x - size * MOUTH_H;
mouthStart.y = midPoint.y + size * MOUTH_V;
CGPoint mouthEnd = mouthStart;
mouthEnd.x += size * MOUTH_H * 2;
CGPoint mouthCP1 = mouthStart;
mouthCP1.x += size * MOUTH_H * 2/3;
CGPoint mouthCP2 = mouthEnd;
mouthCP1.x -= size * MOUTH_H * 2/3;
float smile = [self.dataSource smileForFaceView:self];
if (smile < -1)
{
smile = -1;
}
else if (smile > 1)
{
smile = 1;
}
CGFloat smileOffset = MOUTH_SMILE * size * smile;
mouthCP1.y += smileOffset;
mouthCP2.y += smileOffset;
CGContextBeginPath(context);
CGContextMoveToPoint(context, mouthStart.x, mouthStart.y);
CGContextAddCurveToPoint(context, mouthCP1.x, mouthCP1.y, mouthCP2.x, mouthCP2.y, mouthEnd.x, mouthEnd.y);
CGContextStrokePath(context);
}
先上个图看一下效果:
这里有一点要注意,我们需要考虑横屏的情况,因为我是在xcode5环境下写的代码,就不用老师进的Struts and springs了,直接加约束,让storyboard帮我自动计算,如下所示:
然后再来看一下效果,
这个似乎也不是我们想要的,它自动拉伸了,我们还需要加一些代码调整,横屏时要重绘笑脸。
- (void)setup
{
//UIViewContentModeRedraw可以使屏幕旋转时调用drawRect
self.contentMode = UIViewContentModeRedraw;
}
- (void)awakeFromNib
{
[self setup];
}
这样再来看看效果:
现在我们来添加手势识别,让这个笑脸支持缩放功能。
需要在facView和根控制器里加入相应的代码,facView里:
//手势识别,缩放功能
- (void)pinch:(UIPinchGestureRecognizer *)gesture
{
if ((gesture.state == UIGestureRecognizerStateChanged) ||
(gesture.state == UIGestureRecognizerStateEnded))
{
/*
下面两行代码其实和这一行效果是一样的,所以注意理解第二行置1的作用.可以查一下gesture.scale是怎么取值的
self.scale = gesture.scale;
*/
self.scale *= gesture.scale;
NSLog(@"scale:%f", gesture.scale);
gesture.scale = 1;
}
}
然后在根控制器里:
[self.faceView addGestureRecognizer:[[UIPinchGestureRecognizer alloc] initWithTarget:self.faceView action:@selector(pinch:)]];
看看效果:
接下来继续添加功能,我们用代理和上下滑动的手势识别来实现通过上下滑动来控制笑脸的微笑程度。
手势识别好理解,为什么要用代理呢?可以这样理解,FaceView里的这个笑脸,是通过一个幸福指数(controller里的happiness)来控制,这个幸福指数是一个数据源,那FaceView是一个视图,视图本身是不能拥有数据源的,所以要把controller作为代理管理数据源。
下面是代码。
添加手势识别
添加一个上下滑动的手势,注意target不是faceView而是controller,所以处理函数也在controller里实现, UIPanGestureRecognizer主要用于拖动,就是捕捉手指的位移
[self.faceView addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleHappinessGesture:)]];
self.faceView.dataSource = self; //把控制器设为代理
注意下面inView后面都是指定self.faceView作为参数,这个参数的意义是你打算让这个手势转换在哪个坐标系工作,当然是我们的FaceView了
- (void)handleHappinessGesture:(UIPanGestureRecognizer *)gesture
{
if ((gesture.state == UIGestureRecognizerStateChanged) ||
(gesture.state == UIGestureRecognizerStateEnded))
{
CGPoint translation = [gesture translationInView:self.faceView];//转换成坐标系中的点位移变化
self.happiness -= translation.y / 2; //除2的作用时,减少变化的幅度
[gesture setTranslation:CGPointZero inView:self.faceView];//置0可以让变化幅度不累加
}
}
接下来实现协议中定义的函数,
- (float)smileForFaceView:(FaceView *)sender
{
return (self.happiness - 50)/50.0;//happiness是0~100, 微笑程度是-1~1,需要转换
}
使用代理的位置
float smile = [self.dataSource smileForFaceView:self];
if (smile < -1)
{
smile = -1;
}
else if (smile > 1)
{
smile = 1;
}
模拟器下运行,会发现随着鼠标的上下拖动,笑脸的微笑程度会变化。
代码下载地址:
https://github.com/pony-maggie/Happiness