iOS的UI基礎

Core Animation

iOS系統的核心地位:應用內和應用間都會應用(多個程序間的手勢切換)SpringBoardBackBoard。

應用內的工作:Core Animation Pipeline管線(layout,display,preload,commit)。

應用外的工作:提交到Render Server服務,反序列化構建渲染樹GPU的工作:提交到GPU進行合成和渲染,輸出到緩沖區。

注1:代碼只能控制CPU處理中的管線中的布局和顯示兩個階段(通過setNeedLayout和setNeedDisplay)(setNeedDisplayInRect還能設置臟矩形來減少不必要的繪制)。

注2:CPU偏計算;而圖像的處理和渲染,用硬件會更快,因為GPU對圖像計算進行了優化。


隱式動畫

概念:更改一個CALayer的可做動畫屬性,Core Animation會執行一個隱式動畫,平滑過渡到新值。動畫的執行時間取決于事務的設置,動畫的類型取決于圖層行為。

實現:layer屬性更改時會調用actionForKey方法,然后執行actionForLayer:forKey:代理方法(UIView禁用隱式動畫的原理),如果沒有實現代理,就查找屬性-名稱的actions映射字典和style字典,最后直接調defaultActionForKey。查找完actionForKey方法會返回一個CAAction做動畫或NULL不做動畫。

時機:由事務的管理機制控制。

注:UIView更改默認沒有隱式動畫,因為UIKit禁用了:UIView實現了CALayerDelegate的-actionForLayer:forKey方法,當屬性的更改不在一個動畫塊中時,所有圖層行為都會返回null,以此來禁用隱式動畫。


顯式動畫

CABasicAnimation和CAKeyframeAnimation

虛擬屬性:keyPath = "transform.scale .position .rotation",不用創建CATransform3D指定動畫變換。

[layer removeAnimationForKey];? // 通過key在動畫過程中取消動畫。

動畫的暫停

CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];

layer.speed = 0.0;

layer.timeOffset = pausedTime;

動畫的繼續

CFTimeInterval pausedTime = [layer timeOffset];

layer.speed = 1.0;

layer.timeOffset = 0.0;

layer.beginTime = 0.0;

CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;

layer.beginTime = timeSincePause;


事務

事務是一系列屬性動畫集合,通過CATransaction管理事務組。CATransaction沒有屬性和實例方法,只能通過+begin和+commit管理出入棧,+setAnimationDuration管理事務的動畫時長和+animationDuration獲取值(默認0.25),setCompletionBlock:可以設置事務完成后的回調。

事務的管理機制

每個RunLoop周期都會自動開始一個新的事務,把更改的屬性收集起來做一次默認0.25s的隱式動畫。

注:一般手動更改隱式動畫的時間,需要新開事務避免影響別的動畫(如屏幕旋轉)。

UIView的動畫

UIView的begin/commitAnimation底層原理也是設置了CATransaction。

UIView的animateWithDuration:animations:的Block動畫方法其實也是內部自動調用了CATransaction的begin和commit,這樣Block中的屬性改變都會被事務所包含。


呈現樹

presentationLayer其實更改圖層屬性后,屬性值立刻更新,但屏幕沒有馬上發生改變,因為它只是定義了圖層動畫結束后將要變化的外觀,即圖層樹模型。但在設置新值后和新值生效前,屏幕渲染的過程中,當前顯示的屬性值,在某些場景下更有意義:1、在基于定時器的動畫過程中獲取當前圖層的準確位置;2、在做動畫的圖層中,通過hitTest響應用戶交互。(touchesBegan/Target-Action中,判斷觸摸點是否在presentationLayer內)

注:關鍵方法:locationInView / convertPoint->Point轉化觸摸點坐標系,presentationLayer -> CALayer獲取呈現樹中的視圖位置,hitTest -> CALayer/ Contains-> Bool返回觸摸的Layer / 判斷觸摸點是否在Layer上。


圖片IO

imageWithContentsOfFile方法加載本地目錄的圖片文件,會在加載到內存之后才進行解碼解壓,所以會在繪制的時候影響性能,且不會進行內存緩存,適用于讀取一次性大圖的場景imageNamed可以避免延遲加載,會馬上解壓并加載到內存中緩存起來,但是只對assets中的資源有效。

強制解壓和提前渲染

使用CGContext從上下文中繪制一個新的圖片(直接繪制成CGImageRef位圖)來代替(或繪制成一個點也會解壓但不會節省繪制時間),優化了因解壓增加的繪制時間,還可以丟棄解壓后的圖片節省內存,但需要更復雜的計算,CPU消耗總量增加。

總結:使用CGContext異步重繪圖片,得到解壓縮后的位圖。


UIView和CALayer

View持有Layer用于顯示,View可以響應交互負責管理,View的屬性大部分從Layer映射而來,UIView支持自動布局;Layer的delegate是View,動畫會通知View,Layer更輕量級。

提供View和layer兩個平行的層級關系是為了職責分離,避免重復代碼。


frame和bounds

frame:外部坐標,在父圖層占據的位置bounds:內部坐標本地坐標系,作用于子視圖,左上角通常是{0, 0}(通過更改bounds實現簡單的scrollView)

注:frame是一個虛擬屬性,是根據bounds+position+transform計算而來;當對圖層進行旋轉或縮放時,frame實際是變換后,補齊的正矩形區域,即寬高可能和bounds不一致。


anchorPoint

錨點,UIView未暴露的屬性,默認中心{0.5, 0.5},左上角{0, 0} 右下角{1, 1},小于0大于1將放置在圖層外。UIView的Center和CALayer的Position屬性加上錨點,才是圖層的實際位置,所以圖層錨點更改,frame會發生變化。


masksToBounds

Layer屬性,設置為YES截取子圖層實現圓角(cornerRadius);設為NO避免截取陰影(shadowOpacity,shadowColor,shadowOffset,shadowRadius,shadowPath-CGPath)。


mask蒙版

Layer屬性,定義了父視圖的可見區域,mask圖層的顏色不重要,實心部分將會保留,其他隱藏


CGAffineTransform

UIView 仿射變換,僅支持2D變換:MakeRotation,Scale,Translation等。

CATransform3D

CALayer 3D變換,方法和仿射變換類似,加入zPosition形成4*4矩陣。

CAShapeLayer

基于CGPath路徑創建CAShapeLayer矢量圖形(非CG的Bitmap),bezierPathWithRoundedRect: byRoundingCorners: cornerRadii:可以單獨指定每個角的圓角,作為蒙版為宿主層加圓角(mask)。

AVPlayerLayer

AVFoundation提供,和CA緊密結合,是CALayer的子類。AVPlayer + AVPlayerLayer簡單的播放視頻。


UIView的布局更新和重繪機制

-loadViewIfNeeded

VC初始化后,強制提前執行viewdidload加載控件和數據裝載。

-layoutSubviews

此函數用于,父視圖的布局、大小等發生了改變,你想要同時更新其子視圖的布局時,更新子視圖布局的代碼應寫在父類重寫后的layoutSubviews里(它默認不做任何事),但這時就不能在外部設置子視圖的布局了。

注:初始化含frame(且顯示出來)或改變frame,addSubview,滾動UIScrollView,旋轉屏幕,改變子view的大小會觸發layoutSubviews。

-setNeedsLayout

設置布局刷新標記——標記為需要重新布局。此方法會異步觸發layoutSubviews(執行完成后刷新標記又置為NO),如果想立刻觸發,應立刻調用layoutIfNeeded。

-layoutIfNeeded(寫在animation內,用于實現更新約束時的動畫效果)

view.layoutIfNeeded()后frame及坐標為約束完成后的值,可用于viewdidload中獲取準確的frame。

配合setNeedsLayout,在有刷新標記的情況下立刻觸發layoutSubviews。但在view的init方法后、第一次顯示前(addSubview前),標記是“需要刷新”的。

self.testView= [[LayoutTestViewalloc]initWithFrame:CGRectMake(0,0,300,300)];? //初始化會調用兩次setNeedsDisplay

//此時視圖標記是“需要刷新”的,直接調用[self.testView layoutIfNeeded];會觸發layoutSubviews,但之后的addSubview就不觸發了

[self.view addSubview:self.testView]; //默認觸發layoutSubviews

self.testView.frame = CGRectMake(0,0,500,500); //改變frame會再次觸發layoutSubviews()

- (void)click {

? ? ? [self.testView setNeedsLayout]; //標記為可刷新且異步觸發layoutSubviews

? ? ? [self.testView layoutIfNeeded]; //立刻觸發layoutSubviews

? ? ? //只觸發一次

}

Drawing and Updating the View

-drawRect:(CGRect)rect(內存殺手)

view首次顯示或調用setNeedsDisplay時執行。

重寫此方法,執行自定義繪制內容任務。默認不起作用。永遠不要直接調用drawRect。重寫此方法多用于UIView結合CGContextRef的手動繪圖(與CALayer對應的是drawInContext)

CGContextRef ctx =UIGraphicsGetCurrentContext(); //獲取當前畫板(圖形上下文,只在UIView的drawRect中有效)(還可以使用)

注:view重寫drawRect后如果不指定背景色或把opaque設成NO背景將變黑。

實際需求:1、在當前畫板上畫圖像,曲線、虛線、幾何圖形、寫字等等。

2、UITextView添加自定義placeholder的需求中,將holder的label添加在寫drawRect內,設置holder字符串時調用setNeedsDisplay重繪UITextView。

-setNeedDisplay

在下一個繪畫周期(1/60秒后)主動觸發drawRect執行重繪。setNeedDisplayInRect方法可限定在rect范圍內重繪。一般結合重寫drawRect來使用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容