讀書筆記
先看效果

2017-08-17 18_31_25.gif
源碼鏈接:https://github.com/LeeLom/AnimationSample
這節(jié)內(nèi)容主要是利用貝塞爾曲線來模擬一個球產(chǎn)生形變的過程。利用四條曲線來拼接出完整的球體。

圖1.png
所以,我們畫出這個球體的步驟,實際上也就是確定多邊形Ac1c2Bc3c4Cc5c6Dc7c8的過程,也即是確定各個點坐標的過程。我們得到這個多邊形后,利用貝塞爾曲線去逼近,既可以得到我們所需要的形變的圖像。
書中給出的確定多邊形的方法如下:
- 確定外接正方形的位置:得到(origin_x, origin_y)
- 確定多邊形各個坐標的:
- 確定多邊形的坐標的過程中,我們需要根據(jù)每次滑動的值,來確定
offset和movedDistance。這兩個的計算公式可以參考文中的表達式。
- 確定多邊形的坐標的過程中,我們需要根據(jù)每次滑動的值,來確定
CGFloat offset = _outsideRect.size.width / 3.6;
CGFloat movedDistance = (_outsideRect.size.width * 1 / 6) * fabs(_progress - 0.5) * 2;
- 確定各個點的坐標:
- A點:橫坐標永遠與外接多邊形相同,縱坐標為正方形的縱坐標加上移動的距離
- B點:縱坐標和外接多邊形相同,橫坐標通過判斷左右移動確定。像左邊移動,則為外接多邊形的橫坐標與正方形邊長一半,移動距離的兩倍求和的值;像右邊移動,則為外接多邊形的橫坐標與正方形邊長一半的和。
其余點類似,這里不做過多解釋。如果不太理解,其實可以講progress分別設置成為不同的值進行查看即可。
- 接著我們首先通過
(origin_x, orgin_y)畫出外接正方形。 邊長設置為定值 - 使用
Ac1c2Bc3c4Cc5c6Dc7c8畫出外接多邊形,同時利用貝塞爾曲線去逼近這個多邊形 - 書中為了方便我們便捷,還將這12個點分別也畫了出來。
以上,就是書中本節(jié)的內(nèi)容,最本質(zhì)的就是不斷的根據(jù)滑動條的值去更新正方形的位置,從而調(diào)整外接多邊形的形狀,進而得到模擬出的貝塞爾曲線。
補充知識點
關于這節(jié)內(nèi)容,在代碼實現(xiàn)的過程中,發(fā)現(xiàn)了不少知識盲區(qū),網(wǎng)上查找了不少資料進行了一個整理:
關于initWithFrame和initWithCoder的區(qū)別
- 在
CircleView.m中,初始化方法使用的是initWithFrame:,那么initWithCoder與它有什么區(qū)別?
initWithFrame適用于代碼創(chuàng)建頁面的時候使用;
initWithCoder適用于xib、storyboard創(chuàng)建的時候使用。與initWithCoder類似的還有一個是awakeFromNib, 該方法在initWithCoder:方法后調(diào)用,順序是initWithCoder:-->awakeFromNib
PS1. OC類的初始化方法- 普通類初始化:init,initialize
- 控制器類的初始化方法:init, initialize,initWithCoder:
- UI空間類的初始化方法:init,initialize, initWithFrame: ,initWithCoder:
PS2. Stack Overflow上看到一個比較有意思的問題,跟Runloop結合的。
在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
控制臺的輸出結果如下:
2013-10-17 12:33:46.209 test1[8422:60b] initWithFrame
2013-10-17 12:33:46.211 test1[8422:60b] init
分析一下控制臺出現(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")
關于UIView和CALayer的區(qū)別
-
UIView可以響應事件,CALayer不可以。UIKit使用UIResponder作為響應對象,來響應系統(tǒng)傳遞過來的事件進行處理,UIView作為UIResponder的響應對象,因此可以處理。
處理事件的函數(shù)。
– touchesBegan:withEvent:
– touchesMoved:withEvent:
– touchesEnded:withEvent:
– touchesCancelled:withEvent:
- 每個UIView內(nèi)部都有一個CALayer在背后提供內(nèi)容的繪制和顯示,并且UIView的尺寸樣式都是由內(nèi)部的Layer提供。兩者都有樹狀層級結構,layer內(nèi)部有subLayers, view內(nèi)部有subViews。
- View在顯示的時候, UIView作為layer的CALayerDelegate,View的顯示內(nèi)容由內(nèi)部的CALayer的display
- CALayer是默認修改屬性支持隱式動畫,在給UIView的Layer做動畫的時候,View作為Layer的代理,Layer通過actionForLayer:forKey:向View請求響應的action(動畫行為)
- layer 內(nèi)部維護著三分 layer tree,分別是 presentLayer Tree(動畫樹),modeLayer Tree(模型樹), Render Tree (渲染樹),在做 iOS動畫的時候,我們修改動畫的屬性,在動畫的其實是 Layer 的 presentLayer的屬性值,而最終展示在界面上的其實是提供 View的modelLayer
關于繪圖的一些函數(shù)
- 繪制路徑的相關函數(shù)
繪制路徑:制定起始點到另一個點,然后兩個點之間可以使用直線或者曲線來連接,就形成了一條路徑,例如使用下面的函來實現(xiàn)CGContextMoveToPoint()//指定起始點或者移動到新的點 CGContextAddLineToPoint()//從當前的點到制定的點之間畫一條線 CGContextAddArc()//圓弧 CGContextAddArcToPoint()//會在當前點和指定點與坐標系平行的直線做切線畫圓弧 CGContextAddCurveToPoint()//畫曲線 CGContextClosePath()//將起始點和結束點連接起來形成封閉的路徑 CGContextAddEllipseInRect()//畫橢圓 - 設置路徑的相關屬性
設置需要繪制的路徑的屬性。設置顏色、透明度、寬度、繪制模式...例如用下面的函數(shù)來實現(xiàn)CGContextSetLineWidth()//線寬度 CGContextSetLineJoin()//線連接點的樣式 CGContextSetLineCap()//端點的樣式 CGContextSetLineDash()//虛線 CGContextSetStrokeColorWithColor()//stroke模式時的顏色 CGContextSetFillColorWithColor()//fill模式時的顏色 - 繪制內(nèi)容的兩種方式
fill和Stroke
stroke: 描邊,只繪制路徑
fill:繪制路徑包括的所有區(qū)域(所有路徑都會被當做closePath處理)
fill的兩種模式:
(1)non zero widing number(默認):如果兩個路徑有重疊的時候,繪制方向相同的話,那么重疊部分的繪制可能不是我們希望的
(2)even-odd:不受繪制方向的影響
(3)CGContextEOFillPath
(4)CGContextFillPath
(5)CGContextFillRect
(6)CGContextFillRexts
(7)CGContextFillEllipseInRectCGContextStrokePath(context); CGContextFillPath(context); - 兩個小例子
/**
* 示例:stroke模式繪制線
*/
override func draw(rect: CGRext) {
//獲取上下文
let context = UIGraphicsGetCurrentContext();
//設置stroke模式的顏色使用CGColor(這里涉及顏色和顏色空間的概念)
let strokeColor = UIColor.blueColor();
CGContextSetStrokeColorWIthColor(context, strokeColor.CGColor);
//設置線的寬度
CGContextSetLineWidth(context, 5.0);
//設置連接處樣式
CGContextSetLineJoin(context, CGLineJoin.Round);
//起始點
CGContextMoveToPoint(context, 10.0, 10.0);
//在兩個點之間畫一條線,所以當前的結束點成為了下一個的起始點
CGContextAddLineToPoint(context, 10.0, 80.0);
//繼續(xù)在兩個點之間畫一條線
CGContextAddLineToPoint(context, 80.0, 80.0);
//設置為封閉路徑,將收尾連接起來
CGContextClosePath(context);
//繪制當前路徑
CGContextStrokePath(context);
}
/**
* 示例:fill模式繪制線
*/
override func draw(rect: CGRext) {
//獲取上下文
let context = UIGraphicsGetCurrentContext();
//設置stroke模式的顏色使用CGColor(這里涉及顏色和顏色空間的概念)
let fillColor = UIColor.blueColor();
CGContextSetFillColorWIthColor(context, fillColor.CGColor);
//設置線的寬度
CGContextSetLineWidth(context, 5.0);
//設置連接處樣式
CGContextSetLineJoin(context, CGLineJoin.Round);
//起始點
CGContextMoveToPoint(context, 10.0, 10.0);
//在兩個點之間畫一條線,所以當前的結束點成為了下一個的起始點
CGContextAddLineToPoint(context, 10.0, 80.0);
//繼續(xù)在兩個點之間畫一條線
CGContextAddLineToPoint(context, 80.0, 80.0);
// 設置為封閉路徑,將收尾連接起來
// CGContextClosePath(context);
//繪制當前路徑
CGContextFillPath(context);
}