core graphics之CALayer &UIView

引文

有時候為了實現某些效果,比如:

  • 繪制某些簡易圖案,比如對勾,字形
  • 手勢相關的畫線
  • 制作一些復雜動效

需要對簡單的UIView進行操作,這時需要我們繼承CALayer &UIView,重寫里面的繪制方法。兩者的區別可以參見這里,二者的主要區別是:

  • UIView繼承于UIResponder,在 UIResponder中定義了處理各種事件和事件傳遞的接口, 而 CALayer直接繼承 NSObject,并沒有相應的處理事件的接口。UIView可以響應事件,Layer不可以.
  • UIView主要是對顯示內容的管理而 CALayer 主要側重顯示內容的繪制。可以理解成控制器和內部模型的關系

如何重寫來控制顯示

  • 對UIView
    繪制:- (void)drawRect:(CGRect)rect;
    修改了元素,需要重繪:-(void)setNeedsDisplay;
  • 對CALayer
    繪制:- (void)drawInContext:(CGContextRef)ctx;
    修改了元素,需要重繪:-(void)setNeedsDisplay;
    修改了元素,需要自動重繪:
  +  (BOOL)needsDisplayForKey:(NSString *)key {
  if ([key isEqualToString:@"progress"]) {
        return YES;
    }
    return [super needsDisplayForKey:key];
}

如何在繪制方法里繪圖呢,下面是一些示例代碼:

  • 對CALayer
    // 更新界面
  //構劃路徑
    UIBezierPath* ovalPath = [UIBezierPath bezierPath];
    [ovalPath moveToPoint: pointA];
    [ovalPath addCurveToPoint:pointB controlPoint1:c1 controlPoint2:c2];
    [ovalPath addCurveToPoint:pointC controlPoint1:c3 controlPoint2:c4];
    [ovalPath addCurveToPoint:pointD controlPoint1:c5 controlPoint2:c6];
    [ovalPath addCurveToPoint:pointA controlPoint1:c7 controlPoint2:c8];
    [ovalPath closePath];
//AddPath
 CGContextAddPath(ctx, ovalPath.CGPath);
//填充
    CGContextSetFillColorWithColor(ctx, [self.indicatorColor colorWithAlphaComponent:0.3].CGColor);
    CGContextFillPath(ctx);
  • UIView 由于在- (void)drawRect:(CGRect)rect; 中沒有類似的context,所以需要先調用CGContextRef context = UIGraphicsGetCurrentContext();其余相同

項目中碰到的相關使用案例

1. loading轉圈動效,完成后打勾:

#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>

@interface ArcToCircleLayer : CALayer
@property (nonatomic) CGFloat progress;
@property (nonatomic,strong) UIColor* fillColor;
@property (nonatomic,strong) UIColor* finishedStrokeColor;

@property (nonatomic,assign) BOOL isFinished;

@end
#import "ArcToCircleLayer.h"

static CGFloat const kLineWidth = 6;

@implementation ArcToCircleLayer

@dynamic progress;

+ (BOOL)needsDisplayForKey:(NSString *)key {
    if ([key isEqualToString:@"progress"]) {
        return YES;
    }

    return [super needsDisplayForKey:key];
}

- (void)drawInContext:(CGContextRef)ctx {
    UIBezierPath *path = [UIBezierPath bezierPath];

    CGFloat radius = MIN(CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)) / 2 - kLineWidth / 2;
    CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));

    // O
    CGFloat originStart = M_PI * 7 / 2;
    CGFloat originEnd = M_PI * 3/2;
    CGFloat currentOrigin = originStart - (originStart - originEnd) * self.progress;

    // D
    CGFloat destStart = M_PI * 3;
    CGFloat destEnd = -1*M_PI/2;
    CGFloat currentDest = destStart - (destStart - destEnd) * self.progress;

    [path addArcWithCenter:center radius:radius startAngle: currentOrigin endAngle:currentDest clockwise:NO];
    CGContextAddPath(ctx, path.CGPath);
    CGContextSetLineWidth(ctx, kLineWidth);
    if (_isFinished) {
        if (self.finishedStrokeColor) {
            CGContextSetStrokeColorWithColor(ctx, self.finishedStrokeColor.CGColor);

        }else{
            CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);

        }

    }else{
        CGContextSetStrokeColorWithColor(ctx, [UIColor colorWithHexString:@"13eaa4"].CGColor);

    }

    CGContextStrokePath(ctx);
    if (_isFinished) {
        [path addArcWithCenter:center radius:radius startAngle: currentOrigin endAngle:currentDest clockwise:NO];
        CGContextAddPath(ctx, path.CGPath);
        CGContextSetFillColorWithColor(ctx, self.fillColor.CGColor);
        
        CGContextFillPath(ctx);
    }

}

2. 仿百度貼吧下拉刷新的效果

隨著下拉的進行:

  • 小水滴由橢圓拉伸至圓形
  • 圓形變大
  • 圓形有個彈性跳躍的動畫,同時圓形內部有附帶動畫

這里的難點是如何實現彈性跳躍的動畫,這里特別感謝大神[KittenYang]的這篇文章:談談iOS中粘性動畫以及果凍效果的實現

"小球是由弧AB、弧BC、弧CD、弧DA 四段組成,其中每段弧都綁定兩個控制點:弧AB 綁定的是 C1 、 C2;弧BC 綁定的是 C3 、 C4 ....."

這里解釋下控制點的計算:
一段Bezier曲線由起點、終點、控制點構成,關于Bezier曲線的背景、曲線計算可以參考wiki,其中說到:

The start and end of the curve is tangent to the first and last section of the Bézier polygon, respectively.
A curve can be split at any point into two subcurves, or into arbitrarily many subcurves, each of which is also a Bézier curve.

控制點引起曲線的變化如圖:

引文中利用控制點與起點和終點的相切關系,(由于預期得到的曲線都是橢圓,在起點和終點都是相切的,所以兩控制點必在垂直切線上)得到控制點的算法:

CGPoint c1 = CGPointMake(pointA.x + offset, pointA.y);  
CGPoint c2 = CGPointMake(pointB.x, pointB.y - offset);

CGPoint c3 = CGPointMake(pointB.x, pointB.y + offset);  
CGPoint c4 = CGPointMake(pointC.x + offset, pointC.y);

CGPoint c5 = CGPointMake(pointC.x - offset, pointC.y);  
CGPoint c6 = CGPointMake(pointD.x, pointD.y + offset);

CGPoint c7 = CGPointMake(pointD.x, pointD.y - offset);  
CGPoint c8 = CGPointMake(pointA.x - offset, pointA.y);

關于偏移量的計算:

當offSet設置為 直徑除以3.6 的時候,弧線能完美地貼合成圓弧。我隱約感覺這個 3.6 是必然,貌似和360度有某種關系,或許通過演算能得出 3.6 這個值的必然性,但我沒有嘗試。

其實這個值可以這樣計算:這類似于割圓術,算的最佳的可能更接近3.5。
這樣繪圖的工作就結束了,通過動態調整ABCD點就能實現圖形的變化了

3. 手勢密碼

手勢密碼是項目中挺重要的一環,其中有幾個要點

  • 進入相關頁面(首次進入|從后臺進入)都會進行認證
  • 手勢密碼頁面的兩種模式:設置,驗證
  • 設置需要在上方額外顯示小九宮格來顯示設置的圖形
  • 劃線時途徑中的圓形要有相關變色反饋、成功失敗也要有

設計時相關的模型為:
PasswordInputWindow:提供給外部的窗口類,用來調起手勢窗口
GesturePasswordController:窗口類的根控制器
GesturePasswordView:搭載TentacleView、NineCircleView、提示視圖的父視圖
TentacleView:搭載九宮格GesturePasswordButton的父視圖,主要處理滑動手勢來調整按鈕高亮顯示
NineCircleView:微縮版的TentacleView
GesturePasswordButton:繪制多種狀態下的數字按鍵。
下面代碼給出TentacleView怎么畫線的

- (void)drawRect:(CGRect)rect
{
    // Drawing code
//    if (touchesArray.count<2)return;
    for (int i=0; i<touchesArray.count; i++) {
        CGContextRef context = UIGraphicsGetCurrentContext();
        if (![[touchesArray objectAtIndex:i] objectForKey:@"num"]) { //防止過快滑動產生垃圾數據
            [touchesArray removeObjectAtIndex:i];
            continue;
        }
        if (success) {
           // CGContextSetRGBStrokeColor(context, 2/255.f, 174/255.f, 240/255.f, 0.7);//線條顏色
            CGContextSetRGBStrokeColor(context, 19/255.f, 234/255.f, 164/255.f, 0.2);//線條顏色
        }
        else {
            CGContextSetRGBStrokeColor(context, 255/255.f, 93/255.f, 93/255.f, 0.2);//紅色
        }
        CGContextSetLineCap(context,kCGLineCapRound);
        CGContextSetLineWidth(context,24/[UIScreen mainScreen].scale);
        CGContextMoveToPoint(context, [[[touchesArray objectAtIndex:i] objectForKey:@"x"] floatValue], [[[touchesArray objectAtIndex:i] objectForKey:@"y"] floatValue]);
        if (i<touchesArray.count-1) {
            CGContextAddLineToPoint(context, [[[touchesArray objectAtIndex:i+1] objectForKey:@"x"] floatValue],[[[touchesArray objectAtIndex:i+1] objectForKey:@"y"] floatValue]);
        }
        else{
            if (success) {
                CGContextAddLineToPoint(context, lineEndPoint.x,lineEndPoint.y);
            }
        }
        CGContextStrokePath(context);
    }
}

end

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容

  • 在iOS中隨處都可以看到絢麗的動畫效果,實現這些動畫的過程并不復雜,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,551評論 6 30
  • 前言 本文只要描述了iOS中的Core Animation(核心動畫:隱式動畫、顯示動畫)、貝塞爾曲線、UIVie...
    GitHubPorter閱讀 3,649評論 7 11
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現這些動畫的過程并不復雜,今天將帶大家一窺iOS動畫全貌。在這里你可以看...
    F麥子閱讀 5,140評論 5 13
  • AnimationCircle 用四條曲線拼接出小球的形狀,逆時針開始,分別是弧AB,弧BC,弧CD,弧DA,每條...
    JoyceZhao閱讀 714評論 0 6
  • 1.自定義控件 a.繼承某個控件 b.重寫initWithFrame方法可以設置一些它的屬性 c.在layouts...
    圍繞的城閱讀 3,444評論 2 4