-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
236 lines (216 loc) · 163 KB
/
atom.xml
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[Animatious 一起动画开源组]]></title>
<subtitle><![CDATA[我们是一起动画开源组Animatious。我们致力于成为中国最顶尖的动效开源团队。]]></subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://anius.io/"/>
<updated>2015-12-03T03:21:32.000Z</updated>
<id>http://anius.io/</id>
<author>
<name><![CDATA[Animatious]]></name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title><![CDATA[Epic-Black-Friday-Deals 效果的前端技术实现]]></title>
<link href="http://anius.io/2015/11/28/epicBlackFridayDeals%E5%89%8D%E7%AB%AF%E5%AE%9E%E7%8E%B0/"/>
<id>http://anius.io/2015/11/28/epicBlackFridayDeals前端实现/</id>
<published>2015-11-28T13:25:40.000Z</published>
<updated>2015-12-03T03:21:32.000Z</updated>
<content type="html"><![CDATA[<h1 id="介绍">介绍</h1><p>这是一个通过 web 技术实现的效果演示 demo 。</p>
<p>原设计链接: <a href="https://dribbble.com/shots/2372734-Epic-Black-Friday-Deals" target="_blank" rel="external">Epic-Black-Friday-Deals</a></p>
<p>原效果图:</p>
<p><img width="400px" height="300px" src="https://d13yacurqjgara.cloudfront.net/users/107759/screenshots/2372734/ink2.gif" alt="Ink2"></p>
<p>Demo 链接: <a href="https://chemzqm.github.io/dribbble-effects/friday.html" target="_blank" rel="external">https://chemzqm.github.io/dribbble-effects/friday.html</a></p>
<p>注:使用 safari 保存到桌面浏览效果更佳。</p>
<p>整个效果分为两个部分实现,上半部分通过 canvas 不断绘制实现,下半部分使用了 css 的 transform 和 transition 来实现。 下面是详细介绍。</p>
<h2 id="基于_canvas_实现的上半部分动画">基于 canvas 实现的上半部分动画</h2><h3 id="关于canvas">关于canvas</h3><p>canvas 是使用 javascript 进行绘图的基本 API, 它本身只提供了基础的画图 API (例如直线、弧线、曲线、矩形、填充等),通过学习<a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial" target="_blank" rel="external">MDN提供的教程</a>, 一个具备 javascript 基本知识的开发者可以很快掌握。</p>
<p>canvas 并不提供动画,事件等更高层的 API,所以需要相应功能都需要自行计算,或者借助其它库来实现。</p>
<p>尽管抽象性很低(或者说非常底层)但是相应的好处是可塑性比较好,因为开发者可以精确的控制每一个细节如何完成。</p>
<h3 id="canvas_动画实现">canvas 动画实现</h3><p>canvas 本身是静态的,并不提供动画,所以我们借助 API <a href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame" target="_blank" rel="external">requestAnimationFrame</a>, 通过重复的调用这个 API , 不断的清空画布,并借助其返回的 timestamp 时间戳计算绘制所需的参数值,就可以实现动画效果了, 代码示例:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> raf = <span class="built_in">window</span>.requestAnimationFrame</span><br><span class="line"><span class="keyword">var</span> start</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">animate</span>(<span class="params">timestamp</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(!start) start = timestamp</span><br><span class="line"> <span class="comment">//通过持续时间不同重绘</span></span><br><span class="line"> drawImage(timestamp - start)</span><br><span class="line"> raf(animate)</span><br><span class="line">}</span><br><span class="line">raf(animate)</span><br></pre></td></tr></table></figure>
<p>当然你也能用 setInterval 这样的 API 实现,但是这么做性能太差,比如当前页面隐藏的时候 requestAnimationFrame 是不会触发调用的,这样就节省了客户端的资源。</p>
<h3 id="实现缓动效果">实现缓动效果</h3><p>缓动效果可以让你的动画更柔和自然,<a href="http://easings.net/zh-cn" target="_blank" rel="external">easings.net</a>上可以看到已经命名的缓动函数,其实质就是把一个 0 到 1 之间的值进行转化。 <a href="https://github.com/component/ease/blob/master/index.js" target="_blank" rel="external">component/ease</a> 是一个实现了缓动函数的 javascript 库,我们拿它的一个函数 <code>inQuad</code> 举例:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">inQuad</span>(<span class="params">n</span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> n * n;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">var</span> duration = <span class="number">600</span> <span class="comment">//动画持续 600 ms</span></span><br><span class="line"><span class="keyword">var</span> raf = <span class="built_in">window</span>.requestAnimationFrame</span><br><span class="line"><span class="keyword">var</span> start</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">animate</span>(<span class="params">timestamp</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(!start) start = timestamp</span><br><span class="line"> <span class="keyword">var</span> percent = (timestamp - start)/duration</span><br><span class="line"> <span class="comment">// 转化为缓动后的值</span></span><br><span class="line"> percent = inQuad(percent)</span><br><span class="line"> drawImage(percent)</span><br><span class="line"> raf(animate)</span><br><span class="line">}</span><br><span class="line">raf(animate)</span><br></pre></td></tr></table></figure>
<p>这样我们的 <code>drawImage</code>接收到的就是缓动后的百分比了。</p>
<p>尽管 <code>drawImage</code> 接收到了百分比,但是具体的值很是需要自己计算,如果你想省事可以借助 <a href="https://github.com/component/tween" target="_blank" rel="external">component/tween</a> 之类的库来帮你计算属性值,事实上后面要讲到的日期选择的滑动效果就是借助 tween 来实现的.</p>
<h2 id="keyframe(关键帧)_的实现">keyframe(关键帧) 的实现</h2><p>keyframe(关键帧)是一种对动画控制非常有用的抽象,比如说一个画一个圈的起始和终止就各自对应一个 keyframe,css animation 就是通过控制 keyframe 来实现动画的 API。 canvas 也有类似的库来实现 keyframe,例如 <a href="https://github.com/jeremyckahn/rekapi" target="_blank" rel="external">rekapi</a>。 因为这次的动画并不需要 playback 支持(或者说我比较懒),所以这次只是简单的通过状态来实现了不同效果的切换,代码大致如下:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">View</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">//负责圆圈的绘制</span></span><br><span class="line"> <span class="keyword">this</span>.circle = <span class="keyword">new</span> Circle(<span class="keyword">this</span>)</span><br><span class="line"> <span class="comment">//负责中间图标绘制</span></span><br><span class="line"> <span class="keyword">this</span>.icon = <span class="keyword">new</span> Icon(<span class="keyword">this</span>)</span><br><span class="line"> <span class="comment">//负责时间文本的绘制</span></span><br><span class="line"> <span class="keyword">this</span>.time = <span class="keyword">new</span> Time(<span class="keyword">this</span>)</span><br><span class="line"> <span class="keyword">this</span>.stat = <span class="string">'stopped'</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">//展开状态</span></span><br><span class="line">View.prototype.pend = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.stat = <span class="string">'pending'</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">//中间对勾效果</span></span><br><span class="line">View.prototype.check= <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.stat = <span class="string">'checking'</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">//重置为展开状态</span></span><br><span class="line">View.prototype.reset = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.stat = <span class="string">'reseting'</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">//主绘制函数</span></span><br><span class="line">View.prototype.draw = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.circle.draw()</span><br><span class="line"> <span class="keyword">this</span>.time.draw()</span><br><span class="line"> <span class="keyword">this</span>.icon.draw()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//每个子模块通过判定 view 的 stat 绘制</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Circle</span>(<span class="params">view</span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.view = view</span><br><span class="line">}</span><br><span class="line">Circle.prototype.draw = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> stat = <span class="keyword">this</span>.view.stat</span><br><span class="line"> <span class="keyword">switch</span> (stat) {</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'pending'</span>:</span><br><span class="line"> <span class="keyword">this</span>.pend()</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'checking'</span>:</span><br><span class="line"> <span class="keyword">this</span>.check()</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'reseting'</span>:</span><br><span class="line"> <span class="keyword">this</span>.reset()</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// time 模块和 icon 模块同上</span></span><br></pre></td></tr></table></figure>
<p>这种写法比较方便省事,但是抽象性比较差,建议需要灵活控制的话还是找一个适合的库来辅助。</p>
<h2 id="动画流程控制">动画流程控制</h2><p>因为没有实现 keyframe ,所以流程控制(例如取消动画和动画连续)也得自己来实现了。 可选的办法有 callback 回调,事件,Promise等,这次使用了 Promise 实现,因为使用起来比较简洁,而且可以很容易实现取消操作。</p>
<p>通过让 Promise reject 来取消原来动画流程:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">View.prototype.cancel = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.promise = <span class="keyword">this</span>.promise || <span class="built_in">Promise</span>.resolve(<span class="literal">null</span>)</span><br><span class="line"> <span class="keyword">this</span>.canceled = <span class="literal">true</span></span><br><span class="line"> <span class="keyword">var</span> self = <span class="keyword">this</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.promise.then(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 当前 promise 已经执行完毕</span></span><br><span class="line"> self.canceled = <span class="literal">false</span></span><br><span class="line"> }, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// reject 表明成功结束</span></span><br><span class="line"> self.canceled = <span class="literal">false</span></span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">View.prototype.animate = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> duration = <span class="keyword">this</span>.duration</span><br><span class="line"> <span class="keyword">var</span> start</span><br><span class="line"> <span class="keyword">var</span> self = <span class="keyword">this</span></span><br><span class="line"> <span class="keyword">var</span> promise = <span class="keyword">this</span>.promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span> (<span class="params">resolve, reject</span>) </span>{</span><br><span class="line"> <span class="comment">// raf 调用的绘制主进程</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">step</span>(<span class="params">timestamp</span>) </span>{</span><br><span class="line"> <span class="comment">// transform 重置</span></span><br><span class="line"> <span class="comment">// 因为使用了 https://github.com/component/autoscale-canvas/blob/master/index.js 通过 scale 支持 Retina,</span></span><br><span class="line"> <span class="comment">// 所以这里不能简单的把 scale 设为 1</span></span><br><span class="line"> self.ctx.setTransform(<span class="built_in">window</span>.devicePixelRatio || <span class="number">1</span> ,<span class="number">0</span> ,<span class="number">0</span> ,<span class="built_in">window</span>.devicePixelRatio || <span class="number">1</span> ,<span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"> <span class="comment">// 清空画布</span></span><br><span class="line"> self.ctx.clearRect(<span class="number">0</span>, <span class="number">0</span>, self.width, self.height)</span><br><span class="line"> <span class="comment">// 停止动画并且 reject</span></span><br><span class="line"> <span class="keyword">if</span> (self.canceled === <span class="literal">true</span>) {</span><br><span class="line"> <span class="keyword">return</span> reject()</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!start) start = timestamp</span><br><span class="line"> <span class="keyword">var</span> d = timestamp - start</span><br><span class="line"> <span class="comment">// 回着不同模块</span></span><br><span class="line"> self.draw()</span><br><span class="line"> <span class="comment">// 成功结束</span></span><br><span class="line"> <span class="keyword">if</span> (d > duration) {</span><br><span class="line"> <span class="keyword">return</span> resolve()</span><br><span class="line"> }</span><br><span class="line"> raf(step)</span><br><span class="line"> }</span><br><span class="line"> raf(step)</span><br><span class="line"> })</span><br><span class="line"> <span class="keyword">return</span> promise</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>调用 <code>view.cancel()</code> 就可以取消原来的流程了。</p>
<p><a href="https://github.com/chemzqm/dribbble-effects/blob/master/lib/friday/index.js" target="_blank" rel="external">view模块</a> 的 <code>cancel</code> <code>pend</code> <code>reset</code> 和 <code>check</code> 方法都返回了 promise 对象,这样我们需要开始新的流程就可以这样写:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 先取消当前动画</span></span><br><span class="line">view.cancel()</span><br><span class="line">.then(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 只有当前是 checked 完成状态才走 reset 流程</span></span><br><span class="line"> <span class="keyword">if</span> (view.checked) {</span><br><span class="line"> <span class="keyword">return</span> view.reset().then(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 暂停一下再开始展开</span></span><br><span class="line"> <span class="keyword">return</span> view.wait(<span class="number">300</span>)</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}).then(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> view.pend()</span><br><span class="line">}).then(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> view.check()</span><br><span class="line">}).catch(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 因为通过 fail 来终止动画,不捕获会报错</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<h3 id="具体效果实现">具体效果实现</h3><ul>
<li>消逝圆圈的残余尾巴:首先是设定了一个区间,例如:<code>arr = [1, 2, 2, 1, 1]</code> 表示有 <code>arr.length</code> 个区间,每个区间有 <code>arr[i]</code> 个尾巴,每次变换区间都重新生成尾巴:</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">step_len = arr.length</span><br><span class="line">Circle.prototype.pend = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> ctx = <span class="keyword">this</span>.ctx</span><br><span class="line"> <span class="keyword">var</span> percent = <span class="keyword">this</span>.view.percent</span><br><span class="line"> <span class="keyword">var</span> step = <span class="built_in">Math</span>.floor(percent*step_len)</span><br><span class="line"> <span class="comment">// step 转变,生成新的 tail</span></span><br><span class="line"> <span class="keyword">if</span> (step > <span class="keyword">this</span>.step) {</span><br><span class="line"> <span class="keyword">this</span>.createTails()</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">this</span>.step = step</span><br><span class="line"> <span class="comment">// 绘制每一个tail</span></span><br><span class="line"> <span class="keyword">this</span>.tails.forEach(<span class="function"><span class="keyword">function</span> (<span class="params">tail</span>) </span>{</span><br><span class="line"> <span class="comment">// 计算 tail 的 percent</span></span><br><span class="line"> <span class="keyword">var</span> p = percent*step_len - step</span><br><span class="line"> <span class="comment">// tail 根据自己 percent (0 为初始 1为消失)进行绘制</span></span><br><span class="line"> tail.draw(p)</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Circle.prototype.createTails = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> n = <span class="keyword">this</span>.step</span><br><span class="line"> <span class="keyword">var</span> num = steps[n]</span><br><span class="line"> <span class="keyword">var</span> tails = []</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < num; i++) {</span><br><span class="line"> <span class="keyword">var</span> tail = <span class="keyword">new</span> Tail(<span class="keyword">this</span>, i)</span><br><span class="line"> tails.push(tail)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">this</span>.tails = tails</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>时间动画的计算,这里的展开和重置动画是不同的(暂开时分秒的动画要远快与时,重置时时分秒的速率是相同的),所以也就不能简单的 playback 了,重置时只需要 <code>percent*total</code> 就能计算对应的十分秒,展开时需要根据一天的十分秒进行一点计算:<figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">function pad(<span class="keyword">n</span>) {</span><br><span class="line"> <span class="keyword">return</span> ('0' + <span class="literal">String</span>(<span class="keyword">n</span>)).slice(-2)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function toHMS(<span class="keyword">n</span>) {</span><br><span class="line"> <span class="keyword">var</span> <span class="keyword">h</span> = Math.<span class="literal">floor</span>(<span class="keyword">n</span>/3600)</span><br><span class="line"> <span class="keyword">var</span> <span class="keyword">m</span> = Math.<span class="literal">floor</span>((<span class="keyword">n</span> - <span class="keyword">h</span>*3600)/60)</span><br><span class="line"> <span class="keyword">var</span> s = Math.<span class="literal">floor</span>(<span class="keyword">n</span>%60)</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> <span class="keyword">h</span>: pad(<span class="keyword">h</span>),</span><br><span class="line"> <span class="keyword">m</span>: pad(<span class="keyword">m</span>),</span><br><span class="line"> s: pad(s)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 1天的秒数</span></span><br><span class="line"><span class="keyword">var</span> <span class="keyword">total</span> = 24*60*60</span><br><span class="line"><span class="comment">// 获取 hms</span></span><br><span class="line">toHMS(<span class="keyword">n</span>)</span><br></pre></td></tr></table></figure>
</li>
</ul>
<p>其实这里有个坑,就是因为秒数变化飞快,有可能 1/60 秒就完成了 0~60 的变化, 这样如果浏览器是 60hz 的话就只会显示相对固定的秒数,例如 <code>5x</code> (x为0 ~ 9), 解决办法就是调整下动画持续时间 😅</p>
<ul>
<li><p>设置动态文本。这次本来打算使用自定义字体 ProximaNova-Light 来显示时间的,但是发现这个字体的数字不是等宽的,结果就是显示动画的时候数字会一直抖动,改为系统自带字体 <code>sans-serif</code> <code>Helvetica</code> 就没有这种问题。</p>
</li>
<li><p>对勾的实现。本次实现最为复杂的部分,因为涉及角度、位置、长度的精确计算,最后使用了黄金分割,也就是右边比左边 1.618: 1, 结果比较满意。代码如下:</p>
</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">Icon.prototype.drawCorrect = <span class="function"><span class="keyword">function</span> (<span class="params">p, tx</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> l = <span class="number">14</span></span><br><span class="line"> <span class="keyword">var</span> tl = <span class="number">2.618</span>*l</span><br><span class="line"> <span class="keyword">var</span> len = tl*(outBack(p))</span><br><span class="line"> <span class="keyword">var</span> ctx = <span class="keyword">this</span>.ctx</span><br><span class="line"> <span class="keyword">var</span> x = <span class="keyword">this</span>.x</span><br><span class="line"> <span class="keyword">var</span> y = <span class="keyword">this</span>.y</span><br><span class="line"> <span class="comment">// 起始 x y</span></span><br><span class="line"> <span class="keyword">var</span> sx = x - <span class="number">10</span></span><br><span class="line"> <span class="keyword">var</span> sy = y - <span class="number">3</span></span><br><span class="line"> <span class="keyword">var</span> subtense = <span class="built_in">Math</span>.min(len, l)</span><br><span class="line"> <span class="comment">// 中间点 x y</span></span><br><span class="line"> <span class="keyword">var</span> mx = sx + subtense*<span class="built_in">Math</span>.cos(<span class="number">45</span>*PI/<span class="number">180</span>)</span><br><span class="line"> <span class="keyword">var</span> my = sy + subtense*<span class="built_in">Math</span>.sin(<span class="number">45</span>*PI/<span class="number">180</span>)</span><br><span class="line"> tx = tx != <span class="literal">null</span> ? tx : - <span class="number">8</span>*(<span class="number">1</span> - outBack(p))</span><br><span class="line"> <span class="comment">// 偏移量</span></span><br><span class="line"> ctx.translate(tx, <span class="number">0</span>)</span><br><span class="line"> ctx.beginPath()</span><br><span class="line"> ctx.moveTo(sx, sy)</span><br><span class="line"> ctx.lineTo(mx, my)</span><br><span class="line"> <span class="keyword">if</span> (len > l) {</span><br><span class="line"> subtense = len - l</span><br><span class="line"> <span class="comment">// 终止 x y</span></span><br><span class="line"> <span class="keyword">var</span> ex = mx + subtense*<span class="built_in">Math</span>.cos(<span class="number">50</span>*PI/<span class="number">180</span>)</span><br><span class="line"> <span class="keyword">var</span> ey = my - subtense*<span class="built_in">Math</span>.sin(<span class="number">50</span>*PI/<span class="number">180</span>)</span><br><span class="line"> ctx.lineTo(ex, ey)</span><br><span class="line"> }</span><br><span class="line"> ctx.stroke()</span><br><span class="line"> <span class="comment">// 重置 transform</span></span><br><span class="line"> ctx.setTransform(<span class="built_in">window</span>.devicePixelRatio || <span class="number">1</span> ,<span class="number">0</span> ,<span class="number">0</span> ,<span class="built_in">window</span>.devicePixelRatio || <span class="number">1</span> ,<span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 出去一点返回</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">outBack</span>(<span class="params">n</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> s = <span class="number">1.70158</span>;</span><br><span class="line"> <span class="keyword">return</span> --n * n * ((s + <span class="number">1</span>) * n + s) + <span class="number">1</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这里几个点的坐标不用重复计算的,但是比较偷懒就没抽象出去🙂</p>
<ul>
<li>创建遮罩。为了实现中间图标的进入和退出效果,我在每次动画的时候都在中间图标的左边或者右边创建了一个与背景色相同的矩形区域,这样图标出去的时候就会被遮罩盖住,看上就就好象图标是在下一层退出的一样。(因为canvas 并没有 z-index)</li>
</ul>
<h2 id="基于_Dom_的下半部分日期选择模块">基于 Dom 的下半部分日期选择模块</h2><h3 id="日期状态变化时颜色渐变">日期状态变化时颜色渐变</h3><p>只需要用 css 一个 transion 就能实现:</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">transition</span>: <span class="tag">color</span> 0<span class="class">.5s</span> <span class="tag">linear</span>;</span><br></pre></td></tr></table></figure>
<p>对应状态切换时改变元素的 className 就可以了,(小技巧: classList API)</p>
<h3 id="日期列表触摸滚动实现">日期列表触摸滚动实现</h3><p>通过不断调整元素的 translateX 或者有 translate3d 时调整 translate3d 的 x 值实现:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> s = el.style</span><br><span class="line"><span class="keyword">if</span> (has3d) {</span><br><span class="line"> s[transform] = <span class="string">'translate3d('</span> + x + <span class="string">'px, 0, 0)'</span></span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> s[transform] = <span class="string">'translateX('</span> + x + <span class="string">'px)'</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这里相对复杂的一步就是 touchend 时计算之后动画的持续时间、最终位置和缓动函数:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 减速度</span></span><br><span class="line"><span class="keyword">var</span> deceleration = <span class="number">0.0004</span></span><br><span class="line"><span class="comment">// 当前滑动速度</span></span><br><span class="line"><span class="keyword">var</span> speed = <span class="keyword">this</span>.speed</span><br><span class="line"><span class="keyword">var</span> x = <span class="keyword">this</span>.x</span><br><span class="line"><span class="comment">// 限定速度</span></span><br><span class="line">speed = <span class="built_in">Math</span>.min(speed, <span class="number">0.6</span>)</span><br><span class="line"><span class="keyword">var</span> minX = - (<span class="keyword">this</span>.total - <span class="keyword">this</span>.count)*width</span><br><span class="line"><span class="comment">// 估算终点</span></span><br><span class="line"><span class="keyword">var</span> destination = x + ( speed * speed ) / ( <span class="number">2</span> * deceleration ) * ( <span class="keyword">this</span>.distance < <span class="number">0</span> ? -<span class="number">1</span> : <span class="number">1</span> )</span><br><span class="line"><span class="comment">// 估算时间</span></span><br><span class="line"><span class="keyword">var</span> duration = speed / deceleration</span><br><span class="line"><span class="keyword">var</span> newX</span><br><span class="line"><span class="keyword">var</span> ease = <span class="string">'out-cube'</span></span><br><span class="line"><span class="comment">// 终点超出右边界 重算终点和缓动函数</span></span><br><span class="line"><span class="keyword">if</span> (destination > <span class="number">0</span>) {</span><br><span class="line"> newX = <span class="number">0</span></span><br><span class="line"> ease = <span class="string">'out-back'</span></span><br><span class="line"><span class="comment">// 终点小于左边界 重算终点和缓动函数</span></span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (destination < minX) {</span><br><span class="line"> newX = minX</span><br><span class="line"> ease = <span class="string">'out-back'</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// 超出边界的话重算持续时间</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> newX === <span class="string">'number'</span>) {</span><br><span class="line"> duration = duration*<span class="built_in">Math</span>.abs((newX - x + <span class="number">50</span>)/(destination - x))</span><br><span class="line"> <span class="comment">//duration = Math.max(200, duration)</span></span><br><span class="line"> destination = newX</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 当前点在边界外,固定持续时间和缓动函数</span></span><br><span class="line"><span class="keyword">if</span> (x > <span class="number">0</span> || x < minX) {</span><br><span class="line"> duration = <span class="number">500</span></span><br><span class="line"> ease = <span class="string">'out-circ'</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// 固定终点为子元素宽度的整数倍</span></span><br><span class="line">destination = <span class="built_in">Math</span>.round(destination/width)*width</span><br><span class="line"><span class="keyword">return</span> {</span><br><span class="line"> x: destination,</span><br><span class="line"> duration: duration,</span><br><span class="line"> ease: ease</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>计算完成后把结果传递给 <code>tween</code> 对象就可以开始滑动了。</p>
<h3 id="日期选择">日期选择</h3><p>用到了 <a href="https://github.com/component/events" target="_blank" rel="external">events</a> 实现事件代理, 以及<a href="https://github.com/chemzqm/tap-event" target="_blank" rel="external">tap-event</a>实现正确的 tap 事件 (都是非常常用的移动端组件)</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 简单的判定,防止 click 和 tap 事件同时触发</span></span><br><span class="line"><span class="keyword">var</span> hastouch = <span class="string">'ontouchstart'</span> <span class="keyword">in</span> <span class="built_in">window</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Footer</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.events = events(el, <span class="keyword">this</span>)</span><br><span class="line"> <span class="keyword">if</span> (hastouch) {</span><br><span class="line"> <span class="keyword">this</span>.events.bind(<span class="string">'touchstart li'</span>, <span class="string">'ontap'</span>)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">this</span>.events.bind(<span class="string">'click li'</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Footer.prototype.onclick = <span class="function"><span class="keyword">function</span> (<span class="params">e</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> li = e.delegateTarget</span><br><span class="line"> <span class="keyword">var</span> children = <span class="keyword">this</span>.el.children</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, l = children.length; i < l; i++) {</span><br><span class="line"> <span class="keyword">if</span> (li === children[i]) {</span><br><span class="line"> <span class="comment">// 高亮选中元素</span></span><br><span class="line"> li.classList.add(<span class="string">'active'</span>)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 移除其它元素高亮</span></span><br><span class="line"> li.classList.remove(<span class="string">'active'</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Footer.prototype.ontap = tap(Footer.prototype.onclick)</span><br></pre></td></tr></table></figure>
<h3 id="事件传递">事件传递</h3><p>使用的 <a href="https://github.com/component/emitter" target="_blank" rel="external">component/emitter</a>, 它实现了非常简单的观察者模式(订阅发布模式)</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Footer</span>(<span class="params">el</span>) </span>{</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Emitter(el)</span><br><span class="line"></span><br><span class="line">Footer.prototype.active = <span class="function"><span class="keyword">function</span> (<span class="params">index</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.emit(<span class="string">'change'</span>, index)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">View</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> footer = <span class="keyword">this</span>.footer = <span class="keyword">new</span> Footer()</span><br><span class="line"> footer.on(<span class="string">'change'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">index</span>) </span>{</span><br><span class="line"> <span class="comment">// active 变化的事件代理函数</span></span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这样我们就实现了 <code>Footer</code> 模块和 <code>View</code> 模块的解耦合</p>
<h2 id="Q_and_A">Q and A</h2><p>Q: 为什么没有注释?</p>
<p>A: 实现比较仓促,而且也不是实际使用项目,所以比较偷懒,其实我的大部分项目都很严肃,也有比较完整的测试和注释。</p>
<p>Q: 为什么 ios 上保存为 app 后状态栏是白色?</p>
<p>A: 因为这是 ios9 的 bug, 原本可以透明的, <a href="https://forums.developer.apple.com/thread/9819" target="_blank" rel="external">了解更多</a></p>
<p>Q: 为什么手机横过来就不能看了?</p>
<p>A: 因为现在safari 还没有实现锁屏的 API !</p>
<h2 id="最后">最后</h2><p>实现 web 动画还有 <a href="https://developer.mozilla.org/en/docs/Web/CSS/animation" target="_blank" rel="external">css animation</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API" target="_blank" rel="external">webgl</a> <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/SVG_animation_with_SMIL" target="_blank" rel="external">svg smil</a> 等方案,这次并没有用到,以后有机会再为大家介绍。</p>
<p>或许是因为 web 存在很多的技术方案以及相应类库,导致很多做 web 的同学觉得搞特效很复杂,但事实并非如此, 只要你了解了它们基本特点并掌握基本方法,就足够做出不错的效果了,再附送一个更简单的例子:<a href="https://chemzqm.github.io/loadings/" target="_blank" rel="external">canvas loadings</a></p>
<p>希望本文能帮你做出更好的 web 动画效果。</p>
<h2 id="关于作者">关于作者</h2><p>[email protected] 一个以简洁高效为追求的 Javascript 开发者</p>
<p>Github: <a href="https://github.com/chemzqm" target="_blank" rel="external">https://github.com/chemzqm</a></p>
<p>weibo: <a href="http://weibo.com/chemzqm" target="_blank" rel="external">http://weibo.com/chemzqm</a></p>
]]></content>
<summary type="html">
<![CDATA[<h1 id="介绍">介绍</h1><p>这是一个通过 web 技术实现的效果演示 demo 。</p>
<p>原设计链接: <a href="https://dribbble.com/shots/2372734-Epic-Black-Friday-Deals" target=]]>
</summary>
<category term="canvas" scheme="http://anius.io/tags/canvas/"/>
<category term="web" scheme="http://anius.io/tags/web/"/>
<category term="代码" scheme="http://anius.io/categories/%E4%BB%A3%E7%A0%81/"/>
</entry>
<entry>
<title><![CDATA[SCCatWaitingHUD的Objc代码实现]]></title>
<link href="http://anius.io/2015/11/25/SCCatWaitingHUD/"/>
<id>http://anius.io/2015/11/25/SCCatWaitingHUD/</id>
<published>2015-11-25T10:19:49.000Z</published>
<updated>2015-11-28T13:21:20.000Z</updated>
<content type="html"><![CDATA[<h1 id="介绍">介绍</h1><p>这个创意其实来源于微博上@画渣程序猿mmoaay转发并艾特我的的一组gif图<br>看到图的时候 我先对图大致进行了结构和层次的区分<br>在设计物体动效的时候,首先是要对动画的不同对象进行拆分,在这里,老鼠,猫眼睛,眼皮,以及猫脸,他们各自的行为分别是不同的,因此分为不同的图层 </p>
<p><img src="/img/2015/11/SCCatWaitingHUD/pic1.jpg" alt=""></p>
<p>在整体动画进行的时候,他们各自的layer所执行的动画是不同的<br>看起来这个控件很简单,接下来我们就来分析一下写出这样一个控件需要注意的关键点吧~</p>
<h1 id="分析">分析</h1><p>先确认我们要做成什么样的控件 </p>
<h2 id="首先_这是一个等待动画_所以应该做成一个HUD类型的控件">首先 这是一个等待动画 所以应该做成一个HUD类型的控件</h2><p>这样可以在当前window的上方出现和消失而不影响当前视图的展示<br>所以我们就要在应用当前默认的UIWindow上再添加一个UIWindow 这样可以在全局任何地方调用这个HUD 同时我们的HUD也要实现全局的单例模式 </p>
<h2 id="其次是动画_最初的动画其实并不难">其次是动画 最初的动画其实并不难</h2><p>只是眼睛图层和老鼠图层围着各自的中心点旋转 且拥有相同的运动周期 这样能让他们在相同时间内移动的弧度是相同的 产生眼睛跟着老鼠走的感觉</p>
<p><img src="/img/2015/11/SCCatWaitingHUD/circle.png" alt=""></p>
<h2 id="最后是眼皮和眼珠的运动协调">最后是眼皮和眼珠的运动协调</h2><p>由于眼珠做的是匀速圆周运动 而眼皮做的是直线缩放运动 因此如何协调这两者的运动时间曲线 决定了最后的动画是否流畅和自然 </p>
<h3 id="还有一点额外的临时产生的需求就是对横屏的支持">还有一点额外的临时产生的需求就是对横屏的支持</h3><p>这是我在写的过程中@sauchye提出的issue,所以我花了一个版本用来加上了对左右横屏的支持</p>
<h1 id="代码实现">代码实现</h1><h2 id="UIWindow">UIWindow</h2><p>UIWindow 是每一个程序都必须创建的根视图,它是UIView的子类,但是地位却在所有的UIView之上。每一个程序在启动的时候,如果你使用了interface builder作为启动界面,那么Xcode会自动把你的第一个Controller添加到根window上,如果你要通过代码初始化Window,则必须要像系统要求的这样声明:</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)application willFinishLaunchingWithOptions:(<span class="built_in">NSDictionary</span> *)launchOptions {</span><br><span class="line"></span><br><span class="line"> <span class="built_in">UIWindow</span> *window = [[<span class="built_in">UIWindow</span> alloc] initWithFrame:[[<span class="built_in">UIScreen</span> mainScreen] bounds]];</span><br><span class="line"></span><br><span class="line"> myViewController = [[MyViewController alloc] init];</span><br><span class="line"> window<span class="variable">.rootViewController</span> = myViewController;</span><br><span class="line"> [window makeKeyAndVisible];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在SCCatWaitingHUD中,由于需要存在一个不干扰原有程序代码逻辑和视图逻辑的View,因此最合适的办法就是声明一个独立的Window。HUD的这种使用模式可以参考MBProgressHUD,开发者可以在全局任意的地方调用HUD的显示,由于HUD所在的Window和系统原本的Window相比level更高,所以可以在不影响原有视图的情况下在任意页面显示。</p>
<p>UIWindow的结构如下,我们可以看到UIWindow本身就是一个UIView。每个UIWindow都会有一个名为rootViewController的属性,而这个属性就是这个window将会呈现的controller对象。</p>
<p><img src="/img/2015/11/SCCatWaitingHUD/window.png" alt=""></p>
<p>Xcode7之后,苹果要求所有的UIWindow在声明的时候都需要有一个rootViewController,即通过代码声明的时候,需要定义一个rootViewController,然后在这个controller之上添加要显示的内容。但是经过验证,在程序运行中创建的非根Window的UIWindow,可以直接当做UIView来使用,仍然不需要强制给一个rootViewController。</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">self</span><span class="variable">.backgroundWindow</span> = [[<span class="built_in">UIWindow</span> alloc]initWithFrame:<span class="keyword">self</span><span class="variable">.frame</span>];</span><br><span class="line">_backgroundWindow<span class="variable">.windowLevel</span> = <span class="built_in">UIWindowLevelStatusBar</span>;</span><br><span class="line">_backgroundWindow<span class="variable">.backgroundColor</span> = [<span class="built_in">UIColor</span> clearColor];</span><br><span class="line">_backgroundWindow<span class="variable">.alpha</span> = <span class="number">0.0</span>f;</span><br></pre></td></tr></table></figure>
<p>所以我们这个HUD的主要显示对象就是名为<code>backgroundWindow</code>的UIWindow属性对象<br>那么怎么控制Window的出现和消失呢<br>UIWindow有几个方法: </p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">- (void)makeKeyWindow;</span><br><span class="line">- (void)makeKeyAndVisible; // convenience. most apps <span class="operator"><span class="keyword">call</span> this <span class="keyword">to</span> <span class="keyword">show</span> the <span class="keyword">main</span> window <span class="keyword">and</span> also make it <span class="keyword">key</span>. otherwise <span class="keyword">use</span> <span class="keyword">view</span> hidden property</span></span><br></pre></td></tr></table></figure>
<p>一般都是用上述第二个方法来让指定Window成为keyWindow并且出现。至于怎么让指定Window消失,苹果并没有提供一些特别的办法,官方文档中有给出下面这种用法</p>
<pre><code>_backgroundWindow.hidden = <span class="type">YES</span>;
// <span class="type">According</span> to <span class="type">Apple</span> <span class="type">Doc</span> : <span class="type">This</span> <span class="keyword">is</span> a convenience <span class="keyword">method</span> to make the receiver the main window <span class="keyword">and</span> displays it <span class="keyword">in</span> front <span class="keyword">of</span> other windows at the same window level <span class="keyword">or</span> lower. <span class="type">You</span> can also hide <span class="keyword">and</span> reveal a window <span class="keyword">using</span> the inherited hidden property <span class="keyword">of</span> <span class="type">UIView</span>.
</code></pre><h2 id="Animation">Animation</h2><p>动画要解决的第一个问题就是,<strong>老鼠的旋转是绕着自身的中心点,然而两个眼珠的旋转并不是</strong>。<br>首先,旋转动画的实现我们肯定是通过<code>CATransform3DRotate</code>,基本的CAAnimation声明如下:</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">double</span> radians(<span class="keyword">float</span> degrees) {</span><br><span class="line"> <span class="keyword">return</span> ( degrees * <span class="number">3.14159265</span> ) / <span class="number">180.0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="built_in">CATransform3D</span> getTransForm3DWithAngle(<span class="built_in">CGFloat</span> angle)</span><br><span class="line">{</span><br><span class="line"> <span class="built_in">CATransform3D</span> transform = <span class="built_in">CATransform3DIdentity</span>;</span><br><span class="line"> transform = <span class="built_in">CATransform3DRotate</span>(transform, angle, <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">return</span> transform;</span><br><span class="line">}</span><br><span class="line">- (<span class="built_in">CABasicAnimation</span> *)rotationAnimation</span><br><span class="line">{</span><br><span class="line"> <span class="built_in">CABasicAnimation</span> *rotationAnimation;</span><br><span class="line"> rotationAnimation = [<span class="built_in">CABasicAnimation</span> animationWithKeyPath:<span class="string">@"transform"</span>];</span><br><span class="line"> rotationAnimation<span class="variable">.fromValue</span> = [<span class="built_in">NSValue</span> valueWith<span class="built_in">CATransform3D</span>:getTransForm3DWithAngle(<span class="number">0.0</span>f)];</span><br><span class="line"> rotationAnimation<span class="variable">.toValue</span> = [<span class="built_in">NSValue</span> valueWith<span class="built_in">CATransform3D</span>:getTransForm3DWithAngle(radians(<span class="number">180.0</span>f))];</span><br><span class="line"> rotationAnimation<span class="variable">.duration</span> = <span class="keyword">self</span><span class="variable">.animationDuration</span>;</span><br><span class="line"> rotationAnimation<span class="variable">.cumulative</span> = <span class="literal">YES</span>;</span><br><span class="line"> rotationAnimation<span class="variable">.repeatCount</span> = HUGE_VALF;</span><br><span class="line"> rotationAnimation<span class="variable">.removedOnCompletion</span>=<span class="literal">NO</span>;</span><br><span class="line"> rotationAnimation<span class="variable">.fillMode</span>=k<span class="built_in">CAFillModeForwards</span>;</span><br><span class="line"> rotationAnimation<span class="variable">.autoreverses</span> = <span class="literal">NO</span>;</span><br><span class="line"> rotationAnimation<span class="variable">.timingFunction</span> = [<span class="built_in">CAMediaTimingFunction</span> functionWithName:k<span class="built_in">CAMediaTimingFunctionLinear</span>];</span><br><span class="line"> <span class="keyword">return</span> rotationAnimation;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们需要来看看CALayer的几个基本属性:frame、bounds、position、anchorPoint。frame和bounds都可以用UIView中的知识去理解,frame是相对于superLayer的位置和大小,而bounds是origin为(0,0)的frame。<br>而anchorPoint则是一个Layer的锚点,相信做过一些动画开发或者游戏开发的同学们都知道锚点的作用。锚点作为Layer变换的基准点,它的坐标值为相对于bounds宽高的比例。在iOS中,它的起始点是Layer的左上角,为标准原点(0,0),右下角为(1,1),默认值为中心点(0.5,0.5);在MacOS中,起始点为左下角,(1,1)在右上角。所以通过修改锚点的相对位置,就可以让旋转的轴心变成我们需要的位置。 </p>
<p>这里有一张图来说明锚点是什么。</p>
<p><img src="/img/2015/11/SCCatWaitingHUD/anchorpoint2.jpg" alt=""></p>
<p>因此我尝试着将眼珠的anchorPoint设置为(1.5,1.5),旋转中心变成了接近眼睛中心的点。但是这时候我们还需要修改position来配合anchorPoint的改变。我们可以这么理解,position是锚点相对于父layer的坐标值,而anchorPoint则是锚点相对于自身的坐标值,两者共同决定了锚点和整个Layer所在的位置。<br>所以我们还需要调整眼珠Layer的position,来和之前设置的anchorPoint的位置重合。</p>
<figure class="highlight pf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">_rightEye.layer.<span class="built_in">anchor</span>Point = CGPointMake(<span class="number">1.5</span>f, <span class="number">1.5</span>f);</span><br><span class="line">_rightEye.layer.position = CGPointMake(<span class="literal">self</span>.faceView.right - <span class="number">13.5</span>f, <span class="literal">self</span>.faceView.top + width/<span class="number">3.0</span>f + <span class="number">7.5</span>f);</span><br></pre></td></tr></table></figure>
<p>这时候,上面的<code>CAAnimation</code>就可以正常的添加到Layer的transform上并且执行出来啦。</p>
<h2 id="眼皮的运动时间曲线">眼皮的运动时间曲线</h2><p>这里最重要的一点就是要让眼皮和眼珠的运动达到协调一致,不会出现翻白眼也不会出现斗鸡眼(哈哈哈哈)。所以我们可以先分析出下面两个结论:</p>
<ul>
<li>眼皮的上下运动周期和眼珠的旋转运动周期一致</li>
<li>眼皮在竖直方向上的位移和眼珠在竖直方向上的位移一致</li>
<li>眼珠在圆周切线方向的线性速率是匀速的</li>
</ul>
<p>因此我们可以得出结论,眼皮在竖直方向上的速率曲线和眼珠在竖直方向上的速率曲线是一致的。我们来分析眼珠的速度方向。首先,我们可以将半周的时长定为常量T,半径定为R。在下面的推导中我会将<strong>T=2</strong>这个条件当做<strong>已知条件</strong>代入。</p>
<p><img src="/img/2015/11/SCCatWaitingHUD/v.jpg" alt=""></p>
<p>通过将速度分解成水平X轴和竖直Y轴的分量后,我们可以得出竖直方向上的眼珠速度的速度曲线,就是简单的<br><img src="/img/2015/11/SCCatWaitingHUD/sinx.jpg" alt=""> </p>
<p>这里的Vy就是竖直速度分量,θ是t时刻的圆心角,由于这里做的是匀速圆周运动,因此角速度和线速度也是匀速的,公式中的V即为匀速的线速度<br><img src="/img/2015/11/SCCatWaitingHUD/xiansudu.png" alt=""> </p>
<p>所以我们可以得出t时刻的圆心角公式为<br><img src="/img/2015/11/SCCatWaitingHUD/xita.jpg" alt=""></p>
<p>在这里,我之前犯了个大错误,不知道有没有读者注意到。之前的版本中,我的公式里将时间和弧度分子和分母的位置颠倒了,并且最后求位移公式的时候,忘记了这是个变速运动。因此得出来的位移函数曲线并不是像之前那个版本所展示的一样。</p>
<p>所以我们将以上几个公式合并起来之后获得一个Vy的公式如下<br><img src="/img/2015/11/SCCatWaitingHUD/finalVy.jpg" alt=""></p>
<p>这个函数的图像如下,由于R没有确定,我在这里使得R=5,来看一下曲线的样子,其实就是一个普通的正弦曲线<br><img src="/img/2015/11/SCCatWaitingHUD/finalVyLine.png" alt=""></p>
<p>这时候,由于加速度是变化的,速度也是变化的,所以这是个非匀变速运动,它的位移曲线只能通过对速度曲线的积分来求得。因此我们要求Vy函数在(0,t)区间的积分。<br><img src="/img/2015/11/SCCatWaitingHUD/jifen.png" alt=""></p>
<p>这个积分求出来得到的结果如下<br><img src="/img/2015/11/SCCatWaitingHUD/Sfinal.jpg" alt=""></p>
<p>这个就是最后的位移公式,它的函数图像如下<br><img src="/img/2015/11/SCCatWaitingHUD/SfinalLine.png" alt=""></p>
<p>注意,我们得出这个函数图像的主要目标是确定眼皮的上下缩放动画的时间曲线,这个曲线代表着位移和时间的函数关系。这时候,这个函数图像就是接下来要给眼皮加上的缩放动画的时间曲线了,我没有用专门的画贝塞尔曲线的软件来画,只是根据函数的图像大致画出了一个贝塞尔曲线,作为了下面这个动画的时间曲线</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">- (<span class="built_in">CAAnimationGroup</span> *)scaleAnimation</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// 眼皮和眼珠需要确定一个运动时间曲线</span></span><br><span class="line"> <span class="built_in">CABasicAnimation</span> *scaleAnimation;</span><br><span class="line"> scaleAnimation = [<span class="built_in">CABasicAnimation</span> animationWithKeyPath:<span class="string">@"transform"</span>];</span><br><span class="line"> scaleAnimation<span class="variable">.fromValue</span> = [<span class="built_in">NSValue</span> valueWith<span class="built_in">CATransform3D</span>:<span class="built_in">CATransform3DIdentity</span>];</span><br><span class="line"> scaleAnimation<span class="variable">.toValue</span> = [<span class="built_in">NSValue</span> valueWith<span class="built_in">CATransform3D</span>:<span class="built_in">CATransform3DMakeScale</span>(<span class="number">1.0</span>, <span class="number">3.0</span>, <span class="number">1.0</span>)];</span><br><span class="line"> scaleAnimation<span class="variable">.duration</span> = <span class="keyword">self</span><span class="variable">.animationDuration</span>;</span><br><span class="line"> scaleAnimation<span class="variable">.cumulative</span> = <span class="literal">YES</span>;</span><br><span class="line"> scaleAnimation<span class="variable">.repeatCount</span> = <span class="number">1</span>;</span><br><span class="line"> scaleAnimation<span class="variable">.removedOnCompletion</span>= <span class="literal">NO</span>;</span><br><span class="line"> scaleAnimation<span class="variable">.fillMode</span>=k<span class="built_in">CAFillModeForwards</span>;</span><br><span class="line"> scaleAnimation<span class="variable">.autoreverses</span> = <span class="literal">NO</span>;</span><br><span class="line"> scaleAnimation<span class="variable">.timingFunction</span> = [<span class="built_in">CAMediaTimingFunction</span> functionWithControlPoints:<span class="number">0.2</span>:<span class="number">0.0</span> :<span class="number">0.8</span> :<span class="number">1.0</span>];</span><br><span class="line"> scaleAnimation<span class="variable">.speed</span> = <span class="number">1.0</span>f;</span><br><span class="line"> scaleAnimation<span class="variable">.beginTime</span> = <span class="number">0.0</span>f;</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CAAnimationGroup</span> *group = [<span class="built_in">CAAnimationGroup</span> animation];</span><br><span class="line"> group<span class="variable">.duration</span> = <span class="keyword">self</span><span class="variable">.animationDuration</span>;</span><br><span class="line"> group<span class="variable">.repeatCount</span> = HUGE_VALF;</span><br><span class="line"> group<span class="variable">.removedOnCompletion</span>= <span class="literal">NO</span>;</span><br><span class="line"> group<span class="variable">.fillMode</span>=k<span class="built_in">CAFillModeForwards</span>;</span><br><span class="line"> group<span class="variable">.autoreverses</span> = <span class="literal">YES</span>;</span><br><span class="line"> group<span class="variable">.timingFunction</span> = [<span class="built_in">CAMediaTimingFunction</span> functionWithControlPoints:<span class="number">0.2</span>:<span class="number">0.0</span>:<span class="number">0.8</span> :<span class="number">1.0</span>];</span><br><span class="line"> </span><br><span class="line"> group<span class="variable">.animations</span> = [<span class="built_in">NSArray</span> arrayWithObjects:scaleAnimation, <span class="literal">nil</span>];</span><br><span class="line"> <span class="keyword">return</span> group;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>你可以看到<code>CAMediaTimingFunction</code>,这就是时间曲线的类,在一般情况下,系统会提供这么几种你常用到的时间曲线</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">CA_EXTERN</span> <span class="built_in">NSString</span> * <span class="keyword">const</span> k<span class="built_in">CAMediaTimingFunctionLinear</span></span><br><span class="line"> __OSX_<span class="built_in">AVAILABLE_STARTING</span> (__MAC_10_5, __IPHONE_2_0);</span><br><span class="line"><span class="built_in">CA_EXTERN</span> <span class="built_in">NSString</span> * <span class="keyword">const</span> k<span class="built_in">CAMediaTimingFunctionEaseIn</span></span><br><span class="line"> __OSX_<span class="built_in">AVAILABLE_STARTING</span> (__MAC_10_5, __IPHONE_2_0);</span><br><span class="line"><span class="built_in">CA_EXTERN</span> <span class="built_in">NSString</span> * <span class="keyword">const</span> k<span class="built_in">CAMediaTimingFunctionEaseOut</span></span><br><span class="line"> __OSX_<span class="built_in">AVAILABLE_STARTING</span> (__MAC_10_5, __IPHONE_2_0);</span><br><span class="line"><span class="built_in">CA_EXTERN</span> <span class="built_in">NSString</span> * <span class="keyword">const</span> k<span class="built_in">CAMediaTimingFunctionEaseInEaseOut</span></span><br><span class="line"> __OSX_<span class="built_in">AVAILABLE_STARTING</span> (__MAC_10_5, __IPHONE_2_0);</span><br></pre></td></tr></table></figure>
<p>他们分别代表了匀速运动,先快后慢,先慢后快,先快后慢再快这四种运动时间曲线。如果你要自定义时间曲线的话,系统也提供了绘制贝塞尔曲线的方法,你只要确定曲线的两个控制点坐标即可。而坐标的原点为(0,0),最后收尾的点则在(1,1),横轴为时间,纵轴为动画的执行程度,坐标值代表着相对比例值,因此我们可以截取上面的函数图像中横轴0到T的部分作为0到1的时间长度,来绘制贝塞尔曲线。而这里我设置了前提条件为T=2,因此只要截取(0,2)区间里的函数曲线就可以了。</p>
<h2 id="最后是一点对横屏的支持">最后是一点对横屏的支持</h2><p>由于我们实现的是一个独立的UIWindow,没有添加ViewController,所以<strong>不能</strong>通过ViewController下面几个涉及到屏幕旋转的回调来获得屏幕方向的变化。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// New Autorotation support.</span></span><br><span class="line">- (BOOL)<span class="function">shouldAutorotate <span class="title">NS_AVAILABLE_IOS</span><span class="params">(<span class="number">6</span>_0)</span> __TVOS_PROHIBITED</span>;</span><br><span class="line">- (UIInterfaceOrientationMask)<span class="function">supportedInterfaceOrientations <span class="title">NS_AVAILABLE_IOS</span><span class="params">(<span class="number">6</span>_0)</span> __TVOS_PROHIBITED</span>;</span><br><span class="line"><span class="comment">// Returns interface orientation masks.</span></span><br><span class="line">- (UIInterfaceOrientation)<span class="function">preferredInterfaceOrientationForPresentation <span class="title">NS_AVAILABLE_IOS</span><span class="params">(<span class="number">6</span>_0)</span> __TVOS_PROHIBITED</span>;</span><br></pre></td></tr></table></figure>
<p>这时候我们就要通过监听系统的一个广播消息来获取屏幕状态的变化。</p>
<figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr_selector">[[NSNotificationCenter defaultCenter]</span> <span class="tag">addObserver</span><span class="pseudo">:self</span> <span class="tag">selector</span>:@<span class="tag">selector</span>(<span class="attribute">statusBarOrientationChange</span>:) <span class="tag">name</span><span class="pseudo">:UIApplicationDidChangeStatusBarOrientationNotification</span> <span class="tag">object</span><span class="pseudo">:nil</span>];</span><br></pre></td></tr></table></figure>
<p>在横屏切换的回调里,我记录了前一个屏幕状态,获取到当前屏幕状态,然后做了一个旋转的变换,这样整个HUD就会随着屏幕方向的变化而变化了。详细的实现可以直接看源代码,这里就不再贴出。</p>
<h1 id="效果">效果</h1><p>这个开源控件的基本原理也就这些了,你可以在我的<a href="https://github.com/SergioChan/SCCatWaitingHUD" target="_blank" rel="external">github</a>上clone下源代码来阅读 :-) ,如果你有更好的建议或者想法也欢迎随时提PR。 </p>
<h1 id="其他资源">其他资源</h1><p>文章内文件提供公共下载<br>配图所用Sketch文件:<a href="/files/2015/11/SCCatWaitingHUD/MAO.sketch">下载链接</a></p>
<h1 id="作者">作者</h1><p>Sergio Chan<br>Github : <a href="https://github.com/SergioChan" target="_blank" rel="external">https://github.com/SergioChan</a><br>Weibo : <a href="http://weibo.com/sergiochan" target="_blank" rel="external">http://weibo.com/sergiochan</a> </p>
]]></content>
<summary type="html">
<![CDATA[<h1 id="介绍">介绍</h1><p>这个创意其实来源于微博上@画渣程序猿mmoaay转发并艾特我的的一组gif图<br>看到图的时候 我先对图大致进行了结构和层次的区分<br>在设计物体动效的时候,首先是要对动画的不同对象进行拆分,在这里,老鼠,猫眼睛,眼皮,以及猫脸,他]]>
</summary>
<category term="ObjC" scheme="http://anius.io/tags/ObjC/"/>
<category term="WaitingHUD" scheme="http://anius.io/tags/WaitingHUD/"/>
<category term="代码" scheme="http://anius.io/categories/%E4%BB%A3%E7%A0%81/"/>
</entry>
<entry>
<title><![CDATA[tvOS视差按钮的ObjC实现]]></title>
<link href="http://anius.io/2015/11/18/tvOS%E8%A7%86%E5%B7%AE%E6%8C%89%E9%92%AE%E7%9A%84ObjC%E5%AE%9E%E7%8E%B0/"/>
<id>http://anius.io/2015/11/18/tvOS视差按钮的ObjC实现/</id>
<published>2015-11-18T12:31:54.000Z</published>
<updated>2015-11-25T14:10:58.000Z</updated>
<content type="html"><![CDATA[<p><img src="/img/2015/11/tvOS/tvOSButtonPic_thumb.jpg" alt=""></p>
<h1 id="介绍">介绍</h1><p>苹果在最新发布的Apple TV里引入了有趣的<a href="https://developer.apple.com/tvos/human-interface-guidelines/icons-and-images/" target="_blank" rel="external">图标设计</a><br>具体说来 图标由2-5个分层图层构成 在图标被选中的时候 图标内每个图层进行不同幅度的位移 从而形成视觉上具有深度距离感的视差效果 图标构成和效果可以见视频: </p>
<p><video id="video" controls preload="none" autoplay loop><br> <source src="https://developer.apple.com/tvos/human-interface-guidelines/icons-and-images/images/icons-and-images-layering.mp4" type="video/mp4"><br> <source src="https://developer.apple.com/tvos/human-interface-guidelines/icons-and-images/images/icons-and-images-layering.ogv" type="video/ogg"><br> <source src="https://developer.apple.com/tvos/human-interface-guidelines/icons-and-images/images/icons-and-images-layering.webm" type="video/webm"><br> </video><br>这种效果很适合用于多媒体类应用 例如图书或者电影封面 让封面变得立体生动 然而这种效果目前只能在Apple TV的tvOS里见到<br>所以 如何在iOS上做出同样的效果呢?现在就让我们一起研究下视差按钮的实现原理 并且自己实现一个吧 ^_^ </p>
<h2 id="原理">原理</h2><p>假设我们已经有了以下图片:(你可以从<a href="/files/2015/11/tvOS/tvOS-Gear.zip">下载链接</a>下载已经分层的四张图片)<br><img src="/img/2015/11/tvOS/tvOSButtonPic_1.jpg" alt="四张已经分层的图片"><br>基于这四张图片 我们该怎么对其进行变换来达到tvOS的视差效果呢?<br>重新观察上文中苹果官方的例子视频 我们可以得出以下结论: </p>
<h3 id="1-_总图层在旋转_但不同于一般在屏幕平面上的旋转_而是相对屏幕具有一定夹角的旋转">1. 总图层在旋转 但不同于一般在屏幕平面上的旋转 而是相对屏幕具有一定夹角的旋转</h3><p>如果不太了解这种旋转是怎么发生的 我们可以看一张有关 CATransform3D 的图:<br><img src="/img/2015/11/tvOS/tvOSButtonPic_2.jpg" alt="CATransform3DMakeRotation的XYZ轴"><br>我们常用的 CGRect 有 X 和 Y 两个位置参数 而 CATransform3D 可以理解为在日常的两个轴以外加了 Z 轴 方向为从手机上表面竖直向下 如图<br>这么一来 我们日常所见的在屏幕平面上的旋转(比如屏幕旋转)其实是绕 Z 轴旋转<br>而绕 X 和 Y 轴的旋转 便是让 CALayer 具有相对屏幕具有一定夹角的旋转 具体表现就是如同tvOS按钮一样有远近效果(其实在透视效果上还是有些不一样 后面会提怎么解决)<br><code>CATransform3DMakeRotation</code> 就是这么通过旋转角度定义的:<br><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">CATransform3D</span> <span class="built_in">CATransform3DMakeRotation</span> (</span><br><span class="line"> <span class="built_in">CGFloat</span> angle, <span class="comment">//绕着向量(x,y,z)旋转的角度</span></span><br><span class="line"> <span class="built_in">CGFloat</span> x, <span class="comment">//x轴分量</span></span><br><span class="line"> <span class="built_in">CGFloat</span> y, <span class="comment">//y轴分量</span></span><br><span class="line"> <span class="built_in">CGFloat</span> z <span class="comment">//z轴分量</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure></p>
<h3 id="2-除去旋转_每个图层都在进行不同半径的圆周移动">2.除去旋转 每个图层都在进行不同半径的圆周移动</h3><p>为什么有移动?<br>透视是创造三维深度感觉的关键 而透视效果最直白的话来说 就是“近大远小”<br>让我们来看个例子吧 :-)<br><img src="/img/2015/11/tvOS/tvOSButtonPic_3.jpg" alt="近大远小的说明"><br>如果只有总图层自转 分图层不进行移动 那么整个按钮虽然有自转效果 但是看起来还是平的<br>如果要保证有三维效果 就要有视差 即近大远小 让<strong>远处的图层移动的距离很小 近处的图层移动距离很大</strong>(大家可以自行想象同样速度远处近处的汽车 看起来移动的距离也不一样)<br>因此 就要令分图层进行圆周移动 离我们近的图层 圆周半径要更大些 保证看起来移动的距离更大<br>我们简单地用 Principle 做了一个<a href="/files/2015/11/tvOS/tvOS_Principle_1.prd">原型</a> 大体效果应该是这个样子的 中间的圆点是移动的轴心<br><img src="/img/2015/11/tvOS/tvOSButtonGif_1.gif" alt="简陋的圆周移动效果"></p>
<h3 id="3-总的图层也会移动">3.总的图层也会移动</h3><p>看到我们刚才那个简陋的效果了没?你有可能会想 为什么看起来和tvOS差别那么大?<br>原因是 tvOS实现的效果 整个按钮并没有明显地移动 而是近似于固定在某个位置的 这么一来 就要求我们在分图层移动的时候 总图层叠加一项反方向的移动 保证按钮固定住<br>于是我们又用 Principle 做了一个<a href="/files/2015/11/tvOS/tvOS_Principle_2.prd">原型</a> 大体效果应该是这个样子的<br><img src="/img/2015/11/tvOS/tvOSButtonGif_2.gif" alt="还是很简陋的圆周移动效果"> </p>
<h3 id="4-高光的移动方向恰好相反">4.高光的移动方向恰好相反</h3><p>高光就是我们在tvOS的图标上看到的白色反光 这个部分其实很简单:<br>用PS画一个白色的圆 加上模糊效果 就是一个 高光图层<br>让图层在移动的时候于其他图层方向相反 即让图层叠加之后的效果为 高光永远在离我们最近的地方 这里说起来会有点困惑 但是用代码实现的时候就自然明白了 ^_^</p>
<h1 id="实现">实现</h1><blockquote>
<p>注:实现部分限于篇幅 不可能将所有代码都粘贴出来 只是在几个关键的地方粘贴出来加以说明<br>完整代码见 <a href="https://github.com/JustinFincher/JZtvOSParallaxButton" target="_blank" rel="external">https://github.com/JustinFincher/JZtvOSParallaxButton</a> </p>
</blockquote>
<h3 id="1-按钮基本">1.按钮基本</h3><p>按照我们的计划 这个按钮默认并没有三维效果 就是很多UIImage叠加起来 只有当我们长按的时候 才会有各种旋转和移动<br>这里动画方式分为两种 第一种是自动动画 首先会移动和旋转到一个特定的角度 然后便开始周期移动了 第二种是手动动画 按钮会跟随手指Drag进行旋转和移动<br>让我们先新建一个UIButton吧 :-)<br><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//JZParallaxButton.h</span></span><br><span class="line"><span class="preprocessor"># import <span class="title"><UIKit/UIKit.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="built_in">NS_ENUM</span>(<span class="built_in">NSInteger</span>, RotateMethodType)</span><br><span class="line">{</span><br><span class="line"> RotateMethodTypeAutoRotate = <span class="number">0</span>, <span class="comment">//自动动画</span></span><br><span class="line"> RotateMethodTypeWithFinger, <span class="comment">//跟随手指</span></span><br><span class="line"> RotateMethodTypeWithFingerReverse, <span class="comment">//跟随手指 但反向</span></span><br><span class="line">};</span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">JZParallaxButton</span> : <span class="title">UIButton</span></span></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure></p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//JZParallaxButton.m</span></span><br><span class="line"><span class="preprocessor"># define LongPressInterval 0.5 //自动动画状态下的长按判断时间</span></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">UIButton</span> ()<<span class="title">UIGestureRecognizerDelegate</span>></span></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">JZParallaxButton</span></span></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>写一个自己的init方法 然后在里面加入长按的手势判断<br><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//JZParallaxButton.m</span></span><br><span class="line">- (instancetype)initButtonWith<span class="built_in">CGRect</span>:(<span class="built_in">CGRect</span>)RectInfo</span><br><span class="line"> WithLayerArray:(<span class="built_in">NSMutableArray</span> *)ArrayOfLayer</span><br><span class="line"> WithRoundCornerEnabled:(<span class="built_in">BOOL</span>)isRoundCorner</span><br><span class="line"> WithCornerRadiusifEnabled:(<span class="built_in">CGFloat</span>)Radius</span><br><span class="line"> WithRotationFrames:(<span class="keyword">int</span>)Frames</span><br><span class="line"> WithRotationInterval:(<span class="built_in">CGFloat</span>)Interval</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">self</span> = [<span class="keyword">super</span> initWithFrame:RectInfo];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//...省略...</span></span><br><span class="line"> </span><br><span class="line"> <span class="built_in">UILongPressGestureRecognizer</span> *longPress = [[<span class="built_in">UILongPressGestureRecognizer</span> alloc] initWithTarget:<span class="keyword">self</span> action:<span class="keyword">@selector</span>(selfLongPressed:)];</span><br><span class="line"> longPress<span class="variable">.delegate</span> = <span class="keyword">self</span>;</span><br><span class="line"> longPress<span class="variable">.minimumPressDuration</span> = LongPressInterval;</span><br><span class="line"> <span class="comment">//self就是UIButton了 所以可以对self add</span></span><br><span class="line"> [<span class="keyword">self</span> addGestureRecognizer:longPress];</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">self</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//JZParallaxButton.m</span></span><br><span class="line"><span class="comment">//长按会触发的方法</span></span><br><span class="line">- (<span class="keyword">void</span>)selfLongPressed:(<span class="built_in">UILongPressGestureRecognizer</span> *)sender</span><br><span class="line">{</span><br><span class="line"> <span class="built_in">CGPoint</span> PressedPoint = [sender locationInView:<span class="keyword">self</span>];</span><br><span class="line"> <span class="built_in">NSLog</span>(<span class="string">@"%F , %F"</span>,PressedPoint<span class="variable">.x</span>,PressedPoint<span class="variable">.y</span>); </span><br><span class="line"> <span class="comment">//可以读取当前按下的位置</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="2-层级关系和逻辑判断">2.层级关系和逻辑判断</h3><p>我们的按钮在实现后应有以下层级:<br><img src="/img/2015/11/tvOS/tvOSButtonPic_4.jpg" alt="层级关系"><br><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">ParallaxButton:</span>UIButton <span class="comment">//我们建立的UIButton SubClass</span></span><br><span class="line">|-<span class="string">BoundsView:</span>UIView <span class="comment">//总视图 </span></span><br><span class="line"> |--<span class="string">LayerImageView1:</span>UIImageView <span class="comment">//分视图 是总视图的SubView</span></span><br><span class="line"> |--<span class="string">LayerImageView2:</span>UIImageView</span><br><span class="line"> |--<span class="string">LayerImageView3:</span>UIImageView</span><br><span class="line"> |--<span class="string">LayerImageView4:</span>UIImageView</span><br><span class="line"> |--<span class="string">LayerImageView5:</span>UIImageView</span><br><span class="line"> |-- .... </span><br><span class="line"> |--<span class="string">LayerImageViewX:</span>UIImageView</span><br></pre></td></tr></table></figure></p>
<p>有的同学有可能会觉得 为什么需要总视图这个 <code>BoundsView</code> 呢 直接将所有的UIImageView都划归为UIButton的SubView不就好了么?<br>实验过直接将UIImageView添加到UIButton为SubView后 我有一个相对合理的解释:<br>我们之前分析原理的时候 说明其实是只有总图层(即 <code>BoundsView</code> )在旋转的 分图层只需处理移动问题<br>如果去除了总图层 就只能让每个分图层(即 <code>LayerImageView</code> )在移动的同时都旋转 这势必带来一个问题 那就是会有“厚度”的感觉<br>让我们实验下 如果层级关系如下图 会是什么结果<br><img src="/img/2015/11/tvOS/tvOSButtonGif_3.gif" alt="错误的显示效果"><br>可以看到 这里的图层移动方式和原型里的效果已经很接近了 但是因为分图层移动半径不一 并且没有总图层进行约束 导致分图层的显示区域不在一个长方形里 看起来像是有厚度了一样 整个按钮实际看起来并没有tvOS按钮里的那种轻盈感<br>因此 需要有总图层进行约束 即将分图层添加为总图层的SubView 并设置总图层Layer的MasksToBounds为YES 这时 所有分图层的可见区域都受限制于总图层 无论怎么旋转和移动都不会出现厚度感了<br>我们现在将视图层级需要的一些变量写出来 然后再实现一些逻辑判断的代码 比如长按后需要做什么<br><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//JZParallaxButton.h</span></span><br><span class="line"><span class="preprocessor"># import <span class="title"><UIKit/UIKit.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="built_in">NS_ENUM</span>(<span class="built_in">NSInteger</span>, ParallaxMethodType)</span><br><span class="line">{</span><br><span class="line"> ParallaxMethodTypeLinear = <span class="number">0</span>,</span><br><span class="line"> ParallaxMethodTypeEaseIn,</span><br><span class="line"> ParallaxMethodTypeEaseOut,</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">JZParallaxButton</span> : <span class="title">UIButton</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">//数组用于记录当前Button包含的所有ImageLayer 即分图层</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">strong</span>) <span class="built_in">NSMutableArray</span> *LayerArray;</span><br><span class="line"></span><br><span class="line"><span class="comment">//button圆角</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) <span class="built_in">BOOL</span> RoundCornerEnabled;</span><br><span class="line"></span><br><span class="line"><span class="comment">//button圆角</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) <span class="built_in">CGFloat</span> RoundCornerRadius;</span><br><span class="line"></span><br><span class="line"><span class="comment">//是否在Parallax</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) <span class="built_in">BOOL</span> isParallax;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) <span class="keyword">int</span> RotationAllSteps;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) <span class="built_in">CGFloat</span> RotationInterval;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) <span class="built_in">CGFloat</span> ScaleBase;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) <span class="built_in">CGFloat</span> ScaleAddition;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) ParallaxMethodType ParallaxMethod;</span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) RotateMethodType RotateMethod;</span><br><span class="line"></span><br><span class="line">- (instancetype)initButtonWith<span class="built_in">CGRect</span>:(<span class="built_in">CGRect</span>)RectInfo</span><br><span class="line"> WithLayerArray:(<span class="built_in">NSMutableArray</span> *)ArrayOfLayer</span><br><span class="line"> WithRoundCornerEnabled:(<span class="built_in">BOOL</span>)isRoundCorner</span><br><span class="line"> WithCornerRadiusifEnabled:(<span class="built_in">CGFloat</span>)Radius</span><br><span class="line"> WithRotationFrames:(<span class="keyword">int</span>)Frames</span><br><span class="line"> WithRotationInterval:(<span class="built_in">CGFloat</span>)Interval;</span><br></pre></td></tr></table></figure></p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//JZParallaxButton.m</span></span><br><span class="line"></span><br><span class="line"><span class="preprocessor"># define RotateParameter 0.5 //用于调整旋转幅度</span></span><br><span class="line"><span class="preprocessor"># define SpotlightOutRange 20.0f //用于高光距离中心的长度</span></span><br><span class="line"><span class="preprocessor"># define zPositionMax 800 //Core Layer变换时摄像机默认z轴位置</span></span><br><span class="line"></span><br><span class="line"><span class="preprocessor"># define BoundsVieTranslation 50 //总图层的移动幅度</span></span><br><span class="line"><span class="preprocessor"># define LayerVieTranslation 20 //分图层的移动幅度</span></span><br><span class="line"><span class="preprocessor"># define LongPressInterval 0.5 //自动动画状态下的长按判断时间 </span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">UIButton</span> ()<<span class="title">UIGestureRecognizerDelegate</span>></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) <span class="keyword">int</span> RotationNowStep; <span class="comment">//记录动画的当前状态</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">weak</span>)<span class="built_in">NSTimer</span> *RotationTimer; <span class="comment">//动画计时器</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">strong</span>) <span class="built_in">UIImageView</span> *SpotLightView; <span class="comment">//高光图层</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">strong</span>) <span class="built_in">UIView</span> *BoundsView; <span class="comment">//总图层</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) <span class="built_in">CGPoint</span> TouchPointInSelf; <span class="comment">//手指按下的时候在Button内部 的位置 用于Button设为跟随手指的时候</span></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>,<span class="keyword">assign</span>) <span class="built_in">BOOL</span> hasPreformedBeginAnimation; <span class="comment">//判断是否在进行动画 防止动画未表演完就触发下一个动作 造成错位</span></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">JZParallaxButton</span></span></span><br><span class="line"><span class="comment">//省略 @synthesize ...</span></span><br><span class="line"></span><br><span class="line">- (instancetype)initButtonWith<span class="built_in">CGRect</span>:(<span class="built_in">CGRect</span>)RectInfo</span><br><span class="line"> WithLayerArray:(<span class="built_in">NSMutableArray</span> *)ArrayOfLayer</span><br><span class="line"> WithRoundCornerEnabled:(<span class="built_in">BOOL</span>)isRoundCorner</span><br><span class="line"> WithCornerRadiusifEnabled:(<span class="built_in">CGFloat</span>)Radius</span><br><span class="line"> WithRotationFrames:(<span class="keyword">int</span>)Frames</span><br><span class="line"> WithRotationInterval:(<span class="built_in">CGFloat</span>)Interval</span><br><span class="line">{</span><br><span class="line"><span class="comment">//省略之前的代码....</span></span><br><span class="line"> LayerArray = [[<span class="built_in">NSMutableArray</span> alloc] initWithCapacity:[ArrayOfLayer count]];</span><br><span class="line"> </span><br><span class="line"> BoundsView = [[<span class="built_in">UIView</span> alloc] initWithFrame:<span class="built_in">CGRectMake</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="keyword">self</span><span class="variable">.frame</span><span class="variable">.size</span><span class="variable">.width</span>, <span class="keyword">self</span><span class="variable">.frame</span><span class="variable">.size</span><span class="variable">.height</span>)];</span><br><span class="line"> BoundsView<span class="variable">.layer</span><span class="variable">.masksToBounds</span> = <span class="literal">YES</span>;</span><br><span class="line"> BoundsView<span class="variable">.layer</span><span class="variable">.shouldRasterize</span> = <span class="literal">TRUE</span>;</span><br><span class="line"> BoundsView<span class="variable">.layer</span><span class="variable">.rasterizationScale</span> = [[<span class="built_in">UIScreen</span> mainScreen] scale];</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">self</span><span class="variable">.RoundCornerEnabled</span>)</span><br><span class="line"> {</span><br><span class="line"> BoundsView<span class="variable">.layer</span><span class="variable">.cornerRadius</span> = <span class="keyword">self</span><span class="variable">.RoundCornerRadius</span>;</span><br><span class="line"> }</span><br><span class="line"> [<span class="keyword">self</span> addSubview:BoundsView];</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < [ArrayOfLayer count]; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">UIImageView</span> *LayerImageView = [[<span class="built_in">UIImageView</span> alloc] initWithFrame:<span class="built_in">CGRectMake</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="keyword">self</span><span class="variable">.frame</span><span class="variable">.size</span><span class="variable">.width</span>, <span class="keyword">self</span><span class="variable">.frame</span><span class="variable">.size</span><span class="variable">.height</span>)];</span><br><span class="line"> <span class="built_in">UIImage</span> *LayerImage = [ArrayOfLayer objectAtIndex:i];</span><br><span class="line"> [LayerImageView setImage:LayerImage];</span><br><span class="line"> LayerImageView<span class="variable">.layer</span><span class="variable">.shouldRasterize</span> = <span class="literal">TRUE</span>;</span><br><span class="line"> LayerImageView<span class="variable">.layer</span><span class="variable">.rasterizationScale</span> = [[<span class="built_in">UIScreen</span> mainScreen] scale];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//从下往上添加</span></span><br><span class="line"> [BoundsView addSubview:LayerImageView];</span><br><span class="line"> [LayerArray addObject:LayerImageView];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//如果把所有分图层都加完了</span></span><br><span class="line"> <span class="keyword">if</span> (i == [ArrayOfLayer count] - <span class="number">1</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//在最上层添加高光分图层</span></span><br><span class="line"> SpotLightView = [[<span class="built_in">UIImageView</span> alloc] initWithFrame:<span class="built_in">CGRectMake</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="keyword">self</span><span class="variable">.frame</span><span class="variable">.size</span><span class="variable">.width</span>,<span class="keyword">self</span><span class="variable">.frame</span><span class="variable">.size</span><span class="variable">.height</span>)];</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">NSString</span> *bundlePath = [[<span class="built_in">NSBundle</span> bundleForClass:[JZParallaxButton class]]</span><br><span class="line"> pathForResource:<span class="string">@"JZParallaxButton"</span> ofType:<span class="string">@"bundle"</span>];</span><br><span class="line"> <span class="built_in">NSBundle</span> *bundle = [<span class="built_in">NSBundle</span> bundleWithPath:bundlePath];</span><br><span class="line"> </span><br><span class="line"> SpotLightView<span class="variable">.image</span> = [<span class="built_in">UIImage</span> imageNamed:<span class="string">@"Spotlight"</span> inBundle:bundle compatibleWithTraitCollection:<span class="literal">nil</span>];</span><br><span class="line"> SpotLightView<span class="variable">.contentMode</span> = <span class="built_in">UIViewContentModeScaleAspectFit</span>;</span><br><span class="line"> SpotLightView<span class="variable">.layer</span><span class="variable">.masksToBounds</span> = <span class="literal">YES</span>;</span><br><span class="line"> [BoundsView addSubview:SpotLightView];</span><br><span class="line"> SpotLightView<span class="variable">.layer</span><span class="variable">.zPosition</span> = zPositionMax;</span><br><span class="line"> [<span class="keyword">self</span> bringSubviewToFront:SpotLightView];</span><br><span class="line"> SpotLightView<span class="variable">.alpha</span> = <span class="number">0.0</span>;</span><br><span class="line"> [LayerArray addObject:SpotLightView];</span><br><span class="line"> }</span><br><span class="line">} </span><br><span class="line">- (<span class="keyword">void</span>)selfLongPressed:(<span class="built_in">UILongPressGestureRecognizer</span> *)sender</span><br><span class="line">{</span><br><span class="line"> <span class="built_in">CGPoint</span> PressedPoint = [sender locationInView:<span class="keyword">self</span>];</span><br><span class="line"> <span class="comment">//NSLog(@"%F , %F",PressedPoint.x,PressedPoint.y);</span></span><br><span class="line"> <span class="keyword">self</span><span class="variable">.TouchPointInSelf</span> = PressedPoint;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span>(sender<span class="variable">.state</span> == <span class="built_in">UIGestureRecognizerStateBegan</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//NSLog(@"Long Press");</span></span><br><span class="line"> <span class="keyword">self</span><span class="variable">.hasPreformedBeginAnimation</span> = <span class="literal">NO</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">switch</span> (<span class="keyword">self</span><span class="variable">.RotateMethod</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> RotateMethodTypeAutoRotate:</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//长按后 如果在进行视差效果就结束 如果现在是普通状态就开启视差效果</span></span><br><span class="line"> <span class="keyword">if</span> (isParallax)</span><br><span class="line"> {</span><br><span class="line"> [<span class="keyword">self</span> EndParallax];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> [<span class="keyword">self</span> BeginParallax];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">case</span> RotateMethodTypeWithFinger:</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//手动动画结束视差效果并不依靠长按 而是通过判断手指是否留在屏幕上</span></span><br><span class="line"> <span class="keyword">if</span> (!isParallax)</span><br><span class="line"> {</span><br><span class="line"> [<span class="keyword">self</span> BeginParallax];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (sender<span class="variable">.state</span> == <span class="built_in">UIGestureRecognizerStateEnded</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//如果长按结束但仍有动画在进行 就等待 LongPressInterval 再执行TouchUp方法 否则立即执行TouchUp</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">self</span><span class="variable">.hasPreformedBeginAnimation</span> == <span class="literal">NO</span>)</span><br><span class="line"> {</span><br><span class="line"> [<span class="keyword">self</span> performSelector:<span class="keyword">@selector</span>(TouchUp) withObject:<span class="keyword">self</span> afterDelay:LongPressInterval ];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> [<span class="keyword">self</span> TouchUp];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>
<p>这里的 <code>shouldRasterize</code> :<br><figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">@<span class="keyword">property</span> BOOL shouldRasterize</span><br><span class="line">Description A Boolean <span class="keyword">that</span> indicates whether <span class="keyword">the</span> layer <span class="keyword">is</span> rendered <span class="keyword">as</span> a bitmap <span class="keyword">before</span> compositing. Animatable</span><br></pre></td></tr></table></figure></p>
<p>在某些时候 例如导入的图片分辨率较大 此时对UIImageView进行CA动画 会出现锯齿 这个时候 可以通过设置 <code>shouldRasterize</code> 来解决<br><figure class="highlight armasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">BoundsView.layer.shouldRasterize </span>= YES<span class="comment">;</span></span><br><span class="line"><span class="keyword">BoundsView.layer.rasterizationScale </span>= [[UIScreen mainScreen] scale]<span class="comment">;</span></span><br></pre></td></tr></table></figure></p>
<p>当然设置 <code>allowsEdgeAntialiasing</code> 也是一种方法 这里我们对比下 可以注意看右边墙壁上的树影 通过 <code>shouldRasterize</code> 设置后 基本上影子不会出现较大的变动:<br><img src="/img/2015/11/tvOS/tvOSButtonGif_4.gif" alt="两种抗锯齿的对比"></p>
<h3 id="3-透视效果需要实现的一个方法">3.透视效果需要实现的一个方法</h3><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//JZParallaxButton.m</span></span><br><span class="line"><span class="comment">//CATransform3DMake 默认给出的CATransform3D是没有透视效果的 需要手动加入这一段 完成从 orthographic 到 perspective 的改变</span></span><br><span class="line"><span class="comment">//这个方法的解析可以看 http://wonderffee.github.io/blog/2013/10/19/an-analysis-for-transform-samples-of-calayer/</span></span><br><span class="line"><span class="built_in">CATransform3D</span> <span class="built_in">CATransform3DMakePerspective</span>(<span class="built_in">CGPoint</span> center, <span class="keyword">float</span> disZ)</span><br><span class="line">{</span><br><span class="line"><span class="built_in">CATransform3D</span> transToCenter = <span class="built_in">CATransform3DMakeTranslation</span>(-center<span class="variable">.x</span>, -center<span class="variable">.y</span>, <span class="number">0</span>);</span><br><span class="line"><span class="built_in">CATransform3D</span> transBack = <span class="built_in">CATransform3DMakeTranslation</span>(center<span class="variable">.x</span>, center<span class="variable">.y</span>, <span class="number">0</span>);</span><br><span class="line"><span class="built_in">CATransform3D</span> scale = <span class="built_in">CATransform3DIdentity</span>;</span><br><span class="line">scale<span class="variable">.m34</span> = -<span class="number">1.0</span>f/disZ;</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">CATransform3DConcat</span>(<span class="built_in">CATransform3DConcat</span>(transToCenter, scale), transBack);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">CATransform3D</span> <span class="built_in">CATransform3DPerspect</span>(<span class="built_in">CATransform3D</span> t, <span class="built_in">CGPoint</span> center, <span class="keyword">float</span> disZ)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">CATransform3DConcat</span>(t, <span class="built_in">CATransform3DMakePerspective</span>(center, disZ));</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="4-实现自动动画">4.实现自动动画</h3><p>这部分主要内容就是 在长按后 按钮会先进入某个特定的角度位置 然后进行自转<br>这里为了简单 使用了NSTimer进行每一帧的计数 但需要注意的是 NSTimer的精度不足以完成真正流畅的动画<br>自动动画里 总图层和分图层的移动 旋转都和两个参数有关:一是当前的计数位置(即Step) 而是图层在总按钮里的层级位置(即LayerArray里的i) 通过这两个参数进行计算CATransform3D<br><figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// JZParallaxButton.m</span></span><br><span class="line"></span><br><span class="line"><span class="preprocessor">#define OutTranslationParameter (float)([LayerArray count] + i)/(float)([LayerArray count] * 2)</span></span><br><span class="line"><span class="preprocessor">#define OutScaleParameter ScaleBase+ScaleAddition/5*((float)i/(float)([LayerArray count]))</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">JZParallaxButton</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)BeginAutoRotation</span><br><span class="line">{</span><br><span class="line"> __<span class="keyword">weak</span> JZParallaxButton *weakSelf = <span class="keyword">self</span>;</span><br><span class="line"> <span class="comment">//需要到达动画的起始位置</span></span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CGFloat</span> PIE = <span class="number">0</span>;</span><br><span class="line"> <span class="built_in">CGFloat</span> Degress = M_PI*<span class="number">2</span>*PIE;</span><br><span class="line"> <span class="comment">//NSlog(@"Degress : %f PIE",PIE);</span></span><br><span class="line"> <span class="built_in">CGFloat</span> Sin = sin(Degress)/<span class="number">4</span>;</span><br><span class="line"> <span class="built_in">CGFloat</span> Cos = cos(Degress)/<span class="number">4</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span> i =<span class="number">0</span>;</span><br><span class="line"> <span class="comment">//计算初始位置的旋转 移动 和缩放</span></span><br><span class="line"> <span class="built_in">CATransform3D</span> NewRotate,NewTranslation,NewScale;</span><br><span class="line"> NewRotate = <span class="built_in">CATransform3DConcat</span>(<span class="built_in">CATransform3DMakeRotation</span>(-Sin*RotateParameter, <span class="number">0</span>, <span class="number">1</span>, <span class="number">0</span>), <span class="built_in">CATransform3DMakeRotation</span>(Cos*RotateParameter, <span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>));</span><br><span class="line"> NewTranslation = <span class="built_in">CATransform3DMakeTranslation</span>(Sin*BoundsVieTranslation*OutTranslationParameter, Cos*BoundsVieTranslation*OutTranslationParameter, <span class="number">0</span>);</span><br><span class="line"> NewScale = <span class="built_in">CATransform3DMakeScale</span>(OutScaleParameter, OutScaleParameter, <span class="number">1</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//使用CATransform3DConcat将三个CATransform3D结合成一个CATransform3D</span></span><br><span class="line"> <span class="built_in">CATransform3D</span> TwoTransform = <span class="built_in">CATransform3DConcat</span>(NewRotate,NewTranslation);</span><br><span class="line"> <span class="built_in">CATransform3D</span> AllTransform = <span class="built_in">CATransform3DConcat</span>(TwoTransform,NewScale);</span><br><span class="line"> <span class="comment">//对BoundsLayer 即总图层进行动画</span></span><br><span class="line"> <span class="built_in">CABasicAnimation</span> *BoundsLayer<span class="built_in">CABasicAnimation</span> = [<span class="built_in">CABasicAnimation</span> animationWithKeyPath:<span class="string">@"transform"</span>];</span><br><span class="line"> BoundsLayer<span class="built_in">CABasicAnimation</span><span class="variable">.duration</span> = <span class="number">0.4</span>f;</span><br><span class="line"> BoundsLayer<span class="built_in">CABasicAnimation</span><span class="variable">.autoreverses</span> = <span class="literal">NO</span>;</span><br><span class="line"> BoundsLayer<span class="built_in">CABasicAnimation</span><span class="variable">.toValue</span> = [<span class="built_in">NSValue</span> valueWith<span class="built_in">CATransform3D</span>:<span class="built_in">CATransform3DPerspect</span>(AllTransform, <span class="built_in">CGPointMake</span>(<span class="number">0</span>, <span class="number">0</span>), zPositionMax)];</span><br><span class="line"> BoundsLayer<span class="built_in">CABasicAnimation</span><span class="variable">.fromValue</span> = [<span class="built_in">NSValue</span> valueWith<span class="built_in">CATransform3D</span>:BoundsView<span class="variable">.layer</span><span class="variable">.transform</span>];</span><br><span class="line"> BoundsLayer<span class="built_in">CABasicAnimation</span><span class="variable">.fillMode</span> = k<span class="built_in">CAFillModeBoth</span>;</span><br><span class="line"> BoundsLayer<span class="built_in">CABasicAnimation</span><span class="variable">.removedOnCompletion</span> = <span class="literal">YES</span>;</span><br><span class="line"> [BoundsView<span class="variable">.layer</span> addAnimation:BoundsLayer<span class="built_in">CABasicAnimation</span> forKey:<span class="string">@"BoundsLayerCABasicAnimation"</span>];</span><br><span class="line"> BoundsView<span class="variable">.layer</span><span class="variable">.transform</span> = <span class="built_in">CATransform3DPerspect</span>(AllTransform, <span class="built_in">CGPointMake</span>(<span class="number">0</span>, <span class="number">0</span>), zPositionMax);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//对LayerArray内的UIImageView 即分图层进行动画</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span> ; i < [LayerArray count]; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//对于不同的前后位置 需要移动的半径不一样</span></span><br><span class="line"> <span class="keyword">float</span> InTranslationParameter = [<span class="keyword">self</span> InTranslationParameterWithLayerArray:LayerArray WithIndex:i];</span><br><span class="line"> <span class="keyword">float</span> InScaleParameter = [<span class="keyword">self</span> InScaleParameterWithLayerArray:LayerArray WithIndex:i];</span><br><span class="line"> <span class="built_in">UIImageView</span> *LayerImageView = [LayerArray objectAtIndex:i];</span><br><span class="line"></span><br><span class="line"> <span class="built_in">CATransform3D</span> NewTranslation ;</span><br><span class="line"> <span class="built_in">CATransform3D</span> NewScale = <span class="built_in">CATransform3DMakeScale</span>(InScaleParameter, InScaleParameter, <span class="number">1</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (i == [LayerArray count] - <span class="number">1</span>) <span class="comment">//是高光所在的View</span></span><br><span class="line"> {</span><br><span class="line"> NewTranslation = <span class="built_in">CATransform3DMakeTranslation</span>(Sin*LayerVieTranslation*InTranslationParameter*SpotlightOutRange, Cos*LayerVieTranslation*InTranslationParameter*SpotlightOutRange, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="comment">//分图层其他的View 注意可以看到这里的 Translation 和高光是相反的</span></span><br><span class="line"> {</span><br><span class="line"> NewTranslation = <span class="built_in">CATransform3DMakeTranslation</span>(-Sin*LayerVieTranslation*InTranslationParameter, -Cos*LayerVieTranslation*InTranslationParameter, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CATransform3D</span> NewAllTransform = <span class="built_in">CATransform3DConcat</span>(NewTranslation,NewScale);</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CABasicAnimation</span> *LayerImageView<span class="built_in">CABasicAnimation</span> = [<span class="built_in">CABasicAnimation</span> animationWithKeyPath:<span class="string">@"transform"</span>];</span><br><span class="line"> LayerImageView<span class="built_in">CABasicAnimation</span><span class="variable">.duration</span> = <span class="number">0.4</span>f;</span><br><span class="line"> LayerImageView<span class="built_in">CABasicAnimation</span><span class="variable">.autoreverses</span> = <span class="literal">NO</span>;</span><br><span class="line"> LayerImageView<span class="built_in">CABasicAnimation</span><span class="variable">.toValue</span> = [<span class="built_in">NSValue</span> valueWith<span class="built_in">CATransform3D</span>:NewAllTransform];</span><br><span class="line"> LayerImageView<span class="built_in">CABasicAnimation</span><span class="variable">.fromValue</span> = [<span class="built_in">NSValue</span> valueWith<span class="built_in">CATransform3D</span>:LayerImageView<span class="variable">.layer</span><span class="variable">.transform</span>];</span><br><span class="line"> LayerImageView<span class="built_in">CABasicAnimation</span><span class="variable">.fillMode</span> = k<span class="built_in">CAFillModeBoth</span>;</span><br><span class="line"> LayerImageView<span class="built_in">CABasicAnimation</span><span class="variable">.removedOnCompletion</span> = <span class="literal">YES</span>;</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CAAnimationGroup</span> *animGroup = [<span class="built_in">CAAnimationGroup</span> animation];</span><br><span class="line"> animGroup<span class="variable">.animations</span> = [<span class="built_in">NSArray</span> arrayWithObjects:LayerImageView<span class="built_in">CABasicAnimation</span>,<span class="literal">nil</span>];</span><br><span class="line"> animGroup<span class="variable">.duration</span> = <span class="number">0.4</span>f;</span><br><span class="line"> animGroup<span class="variable">.removedOnCompletion</span> = <span class="literal">YES</span>;</span><br><span class="line"> animGroup<span class="variable">.autoreverses</span> = <span class="literal">NO</span>;</span><br><span class="line"> animGroup<span class="variable">.fillMode</span> = k<span class="built_in">CAFillModeRemoved</span>;</span><br><span class="line"> </span><br><span class="line"> [<span class="built_in">CATransaction</span> begin];</span><br><span class="line"> LayerImageView<span class="variable">.layer</span><span class="variable">.transform</span> = <span class="built_in">CATransform3DPerspect</span>(NewAllTransform, <span class="built_in">CGPointMake</span>(<span class="number">0</span>, <span class="number">0</span>), zPositionMax);</span><br><span class="line"> [<span class="built_in">CATransaction</span> setCompletionBlock:^</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (i == [LayerArray count] - <span class="number">1</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//简单的周期循环</span></span><br><span class="line"> RotationNowStep = <span class="number">0</span>;</span><br><span class="line"> RotationTimer = [<span class="built_in">NSTimer</span> scheduledTimerWithTimeInterval:RotationInterval/RotationAllSteps target:weakSelf selector:<span class="keyword">@selector</span>(RotationCreator) userInfo:<span class="literal">nil</span> repeats:<span class="literal">YES</span>];</span><br><span class="line"> weakSelf<span class="variable">.hasPreformedBeginAnimation</span> = <span class="literal">YES</span>;</span><br><span class="line"> }</span><br><span class="line"> }];</span><br><span class="line"> [LayerImageView<span class="variable">.layer</span> addAnimation:animGroup forKey:<span class="string">@"LayerImageViewParallaxInitAnimation"</span>];</span><br><span class="line"> [<span class="built_in">CATransaction</span> commit];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//计时器每次触发要执行的方法</span></span><br><span class="line">- (<span class="keyword">void</span>)RotationCreator</span><br><span class="line">{</span><br><span class="line"> __<span class="keyword">weak</span> JZParallaxButton *weakSelf = <span class="keyword">self</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//NSlog(@"RotationNowStep : %d of %d",RotationNowStep,RotationAllSteps);</span></span><br><span class="line"> <span class="keyword">if</span> (RotationNowStep == RotationAllSteps)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//一周完成 计数器置1</span></span><br><span class="line"> RotationNowStep = <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> RotationNowStep ++ ;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CGFloat</span> PIE = (<span class="keyword">float</span>)RotationNowStep/(<span class="keyword">float</span>)RotationAllSteps;</span><br><span class="line"> <span class="built_in">CGFloat</span> Degress = M_PI*<span class="number">2</span>*PIE;</span><br><span class="line"> <span class="comment">//NSlog(@"Degress : %f PIE",PIE);</span></span><br><span class="line"> <span class="built_in">CGFloat</span> Sin = sin(Degress)/<span class="number">4</span>;</span><br><span class="line"> <span class="built_in">CGFloat</span> Cos = cos(Degress)/<span class="number">4</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line"> <span class="built_in">CATransform3D</span> NewRotate,NewTranslation,NewScale;</span><br><span class="line"> NewRotate = <span class="built_in">CATransform3DConcat</span>(<span class="built_in">CATransform3DMakeRotation</span>(-Sin*RotateParameter, <span class="number">0</span>, <span class="number">1</span>, <span class="number">0</span>), <span class="built_in">CATransform3DMakeRotation</span>(Cos*RotateParameter, <span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>));</span><br><span class="line"> NewTranslation = <span class="built_in">CATransform3DMakeTranslation</span>(Sin*BoundsVieTranslation*OutTranslationParameter, Cos*BoundsVieTranslation*OutTranslationParameter, <span class="number">0</span>);</span><br><span class="line"> NewScale = <span class="built_in">CATransform3DMakeScale</span>(OutScaleParameter, OutScaleParameter, <span class="number">1</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CATransform3D</span> TwoTransform = <span class="built_in">CATransform3DConcat</span>(NewRotate,NewTranslation);</span><br><span class="line"> <span class="built_in">CATransform3D</span> AllTransform = <span class="built_in">CATransform3DConcat</span>(TwoTransform,NewScale);</span><br><span class="line"> BoundsView<span class="variable">.layer</span><span class="variable">.transform</span> = <span class="built_in">CATransform3DPerspect</span>(AllTransform, <span class="built_in">CGPointMake</span>(<span class="number">0</span>, <span class="number">0</span>), zPositionMax);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span> ; i < [LayerArray count]; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">float</span> InScaleParameter = [<span class="keyword">self</span> InScaleParameterWithLayerArray:LayerArray WithIndex:i];</span><br><span class="line"> <span class="keyword">float</span> InTranslationParameter = [<span class="keyword">self</span> InTranslationParameterWithLayerArray:LayerArray WithIndex:i];</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (i == [LayerArray count] - <span class="number">1</span>) <span class="comment">//is spotlight</span></span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">UIImageView</span> *LayerImageView = [weakSelf<span class="variable">.LayerArray</span> objectAtIndex:i];</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CATransform3D</span> Translation = <span class="built_in">CATransform3DMakeTranslation</span>(Sin*LayerVieTranslation*InTranslationParameter*SpotlightOutRange, Cos*LayerVieTranslation*InTranslationParameter*SpotlightOutRange,<span class="number">0</span>);</span><br><span class="line"> <span class="built_in">CATransform3D</span> Scale = <span class="built_in">CATransform3DMakeScale</span>(InScaleParameter, InScaleParameter, <span class="number">1</span>);</span><br><span class="line"> <span class="built_in">CATransform3D</span> AllTransform = <span class="built_in">CATransform3DConcat</span>(Translation,Scale);</span><br><span class="line"> </span><br><span class="line"> LayerImageView<span class="variable">.layer</span><span class="variable">.transform</span> = <span class="built_in">CATransform3DPerspect</span>(AllTransform, <span class="built_in">CGPointMake</span>(<span class="number">0</span>, <span class="number">0</span>), zPositionMax);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="comment">//is Parallax layer</span></span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">UIImageView</span> *LayerImageView = [weakSelf<span class="variable">.LayerArray</span> objectAtIndex:i];</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">CATransform3D</span> Translation = <span class="built_in">CATransform3DMakeTranslation</span>(-Sin*LayerVieTranslation*InTranslationParameter, -Cos*LayerVieTranslation*InTranslationParameter, <span class="number">0</span>);</span><br><span class="line"> <span class="built_in">CATransform3D</span> Scale = <span class="built_in">CATransform3DMakeScale</span>(InScaleParameter, InScaleParameter, <span class="number">1</span>);</span><br><span class="line"> <span class="built_in">CATransform3D</span> AllTransform = <span class="built_in">CATransform3DConcat</span>(Translation,Scale);</span><br><span class="line"> </span><br><span class="line"> LayerImageView<span class="variable">.layer</span><span class="variable">.transform</span> = <span class="built_in">CATransform3DPerspect</span>(AllTransform, <span class="built_in">CGPointMake</span>(<span class="number">0</span>, <span class="number">0</span>), zPositionMax);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">float</span>)InTranslationParameterWithLayerArray:(<span class="built_in">NSMutableArray</span> *)Array</span><br><span class="line"> WithIndex:(<span class="keyword">int</span>)i</span><br><span class="line">{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">switch</span> (ParallaxMethod)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//移动半径和图层层级成线性关系</span></span><br><span class="line"> <span class="keyword">case</span> ParallaxMethodTypeLinear:</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">float</span>)(i)/(<span class="keyword">float</span>)([Array count]);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="comment">//移动半径和图层层级成二次关系</span></span><br><span class="line"> <span class="keyword">case</span> ParallaxMethodTypeEaseIn:</span><br><span class="line"> <span class="keyword">return</span> powf((<span class="keyword">float</span>)(i)/(<span class="keyword">float</span>)([Array count]), <span class="number">0.5</span>f);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="comment">//移动半径和图层层级成根号关系</span></span><br><span class="line"> <span class="keyword">case</span> ParallaxMethodTypeEaseOut:</span><br><span class="line"> <span class="keyword">return</span> powf((<span class="keyword">float</span>)(i)/(<span class="keyword">float</span>)([Array count]), <span class="number">2.0</span>f);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">float</span>)(i)/(<span class="keyword">float</span>)([Array count]);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">- (<span class="keyword">float</span>)InScaleParameterWithLayerArray:(<span class="built_in">NSMutableArray</span> *)Array</span><br><span class="line"> WithIndex:(<span class="keyword">int</span>)i</span><br><span class="line">{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">switch</span> (ParallaxMethod)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//缩放与图层层级的不同关系</span></span><br><span class="line"> <span class="keyword">case</span> ParallaxMethodTypeLinear:</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>+ScaleAddition/<span class="number">10</span>*((<span class="keyword">float</span>)i/(<span class="keyword">float</span>)([LayerArray count]));</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">case</span> ParallaxMethodTypeEaseIn:</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>+ScaleAddition/<span class="number">10</span>*powf(((<span class="keyword">float</span>)i/(<span class="keyword">float</span>)([LayerArray count])), <span class="number">0.5</span>f);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">case</span> ParallaxMethodTypeEaseOut:</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>+ScaleAddition/<span class="number">10</span>*powf(((<span class="keyword">float</span>)i/(<span class="keyword">float</span>)([LayerArray count])), <span class="number">2.0</span>f);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>+ScaleAddition/<span class="number">10</span>*((<span class="keyword">float</span>)i/(<span class="keyword">float</span>)([LayerArray count]));</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure></p>
<h3 id="4-实现手动动画">4.实现手动动画</h3><figure class="highlight processing"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//手动动画和自动动画的区别是 移动的角度不再跟进计数器计算 而是直接读取手指的CGPoint</span></span><br><span class="line">__weak JZParallaxButton *weakSelf = self;</span><br><span class="line"> CGFloat XOffest;</span><br><span class="line"> <span class="keyword">if</span> (TouchPointInSelf.x < <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> XOffest = - weakSelf.frame.<span class="built_in">size</span>.<span class="variable">width</span> / <span class="number">2</span>;</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> (TouchPointInSelf.x > weakSelf.frame.<span class="built_in">size</span>.<span class="variable">width</span>)</span><br><span class="line"> {</span><br><span class="line"> XOffest = weakSelf.frame.<span class="built_in">size</span>.<span class="variable">width</span> / <span class="number">2</span>;</span><br><span class="line"> }<span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> XOffest = TouchPointInSelf.x - weakSelf.frame.<span class="built_in">size</span>.<span class="variable">width</span> / <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> CGFloat YOffest;</span><br><span class="line"> <span class="keyword">if</span> (TouchPointInSelf.y < <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> YOffest = - weakSelf.frame.<span class="built_in">size</span>.<span class="variable">height</span> / <span class="number">2</span>;</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> (TouchPointInSelf.y > weakSelf.frame.<span class="built_in">size</span>.<span class="variable">height</span>)</span><br><span class="line"> {</span><br><span class="line"> YOffest = weakSelf.frame.<span class="built_in">size</span>.<span class="variable">height</span> / <span class="number">2</span>;</span><br><span class="line"> }<span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> YOffest = TouchPointInSelf.y - weakSelf.frame.<span class="built_in">size</span>.<span class="variable">height</span> / <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//NSLog(@"XOffest : %f , YOffest : %f",XOffest,YOffest);</span></span><br><span class="line"> </span><br><span class="line"> CGFloat XDegress = XOffest / weakSelf.frame.<span class="built_in">size</span>.<span class="variable">width</span> / <span class="number">2</span>;</span><br><span class="line"> CGFloat YDegress = YOffest / weakSelf.frame.<span class="built_in">size</span>.<span class="variable">height</span> / <span class="number">2</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//NSLog(@"XDegress : %f , YDegress : %f",XDegress,YDegress);</span></span><br></pre></td></tr></table></figure>
<h1 id="效果">效果</h1><p>此时还有很多方法没有实现(比如动画结束后需要变回原有的非三维效果) 不过大体上已经可以看到效果了 你也可以直接将完成版的<a href="https://github.com/JustinFincher/JZtvOSParallaxButton" target="_blank" rel="external">视差按钮下载下来</a><br>Have Fun :-)</p>
<h1 id="其他资源">其他资源</h1><p>如果你对真正三维状态下的按钮还是不太理解 可以点击下面的播放按钮自由查看这个三维模型 点击播放按钮后 通过三维场景右下角的左右切换查看按钮的不同旋转状态 或者点击数字1-5来快速跳转 </p>
<iframe width="640" height="480" src="https://sketchfab.com/models/899bdd009cc64af0b2260fe7570a0510/embed" frameborder="0" allowfullscreen mozallowfullscreen="true" webkitallowfullscreen="true" onmousewheel=""></iframe>
<p>以外 这篇文章里所有文件都是提供公共下载的<br>配图所用Sketch文件:<a href="/files/2015/11/tvOS/tvOS_Sketch.sketch">下载链接</a><br>三维模型文件(进入点击Download):<a href="https://sketchfab.com/models/899bdd009cc64af0b2260fe7570a0510" target="_blank" rel="external">下载链接</a> </p>
<h1 id="作者">作者</h1><p>JustZht<br>Github : <a href="https://github.com/JustinFincher" target="_blank" rel="external">https://github.com/JustinFincher</a><br>Weibo : <a href="http://weibo.com/justzht" target="_blank" rel="external">http://weibo.com/justzht</a> </p>
]]></content>
<summary type="html">
<![CDATA[<p><img src="/img/2015/11/tvOS/tvOSButtonPic_thumb.jpg" alt=""></p>
<h1 id="介绍">介绍</h1><p>苹果在最新发布的Apple TV里引入了有趣的<a href="https://developer.]]>
</summary>
<category term="ObjC" scheme="http://anius.io/tags/ObjC/"/>
<category term="tvOS" scheme="http://anius.io/tags/tvOS/"/>
<category term="代码" scheme="http://anius.io/categories/%E4%BB%A3%E7%A0%81/"/>
</entry>
</feed>