放肆地使用UIBezierPath和CAShapeLayer画各种图形

http://www.cocoachina.com/ios/20160214/15251.html

CAShapeLayer 是 CALayer 的子类,但是比 CALayer 更灵活,可以画出各种图形,当然,你也可以使用其他方式来画,随你。

杂谈

在 CAShapeLayer 中,也可以像 CALayer 一样指定它的 frame 来画,就像这样:

1
2
3
4
5
let layer = CAShapeLayer()
layer.frame = CGRectMake(110, 100, 150, 100)
layer.backgroundColor = UIColor.blackColor().CGColor
 
view.layer.addSublayer(layer)

然后你就会得到如图这样的黑色矩形

QQ截图20160214095926.png

但是,CAShapeLayer 有一个神奇的属性 path 用这个属性配合上 UIBezierPath 这个类就可以达到超神的效果。

UIBezierPath 顾名思义,这是用贝塞尔曲线的方式来构建一段弧线,你可以用任意条弧线来组成你想要的形状,比如,你想用它来和上面一样画一个矩形,那就可以这样子来做:

1
2
3
4
5
let path = UIBezierPath(rect: CGRectMake(110, 100, 150, 100))
let layer = CAShapeLayer()
layer.path = path.CGPath
layer.fillColor = UIColor.blackColor().CGColor
view.layer.addSublayer(layer)

要注意的是,这里就不要用backgroundColor 这个属性了,而要使用 fillColor 和 strokeColor ,前者代表设置这个 Layer 的填充色,后者代表设置它的边框色

1
2
layer.fillColor = UIColor.clearColor().CGCo
lorlayer.strokeColor = UIColor.blackColor().CGColor

可以试一下把上面代码设置颜色的部分改成这个样子,那么运行程序的时候就会是这种样子

302.png

玩一下UIBezierPath

在说回 UIBezierPath ,在 UIBezierPath 的 init 方法中,就有很多方便你画各种图形的方法,比如你可以画一个带圆角的图形

1
2
3
4
5
let path = UIBezierPath(roundedRect: CGRectMake(110, 100, 150, 100), cornerRadius: 50)
let layer = CAShapeLayer()
layer.path = path.CGPath
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.blackColor().CGColor

还可以指定起始角和半径画圆

1
2
3
4
5
6
7
8
let radius: CGFloat = 60.0
let startAngle: CGFloat = 0.0
let endAngle: CGFloat = CGFloat(M_PI * 2)
let path = UIBezierPath(arcCenter: view.center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise:  true )
let layer = CAShapeLayer()
layer.path = path.CGPath
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.blackColor().CGColor

120336-0d0087ee3b02abc4.png

在这里涉及到角度的问题,起始角和结束角,这里的角度使使用弧度制来表示,这里我收藏了一张图片,以方便参考。

120336-7f77dcb0cc28e0ef.png

怎么画曲线

贝塞尔曲线的画法是由起点、终点、控制点三个参数来画的,为了解释清楚这个点,我写了几行代码来解释它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
let startPoint = CGPointMake(50, 300)
let endPoint = CGPointMake(300, 300)
let controlPoint = CGPointMake(170, 200)
 
let layer1 = CALayer()
layer1.frame = CGRectMake(startPoint.x, startPoint.y, 5, 5)
layer1.backgroundColor = UIColor.redColor().CGColor
 
let layer2 = CALayer()
layer2.frame = CGRectMake(endPoint.x, endPoint.y, 5, 5)
layer2.backgroundColor = UIColor.redColor().CGColor
 
let layer3 = CALayer()
layer3.frame = CGRectMake(controlPoint.x, controlPoint.y, 5, 5)
layer3.backgroundColor = UIColor.redColor().CGColor
 
let path = UIBezierPath()
let layer = CAShapeLayer()
 
path.moveToPoint(startPoint)
path.addQuadCurveToPoint(endPoint, controlPoint: controlPoint)
 
layer.path = path.CGPath
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.blackColor().CGColor
 
view.layer.addSublayer(layer)
view.layer.addSublayer(layer1)
view.layer.addSublayer(layer2)
view.layer.addSublayer(layer3)

我很随意的定义了三个点,为了清楚显示它们的位置,我放了三个矩形在上面以便观察,然后调用 path.moveToPoint(startPoint) 让它移动到起始点,然后调用path.addQuadCurveToPoint(endPoint, controlPoint: controlPoint) 这个方法告诉它结束点和控制点,这样它就能画出一条有弧度的线条了,如果把fillColor设置一个颜色,那么它就会变成一个很丑的形状了,示例图如下

120336-0d292e7a1fab289b.png

控制点决定了它的曲率,曲线的顶点不等于控制点的位置,具体可以看一下贝塞尔曲线的定义,你还可以使用两个控制点来画,两个控制点可以使用方法 path.addCurveToPoint(endPoint, controlPoint1: controlPoint, controlPoint2: controlPoint2)来搞定

这样它会是这个样子

120336-f19bd76f1caa0c35.png

再来说说 CAShapeLayer

CAShapeLayer 是个神奇的东西,给它一个path它就能变成你想要的形状,它还有很多可以玩的地方。综合使用可以组合成不同的动画,比如下面这样

01.gif

demo1.gif

02.gif

demo2.gif

03.gif

demo3.gif

这三个动画就是使用了 strokeEnd、strokeStart和lineWidth 三个属性,第一个动画用了strokeEnd这个属性的值范围是0-1,动画显示了从0到1之间每一个值对这条曲线的影响,strokeStart的方法则是相反的,如果把这两个值首先都设置成0.5然后慢慢改变成0和1的时候就会变成第二个动画,配合lineWidth则曲线会慢慢变粗,这里的很多属性都是支持动画的。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private func animation1() {
         let animation = CABasicAnimation(keyPath:  "strokeEnd" )
         animation.fromValue = 0
         animation.toValue = 1
         animation.duration = 2
         layer.addAnimation(animation, forKey:  "" )
     }
     
     private func animation2() {
         layer.strokeStart = 0.5
         layer.strokeEnd = 0.5
         
         let animation = CABasicAnimation(keyPath:  "strokeStart" )
         animation.fromValue = 0.5
         animation.toValue = 0
         animation.duration = 2
         
         let animation2 = CABasicAnimation(keyPath:  "strokeEnd" )
         animation2.fromValue = 0.5
         animation2.toValue = 1
         animation2.duration = 2
         
         layer.addAnimation(animation, forKey:  "" )
         layer.addAnimation(animation2, forKey:  "" )
     }
     
     private func animation3() {
         let animation = CABasicAnimation(keyPath:  "lineWidth" )
         animation.fromValue = 1
         animation.toValue = 10
         animation.duration = 2
         layer.addAnimation(animation, forKey:  "" )
     }

应用一下

前一阵子在仿时光网这个APP,其中有一个Layer的形状很怪异,是这样的

120336-7b526aaef0bbdc19.png

很明显它可以用 CAShapeLayer + UIBezierPath 来做,思路大概是这样,先移动到左上方的位置,然后向下划线,然后往右划线,然后往上划线,还剩一个盖子,这个盖子就用一个控制点控制曲率,非常简单,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
let finalSize = CGSizeMake(CGRectGetWidth(view.frame), 400)
let layerHeight = finalSize.height * 0.2
let layer = CAShapeLayer()
let bezier = UIBezierPath()
bezier.moveToPoint(CGPointMake(0, finalSize.height - layerHeight))
bezier.addLineToPoint(CGPointMake(0, finalSize.height - 1))
bezier.addLineToPoint(CGPointMake(finalSize.width, finalSize.height - 1))
bezier.addLineToPoint(CGPointMake(finalSize.width, finalSize.height - layerHeight))
bezier.addQuadCurveToPoint(CGPointMake(0,finalSize.height - layerHeight),
     controlPoint: CGPointMake(finalSize.width / 2, (finalSize.height - layerHeight) - 40))
layer.path = bezier.CGPath
layer.fillColor = UIColor.blackColor().CGColor
view.layer.addSublayer(layer)

就能画出这样的形状来

120336-06410bfad53fb73e.png

再来一个复杂点的,微信下拉拍小视频的那只眼睛很有趣,来模仿一下那个效果吧,它是这样的

120336-3c41264e40f49e68.gif

首先你得画出这只眼睛,这是眼睛包括5个部分组成(这个是用OC写的)

1
2
3
4
5
@property (strong, nonatomic) CAShapeLayer *eyeFirstLightLayer;
@property (strong, nonatomic) CAShapeLayer *eyeSecondLightLayer;
@property (strong, nonatomic) CAShapeLayer *eyeballLayer;
@property (strong, nonatomic) CAShapeLayer *topEyesocketLayer;
@property (strong, nonatomic) CAShapeLayer *bottomEyesocketLayer;

然后,还是通过 UIBezierPath 和 CAShapeLayer 这样的老套路来画,代码较多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
- (CAShapeLayer *)eyeFirstLightLayer {
     if  (!_eyeFirstLightLayer) {
         _eyeFirstLightLayer = [CAShapeLayer layer];
         CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
         UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
                                                             radius:CGRectGetWidth(self.frame) * 0.2
                                                         startAngle:(230.f / 180.f) * M_PI
                                                           endAngle:(265.f / 180.f) * M_PI
                                                          clockwise:YES];
         _eyeFirstLightLayer.borderColor = [UIColor blackColor].CGColor;
         _eyeFirstLightLayer.lineWidth = 5.f;
         _eyeFirstLightLayer.path = path.CGPath;
         _eyeFirstLightLayer.fillColor = [UIColor clearColor].CGColor;
         _eyeFirstLightLayer.strokeColor = [UIColor whiteColor].CGColor;
     }
     return  _eyeFirstLightLayer;
}
 
- (CAShapeLayer *)eyeSecondLightLayer {
     if  (!_eyeSecondLightLayer) {
         _eyeSecondLightLayer = [CAShapeLayer layer];
         CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
         UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
                                                             radius:CGRectGetWidth(self.frame) * 0.2
                                                         startAngle:(211.f / 180.f) * M_PI
                                                           endAngle:(220.f / 180.f) * M_PI
                                                          clockwise:YES];
         _eyeSecondLightLayer.borderColor = [UIColor blackColor].CGColor;
         _eyeSecondLightLayer.lineWidth = 5.f;
         _eyeSecondLightLayer.path = path.CGPath;
         _eyeSecondLightLayer.fillColor = [UIColor clearColor].CGColor;
         _eyeSecondLightLayer.strokeColor = [UIColor whiteColor].CGColor;
     }
     return  _eyeSecondLightLayer;
}
 
- (CAShapeLayer *)eyeballLayer {
     if  (!_eyeballLayer) {
         _eyeballLayer = [CAShapeLayer layer];
         CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
         UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
                                                             radius:CGRectGetWidth(self.frame) * 0.3
                                                         startAngle:(0.f / 180.f) * M_PI
                                                           endAngle:(360.f / 180.f) * M_PI
                                                          clockwise:YES];
         _eyeballLayer.borderColor = [UIColor blackColor].CGColor;
         _eyeballLayer.lineWidth = 1.f;
         _eyeballLayer.path = path.CGPath;
         _eyeballLayer.fillColor = [UIColor clearColor].CGColor;
         _eyeballLayer.strokeColor = [UIColor whiteColor].CGColor;
         _eyeballLayer.anchorPoint = CGPointMake(0.5, 0.5);
     }
     return  _eyeballLayer;
}
 
- (CAShapeLayer *)topEyesocketLayer {
     if  (!_topEyesocketLayer) {
         _topEyesocketLayer = [CAShapeLayer layer];
         CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
         UIBezierPath *path = [UIBezierPath bezierPath];
         [path moveToPoint:CGPointMake(0, CGRectGetHeight(self.frame) / 2)];
         [path addQuadCurveToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame) / 2)
                      controlPoint:CGPointMake(CGRectGetWidth(self.frame) / 2, center.y - center.y - 20)];
         _topEyesocketLayer.borderColor = [UIColor blackColor].CGColor;
         _topEyesocketLayer.lineWidth = 1.f;
         _topEyesocketLayer.path = path.CGPath;
         _topEyesocketLayer.fillColor = [UIColor clearColor].CGColor;
         _topEyesocketLayer.strokeColor = [UIColor whiteColor].CGColor;
     }
     return  _topEyesocketLayer;
}
 
- (CAShapeLayer *)bottomEyesocketLayer {
     if  (!_bottomEyesocketLayer) {
         _bottomEyesocketLayer = [CAShapeLayer layer];
         CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2);
         UIBezierPath *path = [UIBezierPath bezierPath];
         [path moveToPoint:CGPointMake(0, CGRectGetHeight(self.frame) / 2)];
         [path addQuadCurveToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame) / 2)
                      controlPoint:CGPointMake(CGRectGetWidth(self.frame) / 2, center.y + center.y + 20)];
         _bottomEyesocketLayer.borderColor = [UIColor blackColor].CGColor;
         _bottomEyesocketLayer.lineWidth = 1.f;
         _bottomEyesocketLayer.path = path.CGPath;
         _bottomEyesocketLayer.fillColor = [UIColor clearColor].CGColor;
         _bottomEyesocketLayer.strokeColor = [UIColor whiteColor].CGColor;
     }
     return  _bottomEyesocketLayer;
}

然后更改一下某些属性的值,方便稍后的动画

1
2
3
4
5
6
7
8
- (void)setupAnimation {
     self.eyeFirstLightLayer.lineWidth = 0.f;
     self.eyeSecondLightLayer.lineWidth = 0.f;
     self.eyeballLayer.opacity = 0.f;
     _bottomEyesocketLayer.strokeStart = 0.5f;
     _bottomEyesocketLayer.strokeEnd = 0.5f;
     _topEyesocketLayer.strokeStart = 0.5f;
     _topEyesocketLayer.strokeEnd = 0.5f;}

最后根据 UIScrollView 的 contentOffset 来控制各种属性,办法较笨,但管用。

46.png

最后

总之使用 UIbezierPath 和 CAShapeLayer 可以画出你想要的任何形状,没有它做不到,只有你想不到,搞定了它们你就可以轻松定制你想要的任何控件了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本系统的研发具有重大的意义,在安全性方面,用户使用浏览器访问网站时,采用注册和密码等相关的保护措施,提高系统的可靠性,维护用户的个人信息和财产的安全。在方便性方面,促进了校园失物招领网站的信息化建设,极大的方便了相关的工作人员对校园失物招领网站信息进行管理。 本系统主要通过使用Java语言编码设计系统功能,MySQL数据库管理数据,AJAX技术设计简洁的、友好的网址页面,然后在IDEA开发平台中,编写相关的Java代码文件,接着通过连接语言完成与数据库的搭建工作,再通过平台提供的Tomcat插件完成信息的交互,最后在浏览器中打开系统网址便可使用本系统。本系统的使用角色可以被分为用户和管理员,用户具有注册、查看信息、留言信息等功能,管理员具有修改用户信息,发布寻物启事等功能。 管理员可以选择任一浏览器打开网址,输入信息无误后,以管理员的身份行使相关的管理权限。管理员可以通过选择失物招领管理,管理相关的失物招领信息记录,比如进行查看失物招领信息标题,修改失物招领信息来源等操作。管理员可以通过选择公告管理,管理相关的公告信息记录,比如进行查看公告详情,删除错误的公告信息,发布公告等操作。管理员可以通过选择公告类型管理,管理相关的公告类型信息,比如查看所有公告类型,删除无用公告类型,修改公告类型,添加公告类型等操作。寻物启事管理页面,此页面提供给管理员的功能有:新增寻物启事,修改寻物启事,删除寻物启事。物品类型管理页面,此页面提供给管理员的功能有:新增物品类型,修改物品类型,删除物品类型。
好的,我可以为您编写一个基于Java语言的AI贪吃蛇。以下是实现该游戏所需的步骤: 1. 创建游戏面并绘制初始的贪吃蛇和食物。 2. 实现贪吃蛇的移动,这可以通过定义一个Snake类来实现,类中包含蛇的长度、位置和移动方式等属性和方法。 3. 实现贪吃蛇的碰撞检测,当蛇头撞到墙壁或自己的身体时游戏结束。 4. 实现食物的随机生成和与贪吃蛇的碰撞检测。 5. 在界面上显示得分和游戏结束信息。 以下是Java代码示例,希望能够满足您的需求: ``` import java.awt.Color; import java.awt.Graphics; import java.awt.Point; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.swing.JFrame; import javax.swing.JPanel; public class AI_Snake extends JPanel implements Runnable, KeyListener { private static final long serialVersionUID = 1L; private static final int WIDTH = 300; private static final int HEIGHT = 300; private Thread thread; private boolean running; private boolean right = true, left = false, up = false, down = false; private Snake snake; private List<Point> foods; private int score; private boolean gameover; public AI_Snake() { setPreferredSize(new Dimension(WIDTH, HEIGHT)); setFocusable(true); addKeyListener(this); snake = new Snake(); foods = new ArrayList<Point>(); start(); } private void start() { running = true; thread = new Thread(this); thread.start(); } private void stop() { running = false; try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } private void generateFood() { Random rand = new Random(); int x = rand.nextInt(WIDTH / 10) * 10; int y = rand.nextInt(HEIGHT / 10) * 10; foods.add(new Point(x, y)); } private boolean checkCollision() { int headX = snake.getHead().x; int headY = snake.getHead().y; if (headX < 0 || headX >= WIDTH || headY < 0 || headY >= HEIGHT) { return true; } for (Point p : snake.getBody()) { if (headX == p.x && headY == p.y) { return true; } } return false; } private boolean checkFood() { int headX = snake.getHead().x; int headY = snake.getHead().y; for (Point p : foods) { if (headX == p.x && headY == p.y) { foods.remove(p); snake.grow(); score++; return true; } } return false; } private void draw(Graphics g) { g.clearRect(0, 0, WIDTH, HEIGHT); g.setColor(Color.BLACK); g.drawRect(0, 0, WIDTH, HEIGHT); g.drawString("Score: " + score, 10, 20); if (gameover) { g.drawString("Game Over", WIDTH / 2 - 30, HEIGHT / 2); } else { snake.draw(g); for (Point p : foods) { g.fillRect(p.x, p.y, Snake.SIZE, Snake.SIZE); } } } @Override public void run() { while (running) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (gameover) { stop(); break; } snake.move(right, left, up, down); if (checkCollision()) { gameover = true; continue; } if (checkFood()) { generateFood(); } repaint(); } } @Override public void keyPressed(KeyEvent e) { switch(e.getKeyCode()) { case KeyEvent.VK_RIGHT: right = true; left = false; up = false; down = false; break; case KeyEvent.VK_LEFT: right = false; left = true; up = false; down = false; break; case KeyEvent.VK_UP: right = false; left = false; up = true; down = false; break; case KeyEvent.VK_DOWN: right = false; left = false; up = false; down = true; break; default: break; } } @Override public void keyTyped(KeyEvent e) {} @Override public void keyReleased(KeyEvent e) {} public static void main(String[] args) { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setResizable(false); frame.setTitle("AI Snake"); frame.add(new AI_Snake(), BorderLayout.CENTER); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } private static class Snake { private static final int SIZE = 10; private List<Point> body; public Snake() { body = new ArrayList<Point>(); body.add(new Point(50, 50)); body.add(new Point(40, 50)); body.add(new Point(30, 50)); } public List<Point> getBody() { return body; } public Point getHead() { return body.get(0); } public void grow() { Point last = body.get(body.size() - 1); body.add(new Point(last.x, last.y)); } public void move(boolean right, boolean left, boolean up, boolean down) { for (int i = body.size() - 1; i > 0; i--) { Point curr = body.get(i); Point prev = body.get(i - 1); curr.x = prev.x; curr.y = prev.y; } Point head = getHead(); if (right) head.x += SIZE; if (left) head.x -= SIZE; if (up) head.y -= SIZE; if (down) head.y += SIZE; } public void draw(Graphics g) { for (Point p : body) { g.fillRect(p.x, p.y, SIZE, SIZE); } } } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值