• 主页
  • 系列总集
  • OpenCV
  • CMake
  • iOS
  • Java
  • 前端
所有文章 关于我

  • 主页
  • 系列总集
  • OpenCV
  • CMake
  • iOS
  • Java
  • 前端

在自定义UIView上画线与根据动画机制的研究

2016-01-26

需求是什么

我们公司用了一个坑坑的密码控件TextFiled,此时需要一个6位的支付密码框,由于密码控件是封装起来的,我们改变不了,于是我们决定使用折中方案

  • 自定义一个UIView,SubView包括一个TextFiled和一个UIView,把UIView盖在TextField上,并且自己在UIView上画出来密码控件

多么机智的刘岩姐

提出这个方案的是刘岩姐,于是便有了利用Core Graghics绘图的过程,绘图是通过重写自定义View的**drawRect:**方法

static const CGFloat cornerRadius = 8.0;

@interface PayPGTextFieldView () <OnCharDelegate, PGTextFieldDelegate>
@property (nonatomic, weak) UIView *contentView;
@property (nonatomic, strong) NSMutableArray *pointArray;
@end


#pragma mark - Draw
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    // Drawing code
    CGFloat width = rect.size.width;
    CGFloat height = rect.size.height;
    //计算图标
    CGFloat singleWidth = width/_textField.m_iMaxLen;
    CGFloat everyWidth = singleWidth / 3;
    CGFloat everyHeight = everyWidth;
    //通过画布绘图
    CGContextRef context = UIGraphicsGetCurrentContext();//获取当前画布
    CGContextSetStrokeColorWithColor(context, LINE_COLOR.CGColor);//设定Stroke的颜色
    CGContextSetLineWidth(context, 0.5);//设置划线的宽度
    for(int idx = 1; idx < _textField.m_iMaxLen; idx++)
    {
        CGPoint sourcePoint = CGPointMake(singleWidth * idx, 0);
        CGPoint targetPoint = CGPointMake(singleWidth * idx, height);
        CGContextMoveToPoint(context, sourcePoint.x, sourcePoint.y);//画笔移动到起始点
        CGContextAddLineToPoint(context, targetPoint.x, targetPoint.y);//画笔画到结束点
    }
    CGContextStrokePath(context);//把画笔画过的地方着色(Stroke)
    //根据重绘的画布设置小圆点的Frame
    for (int i = 0; i < 6; ++i) {
        UIView *view = _pointArray[i];
        [view setFrame:CGRectMake(i *singleWidth  + everyWidth, (height - everyHeight) / 2, everyWidth,  everyHeight)];
        view.layer.cornerRadius = everyWidth / 2;
        view.hidden = YES;
    }
}

画方形等应该怎么画

//Get the CGContext from this view
CGContextRef context = UIGraphicsGetCurrentContext();
//Draw a rectangle
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
//Define a rectangle
CGContextAddRect(context, CGRectMake(10.0, 150.0, 60.0, 120.0));     //X, Y, Width, Height
//Draw it
CGContextFillPath(context);

Core Graphics和动画的层次关系

我们先来盗一张图

Figure1

这张图是苹果官方文档给出的动画机制的层次结构,可见Core Graphics是在动画层之下,和OpenGL同层,听上去吊吊哒.

所以说Core Graphics里的绘图是是画布层面上,直接操作的像素点.会比动画更早响应,这是是为什么如果重写drawRect:之后不设置背景色,除了被Stroke的部分都是黑色,因为没有像素(0,0,0)嘛.

动画机制原理

根据参考文档描述,动画和视图的渲染有一个单独的进程,在iOS5以前这个进程叫SpringBoard,在iOS6之后叫BackBoard。我们以下统一称为render server.

在App内部的阶段

  1. 布局:在这个阶段,程序设置 View / Layer 的层级信息,设置 layer 的属性,如 frame,background color 等等。
  2. 创建 backing image:在这个阶段程序会创建 layer 的 backing image,无论是通过 setContents 将一个 image 传給 layer,还是通过 [drawRect:] 或 [drawLayer: inContext:] 来画出来的。所以 [drawRect:] 等函数是在这个阶段被调用的。
  3. 准备:在这个阶段,Core Animation 框架准备要渲染的 layer 的各种属性数据,以及要做的动画的参数,准备传递給 render server。同时在这个阶段也会解压要渲染的 image。(除了用 imageNamed:方法从 bundle 加载的 image 会立刻解压之外,其他的比如直接从硬盘读入,或者从网络上下载的 image 不会立刻解压,只有在真正要渲染的时候才会解压)。
  4. 提交:在这个阶段,Core Animation 打包 layer 的信息以及需要做的动画的参数,通过 IPC(inter-Process Communication)传递給 render server。

在App外部iOS系统层阶段

  1. 根据 layer 的各种属性(如果是动画的,会计算动画 layer 的属性的中间值
  2. 用 OpenGL 准备渲染。渲染这些可视的 layer 到屏幕。

iOS设备的屏幕刷新频率是是60HZ。如果上面的这些步骤在一个刷新周期之内无法做完(1/60s),就会造成掉帧。

参考文档优化动画效果的结论

  1. 减少隐藏的绘制:CATextLayer和UILabel都是将text画入backing image的。如果改了一个包含 text的view的frame的话,text会被重新绘制。
  2. 使用Rasterize:当有比较复杂的Layer层(Table中Cell的圆角)效果,可以使用Layer的shouldRasterize=YES(记得设置适当的rasterizationScale),Layer会被先绘制成bitmap放进内存缓存起来,读取加载会比较顺滑。这种方法适用于不经常改的Layer,如果Layer经常变,就不适合用。
  3. 避免离屏绘制:实现Rounded Corner/Layer Masks/Drop Shadows的效果可以使用 Stretchable Images。比如实现圆角可以让设计给切较小的圆形的图片,Strectch后用于BackgroundImage或者Layer的Content的属性,记得设置好ContentsCenter和ContentScale。
  4. 减少Blending&Overdraw:如果一个Layer被另一个Layer完全遮盖,GPU会不渲染被遮盖的Layer,但是计算是否遮盖很消耗性能,可以通过setBackgroundColor:不透明的颜色或setOpaque:YES手动告诉GPU不要去计算遮盖
  5. 避免将几个半透明的Layer的Color融合在一起,理由也是同4。

名词解释

Rasterize

  1. 在苹果的2012年WWDC大会上,引入的一个CALayer内的BOOL属性,来控制是把Layer在一个离屏绘制的Context上计算好生成bitmap再提交,还是实时运算,默认是NO.

  2. 如果你有一堆复杂动画的Layer,但是这些Layer自己并不改变,应该设置成YES,如果Layer经常改变,应该设为NO,此时如果设为YES反而会变慢

离屏绘制

离屏的绘制一般两种情况:

  1. 有些效果(如 rounded corners,layer masks,drop shadows 和 layer rasterization)不能直接的绘制到屏幕上,必须先绘制到一个 image context 上,这种操作会引入额外的内存和 CPU 消耗。
  2. 实现了 drawRect 或者 drawLayer:inContext:,为了支持任意的绘制,core graphic 会创建一个大小跟要画的 view 一样的 backing image。并且当画完的以后要传输到 render server 上渲染。所以没事不要重载 drawRect 等函数却什么都不做。

参考文献

[1]http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=297

[2]http://stackoverflow.com/questions/19405741/when-should-i-set-layer-shouldrasterize-to-yes

赏

请问老板还招人么(/ω\)

支付宝
微信
  • iOS
  • C
  • View
  • Tips

扫一扫,分享到微信

微信分享二维码
UIView和CoreAnimation的动画研究
通过CoreGraphics切小图
© 2021 Alan Li
Hexo Theme Yilia by Litten
  • 所有文章
  • 关于我

tag:

  • iOS
  • Java
  • Collection
  • Python
  • Shell
  • CMake
  • Memory
  • JavaScript
  • Architecture
  • AnchorPoint
  • Android
  • Web
  • Annotation
  • AFNetworking
  • Window
  • ViewController
  • AutoLayout
  • Dozer
  • CoreAnimation
  • Cycle Retain
  • Block
  • UI
  • IDE
  • FrontEnd
  • CSS
  • Category
  • TableViewCell
  • Security
  • Net
  • JSP
  • Spring
  • C
  • MyBatis
  • Date
  • React
  • GCD
  • UITouch
  • Gesture
  • UIControl
  • Git
  • HTML
  • HTTPS
  • HTTP
  • Servlet
  • Server
  • DataBase
  • MySQL
  • Linux
  • Tutorial
  • Ajax
  • Type
  • JQuery
  • JSON
  • Exception
  • Parameter
  • Reflect
  • Thread
  • Sort
  • KVO
  • MKMapKit
  • Overlay
  • Maven
  • Configure
  • Tips
  • Transaction
  • Swift
  • NavigationBar
  • Nginx
  • Runtime
  • OpenCV
  • Property
  • Playground
  • Protocol
  • Redux
  • ScrollView
  • Session
  • Cookie
  • Shiro
  • Error
  • Singleton
  • RegEx
  • StackView
  • StatusBar
  • Base64
  • Socket
  • TCP
  • IP
  • TextField
  • CALayer
  • UILabel
  • View
  • Animation
  • Xcode
  • Hexo
  • Terminal
  • OC
  • Device
  • Log
  • Image
  • JUnit
  • Oval
  • Archive
  • XSS
  • Compiler
  • Aspect
  • Responder
  • Class
  • FireWall
  • RetainCount
  • Const
  • Frame
  • String
  • Symbols
  • Framework
  • CocoaPods
  • Unity
  • Message
  • Button
  • AuthorizationStatus
  • Struct
  • XCTest
  • NSNotification
  • Contact

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

我写的,大概率是错的。。。。。