讀書(shū)筆記
先看效果
源碼鏈接:https://github.com/LeeLom/AnimationSample
這節(jié)內(nèi)容主要是利用貝塞爾曲線(xiàn)來(lái)模擬一個(gè)球產(chǎn)生形變的過(guò)程掸鹅。利用四條曲線(xiàn)來(lái)拼接出完整的球體古掏。
所以秸应,我們畫(huà)出這個(gè)球體的步驟瀑焦,實(shí)際上也就是確定多邊形Ac1c2Bc3c4Cc5c6Dc7c8
的過(guò)程荆针,也即是確定各個(gè)點(diǎn)坐標(biāo)的過(guò)程敞嗡。我們得到這個(gè)多邊形后,利用貝塞爾曲線(xiàn)去逼近航背,既可以得到我們所需要的形變的圖像喉悴。
書(shū)中給出的確定多邊形的方法如下:
- 確定外接正方形的位置:得到(origin_x, origin_y)
- 確定多邊形各個(gè)坐標(biāo)的:
- 確定多邊形的坐標(biāo)的過(guò)程中,我們需要根據(jù)每次滑動(dòng)的值玖媚,來(lái)確定
offset
和movedDistance
箕肃。這兩個(gè)的計(jì)算公式可以參考文中的表達(dá)式。
- 確定多邊形的坐標(biāo)的過(guò)程中,我們需要根據(jù)每次滑動(dòng)的值玖媚,來(lái)確定
CGFloat offset = _outsideRect.size.width / 3.6;
CGFloat movedDistance = (_outsideRect.size.width * 1 / 6) * fabs(_progress - 0.5) * 2;
- 確定各個(gè)點(diǎn)的坐標(biāo):
- A點(diǎn):橫坐標(biāo)永遠(yuǎn)與外接多邊形相同今魔,縱坐標(biāo)為正方形的縱坐標(biāo)加上移動(dòng)的距離
- B點(diǎn):縱坐標(biāo)和外接多邊形相同勺像,橫坐標(biāo)通過(guò)判斷左右移動(dòng)確定。像左邊移動(dòng)涡贱,則為外接多邊形的橫坐標(biāo)與正方形邊長(zhǎng)一半咏删,移動(dòng)距離的兩倍求和的值;像右邊移動(dòng)问词,則為外接多邊形的橫坐標(biāo)與正方形邊長(zhǎng)一半的和督函。
其余點(diǎn)類(lèi)似,這里不做過(guò)多解釋激挪。如果不太理解辰狡,其實(shí)可以講progress
分別設(shè)置成為不同的值進(jìn)行查看即可。
- 接著我們首先通過(guò)
(origin_x, orgin_y)
畫(huà)出外接正方形垄分。 邊長(zhǎng)設(shè)置為定值 - 使用
Ac1c2Bc3c4Cc5c6Dc7c8
畫(huà)出外接多邊形宛篇,同時(shí)利用貝塞爾曲線(xiàn)去逼近這個(gè)多邊形 - 書(shū)中為了方便我們便捷,還將這12個(gè)點(diǎn)分別也畫(huà)了出來(lái)薄湿。
以上叫倍,就是書(shū)中本節(jié)的內(nèi)容,最本質(zhì)的就是不斷的根據(jù)滑動(dòng)條的值去更新正方形的位置豺瘤,從而調(diào)整外接多邊形的形狀吆倦,進(jìn)而得到模擬出的貝塞爾曲線(xiàn)。
補(bǔ)充知識(shí)點(diǎn)
關(guān)于這節(jié)內(nèi)容坐求,在代碼實(shí)現(xiàn)的過(guò)程中蚕泽,發(fā)現(xiàn)了不少知識(shí)盲區(qū),網(wǎng)上查找了不少資料進(jìn)行了一個(gè)整理:
關(guān)于initWithFrame
和initWithCoder
的區(qū)別
- 在
CircleView.m
中桥嗤,初始化方法使用的是initWithFrame:
须妻,那么initWithCoder
與它有什么區(qū)別仔蝌?
initWithFrame
適用于代碼創(chuàng)建頁(yè)面的時(shí)候使用;
initWithCoder
適用于xib、storyboard創(chuàng)建的時(shí)候使用荒吏。與initWithCoder
類(lèi)似的還有一個(gè)是awakeFromNib
, 該方法在initWithCoder:
方法后調(diào)用敛惊,順序是initWithCoder:
-->awakeFromNib
PS1. OC類(lèi)的初始化方法- 普通類(lèi)初始化:init,initialize
- 控制器類(lèi)的初始化方法:init司倚, initialize豆混,initWithCoder:
- UI空間類(lèi)的初始化方法:init,initialize动知, initWithFrame: 皿伺,initWithCoder:
PS2. Stack Overflow上看到一個(gè)比較有意思的問(wèn)題,跟Runloop結(jié)合的盒粮。
在ViewController.m
中
@implementation TestViewController
- (void)viewDidLoad {
[super viewDidLoad];
View1 *view1 = [[View1 alloc] init];
[self.view addSubview:view1];
}
@end
在View1.m
中
@implementation View1
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
NSLog(@"initWithFrame");
}
return self;
}
- (id)init {
self = [super init];
if (self) {
NSLog(@"init");
}
return self;
}
@end
控制臺(tái)的輸出結(jié)果如下:
2013-10-17 12:33:46.209 test1[8422:60b] initWithFrame
2013-10-17 12:33:46.211 test1[8422:60b] init
分析一下控制臺(tái)出現(xiàn)的原因:
- [view1(View1) init]
- [view1(UIView) init] (called by [super init] inside View1)
- [view1(View1) initWithFrame:CGRectZero] (called inside [view(UIView) init] )
- [view1(UIView) initWithFrame:CGRectZero] (called by [super initWithFrame] inside View1)
- ...
- NSLog(@"initWithFrame"); (prints "test1[8422:60b] initWithFrame")
- NSLog(@"init"); (called inside [view1(View1) init] ; prints "test1[8422:60b] init")
關(guān)于UIView
和CALayer
的區(qū)別
-
UIView
可以響應(yīng)事件鸵鸥,CALayer不可以。UIKit使用UIResponder作為響應(yīng)對(duì)象丹皱,來(lái)響應(yīng)系統(tǒng)傳遞過(guò)來(lái)的事件進(jìn)行處理妒穴,UIView作為UIResponder的響應(yīng)對(duì)象,因此可以處理摊崭。
處理事件的函數(shù)讼油。
– touchesBegan:withEvent:
– touchesMoved:withEvent:
– touchesEnded:withEvent:
– touchesCancelled:withEvent:
- 每個(gè)UIView內(nèi)部都有一個(gè)CALayer在背后提供內(nèi)容的繪制和顯示,并且UIView的尺寸樣式都是由內(nèi)部的Layer提供呢簸。兩者都有樹(shù)狀層級(jí)結(jié)構(gòu)矮台,layer內(nèi)部有subLayers, view內(nèi)部有subViews。
- View在顯示的時(shí)候根时, UIView作為layer的CALayerDelegate瘦赫,View的顯示內(nèi)容由內(nèi)部的CALayer的display
- CALayer是默認(rèn)修改屬性支持隱式動(dòng)畫(huà),在給UIView的Layer做動(dòng)畫(huà)的時(shí)候蛤迎,View作為L(zhǎng)ayer的代理确虱,Layer通過(guò)actionForLayer:forKey:向View請(qǐng)求響應(yīng)的action(動(dòng)畫(huà)行為)
- layer 內(nèi)部維護(hù)著三分 layer tree,分別是 presentLayer Tree(動(dòng)畫(huà)樹(shù)),modeLayer Tree(模型樹(shù)), Render Tree (渲染樹(shù)),在做 iOS動(dòng)畫(huà)的時(shí)候,我們修改動(dòng)畫(huà)的屬性替裆,在動(dòng)畫(huà)的其實(shí)是 Layer 的 presentLayer的屬性值,而最終展示在界面上的其實(shí)是提供 View的modelLayer
關(guān)于繪圖的一些函數(shù)
- 繪制路徑的相關(guān)函數(shù)
繪制路徑:制定起始點(diǎn)到另一個(gè)點(diǎn)校辩,然后兩個(gè)點(diǎn)之間可以使用直線(xiàn)或者曲線(xiàn)來(lái)連接,就形成了一條路徑辆童,例如使用下面的函來(lái)實(shí)現(xiàn)CGContextMoveToPoint()//指定起始點(diǎn)或者移動(dòng)到新的點(diǎn) CGContextAddLineToPoint()//從當(dāng)前的點(diǎn)到制定的點(diǎn)之間畫(huà)一條線(xiàn) CGContextAddArc()//圓弧 CGContextAddArcToPoint()//會(huì)在當(dāng)前點(diǎn)和指定點(diǎn)與坐標(biāo)系平行的直線(xiàn)做切線(xiàn)畫(huà)圓弧 CGContextAddCurveToPoint()//畫(huà)曲線(xiàn) CGContextClosePath()//將起始點(diǎn)和結(jié)束點(diǎn)連接起來(lái)形成封閉的路徑 CGContextAddEllipseInRect()//畫(huà)橢圓
- 設(shè)置路徑的相關(guān)屬性
設(shè)置需要繪制的路徑的屬性召川。設(shè)置顏色、透明度胸遇、寬度、繪制模式...例如用下面的函數(shù)來(lái)實(shí)現(xiàn)CGContextSetLineWidth()//線(xiàn)寬度 CGContextSetLineJoin()//線(xiàn)連接點(diǎn)的樣式 CGContextSetLineCap()//端點(diǎn)的樣式 CGContextSetLineDash()//虛線(xiàn) CGContextSetStrokeColorWithColor()//stroke模式時(shí)的顏色 CGContextSetFillColorWithColor()//fill模式時(shí)的顏色
- 繪制內(nèi)容的兩種方式
fill
和Stroke
stroke: 描邊汉形,只繪制路徑
fill:繪制路徑包括的所有區(qū)域(所有路徑都會(huì)被當(dāng)做closePath處理)
fill的兩種模式:
(1)non zero widing number(默認(rèn)):如果兩個(gè)路徑有重疊的時(shí)候纸镊,繪制方向相同的話(huà)倍阐,那么重疊部分的繪制可能不是我們希望的
(2)even-odd:不受繪制方向的影響
(3)CGContextEOFillPath
(4)CGContextFillPath
(5)CGContextFillRect
(6)CGContextFillRexts
(7)CGContextFillEllipseInRectCGContextStrokePath(context); CGContextFillPath(context);
- 兩個(gè)小例子
/**
* 示例:stroke模式繪制線(xiàn)
*/
override func draw(rect: CGRext) {
//獲取上下文
let context = UIGraphicsGetCurrentContext();
//設(shè)置stroke模式的顏色使用CGColor(這里涉及顏色和顏色空間的概念)
let strokeColor = UIColor.blueColor();
CGContextSetStrokeColorWIthColor(context, strokeColor.CGColor);
//設(shè)置線(xiàn)的寬度
CGContextSetLineWidth(context, 5.0);
//設(shè)置連接處樣式
CGContextSetLineJoin(context, CGLineJoin.Round);
//起始點(diǎn)
CGContextMoveToPoint(context, 10.0, 10.0);
//在兩個(gè)點(diǎn)之間畫(huà)一條線(xiàn),所以當(dāng)前的結(jié)束點(diǎn)成為了下一個(gè)的起始點(diǎn)
CGContextAddLineToPoint(context, 10.0, 80.0);
//繼續(xù)在兩個(gè)點(diǎn)之間畫(huà)一條線(xiàn)
CGContextAddLineToPoint(context, 80.0, 80.0);
//設(shè)置為封閉路徑逗威,將收尾連接起來(lái)
CGContextClosePath(context);
//繪制當(dāng)前路徑
CGContextStrokePath(context);
}
/**
* 示例:fill模式繪制線(xiàn)
*/
override func draw(rect: CGRext) {
//獲取上下文
let context = UIGraphicsGetCurrentContext();
//設(shè)置stroke模式的顏色使用CGColor(這里涉及顏色和顏色空間的概念)
let fillColor = UIColor.blueColor();
CGContextSetFillColorWIthColor(context, fillColor.CGColor);
//設(shè)置線(xiàn)的寬度
CGContextSetLineWidth(context, 5.0);
//設(shè)置連接處樣式
CGContextSetLineJoin(context, CGLineJoin.Round);
//起始點(diǎn)
CGContextMoveToPoint(context, 10.0, 10.0);
//在兩個(gè)點(diǎn)之間畫(huà)一條線(xiàn)峰搪,所以當(dāng)前的結(jié)束點(diǎn)成為了下一個(gè)的起始點(diǎn)
CGContextAddLineToPoint(context, 10.0, 80.0);
//繼續(xù)在兩個(gè)點(diǎn)之間畫(huà)一條線(xiàn)
CGContextAddLineToPoint(context, 80.0, 80.0);
// 設(shè)置為封閉路徑,將收尾連接起來(lái)
// CGContextClosePath(context);
//繪制當(dāng)前路徑
CGContextFillPath(context);
}