记一个用Masonry及AutoLayout布局ScrollView子控件Bug

昨天朋友遇见了个关于Masonry及AutoLayout给ScrollView的子控件布局的小Bug,朋友自己解决完之后,抛给了我…

出现的Bug表现为: 
            给ScorllView的子控件(UIView)布局后, 子控件不显示.

环境:
    子控件以ScrollView的frame布局. 不设置子控件宽高.

测试:
    设置断点打印子控件frame, 发现宽高都为0.   布局改为四个约束,只设置高.
    同样不显示. 再次打印, 发现高有值, 而宽为0.

结论:
    使用Masonry或AutoLayout布局ScrollView的子控件时必须给子控件指定宽高.
    PS: Label类不在上述之内, Label类只要有内容,即可显示.

猜测原因:
    也曾猜测为在ViewDidLoad中没有拿到准确的ScrollView的frame,然后在
    viewDidAppear中手动调用了layoutIfNeeded也没有效果, 下面是猜测的
    原因:

    !!!: ScrollView的contentSize是根据子控件的大小自动计算的, 
    而contentSize即是控件的显示范围, 子控件布局可能也是参照
    contentSize的(不确定). 如果是的话, 那么可以解释为: contentSize要
    根据子控件来设置, 子控件又要根据contentSize设置.这样就造成
    上述Bug.

但是:
    我在设置子View之前 手动设置了scrollView的contentSize大小, 发现子控件
    依然不会显示.
    好奇之下,又在viewDidAppear 中设置了断点(所有工作都已经在ViewDidLoad中
    完成), 然后再次打印了子控件及ScrollView, 结果子控件的frame宽高为0, 而
    我手动设置的contentSize并没有起作用, 竟然也为0,0.. 
    这一点之前好像看到过解释, 快忘干净了, 先放在这吧....有时间再看看.

神秘代码地址,让您省去番号搜索

Core Animation 阅读随笔 第六章 - 专用图层

16/04/28 第六章 专用图层

CAShapeLayer

  1. CAShapeLayer是一个通过矢量图形绘制的图层子类,而非bitmap.
  2. 通过CAShapeLayer来绘制相比于Core Graphics而言优点在于:
    • 渲染快速, CAShapeLayer使用了硬件加速,绘制同一图形比Core Graphics快.
    • 搞笑使用内存,一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存. 矢量.
    • 不会被图层边界裁剪,一个CAShapeLayer可以在边界之外绘制。你的图层路径不会像在使用Core Graphics的普通CALayer一样被剪裁掉.
    • 不会出现像素化(矢量).当你给CAShapeLayer做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化。

-

创建一个CGPath

  1. 部分属性:
    • lineWith: 线宽,用点表示单位
    • lineCap: 线条结尾的样子
    • lineJoin: 线条之间的结合点的样子
  2. 可在同一图层绘制多个形状, 但只有一次机会设置属性,若需颜色不同,则要为每个形状准备一个图层.

-

圆角

  1. 通过UIBezierPath绘制自定义圆角个数的矩形:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //设置规则
    CGRect rect = CGRectMake(50, 50, 100, 100);
    CGSize radii = CGSizeMake(20, 20);

    UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
    //创建被三儿路径
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];

    // 设置ShapeLayer
    CAShapeLayer *shapeLayer = [CAShapeLayer alloc]init];
    shapeLayer.lineWidth = 3;
    shapeLayer.lineCap = kCALineCapRound;
    shapeLayer.lineJoin = kCALineJoinRound;
    shapeLayer.path = path.CGPath;

-

CATextLayer

  1. Core Animation提供了一个CALayer的子类CATextLayer,以图层的形式包含了UILabel几乎所有的绘制特性, 并额外提供了一些新的特性.
  2. 性能上,CATextLayerUILabel快很多.CATextLayer使用了Core Text.
  3. iOS6之前,UILabel通过WebKit来实现绘制?
  4. 用CATextLayer来实现一个UILabel:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
        @interface ViewController ()

    @property (nonatomic, weak) IBOutlet UIView *labelView;

    @end

    @implementation ViewController
    - (void)viewDidLoad
    {
    [super viewDidLoad];

    //创建TextLayer添加
    CATextLayer *textLayer = [CATextLayer layer];
    textLayer.frame = self.labelView.bounds;
    [self.labelView.layer addSublayer:textLayer];

    //设置字体颜色
    textLayer.foregroundColor = [UIColor blackColor].CGColor;
    //设置对齐方式
    textLayer.alignmentMode = kCAAlignmentJustified;
    textLayer.wrapped = YES;

    UIFont *font = [UIFont systemFontOfSize:15];

    //设置字体
    CFStringRef fontName = (__bridge CFStringRef)font.fontName;
    CGFontRef fontRef = CGFontCreateWithFontName(fontName);
    textLayer.font = fontRef;
    //设置字体大小
    textLayer.fontSize = font.pointSize;
    CGFontRelease(fontRef);

    //设置文本内容
    NSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis";

    //赋值
    textLayer.string = text;
    }
    @end

    当这样设置后,显示会出现像素化,需要以当前屏幕的Scale来显示:
    textLayer.contentsScale = [UIScreen mainScreen].scale;

  5. CATextLayerfont属性为CFTypeRef类型, 可根据具体需求设置为CGFontRefCTFontRef.
  6. 字体大小由单独的fontSize决定, 因上述CTFontRefCGFontRef并不像UIFont一样包含点大小.
  7. CATextLayerstring属性为id类型,这样既可用NSString也可用富文本NSAttributedString了.

富文本

  1. 字符属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    字符属性可以应用于 attributed string 的文本中。

    NSString *const NSFontAttributeName;(字体)

    NSString *const NSParagraphStyleAttributeName;(段落)

    NSString *const NSForegroundColorAttributeName;(字体颜色)

    NSString *const NSBackgroundColorAttributeName;(字体背景色)

    NSString *const NSLigatureAttributeName;(连字符)

    NSString *const NSKernAttributeName;(字间距)

    NSString *const NSStrikethroughStyleAttributeName;(删除线)

    NSString *const NSUnderlineStyleAttributeName;(下划线)

    NSString *const NSStrokeColorAttributeName;(边线颜色)

    NSString *const NSStrokeWidthAttributeName;(边线宽度)

    NSString *const NSShadowAttributeName;(阴影)(横竖排版)

    NSString *const NSVerticalGlyphFormAttributeName;
  1. 常量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    1> NSFontAttributeName(字体)

    该属性所对应的值是一个 UIFont 对象。该属性用于改变一段文本的字体。如果不指定该属性,则默认为12-point Helvetica(Neue)。

    2> NSParagraphStyleAttributeName(段落)

    该属性所对应的值是一个 NSParagraphStyle 对象。该属性在一段文本上应用多个属性。如果不指定该属性,则默认为 NSParagraphStyle 的defaultParagraphStyle 方法返回的默认段落属性。

    3> NSForegroundColorAttributeName(字体颜色)

    该属性所对应的值是一个 UIColor 对象。该属性用于指定一段文本的字体颜色。如果不指定该属性,则默认为黑色。

    4> NSBackgroundColorAttributeName(字体背景色)

    该属性所对应的值是一个 UIColor 对象。该属性用于指定一段文本的背景颜色。如果不指定该属性,则默认无背景色。

    5> NSLigatureAttributeName(连字符)

    该属性所对应的值是一个 NSNumber 对象(整数)。连体字符是指某些连在一起的字符,它们采用单个的图元符号。0 表示没有连体字符。1 表示使用默认的连体字符。2表示使用所有连体符号。默认值为 1(注意,iOS 不支持值为 2)。

    6> NSKernAttributeName(字间距)

    该属性所对应的值是一个 NSNumber 对象(整数)。字母紧排指定了用于调整字距的像素点数。字母紧排的效果依赖于字体。值为 0 表示不使用字母紧排。默认值为0

    7> NSStrikethroughStyleAttributeName(删除线)

    该属性所对应的值是一个 NSNumber 对象(整数)。该值指定是否在文字上加上删除线,该值参考“Underline Style Attributes”。默认值是NSUnderlineStyleNone。

    8> NSUnderlineStyleAttributeName(下划线)

    该属性所对应的值是一个 NSNumber 对象(整数)。该值指定是否在文字上加上下划线,该值参考“Underline Style Attributes”。默认值是NSUnderlineStyleNone。

    9> NSStrokeColorAttributeName(边线颜色)

    该属性所对应的值是一个 UIColor 对象。如果该属性不指定(默认),则等同于 NSForegroundColorAttributeName。否则,指定为删除线或下划线颜色。更多细节见“Drawing attributedstrings that are both filled and stroked”。

    10> NSStrokeWidthAttributeName(边线宽度)

    该属性所对应的值是一个 NSNumber 对象(小数)。该值改变描边宽度(相对于字体size 的百分比)。默认为 0,即不改变。正数只改变描边宽度。负数同时改变文字的描边和填充宽度。例如,对于常见的空心字,这个值通常为3.0

    11> NSShadowAttributeName(阴影)

    该属性所对应的值是一个 NSShadow 对象。默认为 nil

    12> NSVerticalGlyphFormAttributeName(横竖排版)

    该属性所对应的值是一个 NSNumber 对象(整数)。0 表示横排文本。1 表示竖排文本。在 iOS 中,总是使用横排文本,0 以外的值都未定义。

UILabel的替代品

  1. 可以通过重写View的类方法 + (Class)layerClass来将UILabel替换成CATextLayer;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72

    #import "LayerLabel.h"
    #import <QuartzCore/QuartzCore.h>

    @implementation LayerLabel
    + (Class)layerClass
    {
    //this makes our label create a CATextLayer //instead of a regular CALayer for its backing layer
    return [CATextLayer class];
    }

    - (CATextLayer *)textLayer
    {
    return (CATextLayer *)self.layer;
    }

    - (void)setUp
    {
    //set defaults from UILabel settings
    self.text = self.text;
    self.textColor = self.textColor;
    self.font = self.font;

    //we should really derive these from the UILabel settings too
    //but that's complicated, so for now we'll just hard-code them
    [self textLayer].alignmentMode = kCAAlignmentJustified;

    [self textLayer].wrapped = YES;
    [self.layer display];
    }

    - (id)initWithFrame:(CGRect)frame
    {
    //called when creating label programmatically
    if (self = [super initWithFrame:frame]) {
    [self setUp];
    }
    return self;
    }

    - (void)awakeFromNib
    {
    //called when creating label using Interface Builder
    [self setUp];
    }

    - (void)setText:(NSString *)text
    {
    super.text = text;
    //set layer text
    [self textLayer].string = text;
    }

    - (void)setTextColor:(UIColor *)textColor
    {
    super.textColor = textColor;
    //set layer text color
    [self textLayer].foregroundColor = textColor.CGColor;
    }

    - (void)setFont:(UIFont *)font
    {
    super.font = font;
    //set layer font
    CFStringRef fontName = (__bridge CFStringRef)font.fontName;
    CGFontRef fontRef = CGFontCreateWithFontName(fontName);
    [self textLayer].font = fontRef;
    [self textLayer].fontSize = font.pointSize;

    CGFontRelease(fontRef);
    }
    @end

Swift3.0不远了

因各种理由拖着的Swift学习在Swift3.0之后可以开始了, 相信之后版本更新,API等改动变化会少很多了:)

Core Animation 阅读随笔 第五章 - 变换

16/04/25 第五章 变换

仿射变换 CGAffineTransform

  1. CGAffineTransform是一个3*2的矩阵,可以和二维空间向量做乘法. 主要用于平移,旋转,缩放.
  2. 当对图层应用变换矩阵,图层矩形内的每一个点都被相应地做变换,从而形成一个新的四边形的形状。CGAffineTransform中的“仿射”的意思是无论变换矩阵用什么值,图层中平行的两条线在变换之后任然保持平行,CGAffineTransform可以做出任意符合上述标注的变换.
  3. Core Graphics提供了一系列函数来帮助完全没有数学基础的开发者.
  4. UIView可通过transform进行变换, 事实上UIViewtransform封装了CALayeraffineTransform.
  5. CALayer也有一个transform属性,类型是CATransform3D.
  6. 弧度与角度换算宏

    #define RADIANS_TO_DEGREES(x) ((x)/M_PI*180.0)
    #define DEGREES_TO_RADIANS(x) ((x)/180.0*M_PI)

-

混合变换

Core Graphics提供了一系列的函数可以在一个变换的基础上做更深层次的变换,如果做一个既要缩放又要旋转的变换,这就会非常有用了。例如下面几个函数:

CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)
当操纵一个变换的时候,初始生成一个什么都不做的变换很重要–也就是创建一个CGAffineTransform类型的空值,矩阵论中称作单位矩阵,Core Graphics同样也提供了一个方便的常量:

CGAffineTransformIdentity
最后,如果需要混合两个已经存在的变换矩阵,就可以使用如下方法,在两个变换的基础上创建一个新的变换:

CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);

-

3D变换 - CATransform3D

CG的前缀告诉我们,CGAffineTransform类型属于Core Graphics框架,Core Graphics实际上是一个严格意义上的2D绘图API,并且CGAffineTransform仅仅对2D变换有效。

在第三章中,我们提到了zPosition属性,可以用来让图层靠近或者远离相机(用户视角),transform属性(CATransform3D类型)可以真正做到这点,即让图层在3D空间内移动或者旋转。

和CGAffineTransform类似,CATransform3D也是一个矩阵,但是和2x3的矩阵不同,CATransform3D是一个可以在3维空间内做变换的4x4的矩阵

  1. CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
    CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
    CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)

  2. 以旋转为例, 当只是设置了旋转角度和坐标轴时, 显示结果并不是3D效果, 需要设置矩阵内的m34属性进行透视投影.

透视投影

  1. CATransform3D的透视效果通过一个矩阵中一个很简单的元素来控制:m34
  2. m34用于按比例缩放X和Y的值来计算到底要离视角多远.
  3. m34默认值为0,可通过设置m34为-1.0 / d来应用透视效果,d代表了想象中视角相机和屏幕之间的距离,以像素为单位. 距离并不重要,可以通过运行来估算一个. 通常为500-1000.

    1
    2
    3
    4
    5
    6
    7
      CATransform3D transform = CATransform3DIdentity;

    transform.m34 = - 1.0 / 500.0;

    transform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0);

    self.layerView.layer.transform = transform;

灭点

  1. 当在透视角度绘图的时候,远离相机视角的物体将会变小变远,当远离到一个极限距离,它们可能就缩成了一个点,于是所有的物体最后都汇聚消失在同一个点。

  2. 当改变一个图层的position,你也改变了它的灭点,做3D变换的时候要时刻记住这一点,当你视图通过调整m34来让它更加有3D效果,应该首先把它放置于屏幕中央,然后通过平移来把它移动到指定位置(而不是直接改变它的position),这样所有的3D图层都共享一个灭点。这样做非常麻烦, 可以通过sublayerTransform属性来统一设置灭点(灭点在容器图层中点),可以随意使用position和frame来放置子图层,而不需要把它们放置在屏幕中点,然后为了保证统一的灭点用变换来做平移

待续...

Core Animation 阅读随笔 第四章 - 视觉效果

16/04/23 第四章视觉效果

borderWidth

  1. borderWidth是绘制在frame之内的内边框.
  2. borderWidth的绘制只是沿着图层的边框绘制, 如果layer上的图片(寄宿图)超出layer并显示, borderWidth依然只沿着layer的边界绘制, 而不会包括超出边界的寄宿图, 即:borderWidth是跟随图层边界变化,而非图层里面的内容;

阴影

  1. 阴影和上述边框不同, 阴影是跟随图层内容的, 而非图层边框.
  2. 当即需要阴影又需要裁剪时, 裁剪会把阴影也裁剪掉, 可在图层外再包一个视图,设置外包视图的阴影, 裁剪内容视图;
  3. 默认的阴影效果是跟随图层内容的, 非常消耗资源, 可以通过shadowPath来提高性能.
  4. shadowPathCGPathRef类型,是指向CGPath的指针, CGPathCore Graphics对象,用来指定任意一个矢量图形,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    
@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *layerView1;
@property (nonatomic, weak) IBOutlet UIView *layerView2;
@end

@implementation ViewController

- (void)viewDidLoad
{
[super viewDidLoad];

//阴影模糊度
self.layerView1.layer.shadowOpacity = 0.5f;
self.layerView2.layer.shadowOpacity = 0.5f;

///创建长方形阴影
// 1. 创建绘制路径
CGMutablePathRef squarePath = CGPathCreateMutable();
// 2. 指定绘制长方形的范围
CGPathAddRect(squarePath, NULL, self.layerView1.bounds);
// 3. 设置路径
self.layerView1.layer.shadowPath = squarePath;
// 4. releaase
CGPathRelease(squarePath);

///创建椭圆阴影
// 1. 创建绘制路径
CGMutablePathRef circlePath = CGPathCreateMutable();
// 2. 指定绘制椭圆的范围
CGPathAddEllipseInRect(circlePath, NULL, self.layerView2.bounds);
// 3. 设置路径
self.layerView2.layer.shadowPath = circlePath;
// 4. releaase
CGPathRelease(circlePath);
}
@end

mask蒙版遮罩属性

  1. mask属性本身是一个CALayer,定义了父图层的部分可见区域;
  2. mask属性的color是无关紧要的, 本是就像一个模具(比如:小熊饼干模具,月饼等形状模具),如果mask比父视图小, 那么只会显示mask这个模具形状的内容. 比如你父视图是红色的矩形, 而mask是一个任意颜色的五角星, 那么最终显示结果是一个红色的五角星, 超出mask范围外的都会隐藏. 就像模具.
  3. mask蒙版图层不仅仅限于静态图,任何图层构成的都可以作为mask属性.意味着你可以通过代码或动画来实时生成;

拉伸过滤

最后我们再来谈谈minificationFilter和magnificationFilter属性。总得来讲,当我们视图显示一个图片的时候,都应该正确地显示这个图片(意即:以正确的比例和正确的1:1像素显示在屏幕上)。原因如下:

能够显示最好的画质,像素既没有被压缩也没有被拉伸。
能更好的使用内存,因为这就是所有你要存储的东西。
最好的性能表现,CPU不需要为此额外的计算。
不过有时候,显示一个非真实大小的图片确实是我们需要的效果。比如说一个头像或是图片的缩略图,再比如说一个可以被拖拽和伸缩的大图。这些情况下,为同一图片的不同大小存储不同的图片显得又不切实际。

当图片需要显示不同的大小的时候,有一种叫做拉伸过滤的算法就起到作用了。它作用于原图的像素上并根据需要生成新的像素显示在屏幕上。

事实上,重绘图片大小也没有一个统一的通用算法。这取决于需要拉伸的内容,放大或是缩小的需求等这些因素。CALayer为此提供了三种拉伸过滤方法,他们是:

kCAFilterLinear
kCAFilterNearest
kCAFilterTrilinear
minification(缩小图片)和magnification(放大图片)默认的过滤器都是kCAFilterLinear,这个过滤器采用双线性滤波算法,它在大多数情况下都表现良好。双线性滤波算法通过对多个像素取样最终生成新的值,得到一个平滑的表现不错的拉伸。但是当放大倍数比较大的时候图片就模糊不清了。

kCAFilterTrilinear和kCAFilterLinear非常相似,大部分情况下二者都看不出来有什么差别。但是,较双线性滤波算法而言,三线性滤波算法存储了多个大小情况下的图片(也叫多重贴图),并三维取样,同时结合大图和小图的存储进而�得到最后的结果。

这个方法的好处在于算法能够从一系列已经接近于最终大小的图片中得到想要的结果,也就是说不要对很多像素同步取样。这不仅提高了性能,也避免了小概率因舍入错误引起的取样失灵的问题.

对于大图来说,双线性滤波和三线性滤波表现得更出色

kCAFilterNearest是一种比较武断的方法。从名字不难看出,这个算法(也叫最近过滤)就是取样最近的单像素点而不管其他的颜色。这样做非常快,也不会使图片模糊。但是,最明显的效果就是,会使得压缩图片更糟,图片放大之后也显得块状或是马赛克严重。

对于没有斜线的小图来说,最近过滤算法要好很多

总的来说,对于比较小的图或者是差异特别明显,极少斜线的大图,最近过滤算法会保留这种差异明显的特质以呈现更好的结果。但是对于大多数的图尤其是有很多斜线或是曲线轮廓的图片来说,最近过滤算法会导致更差的结果。换句话说,线性过滤保留了形状,最近过滤则保留了像素的差异。

组透明

  1. UIViewalpha属性可设置透明度, CALayer与之对应的是opacity, 两个属性都会对子视图造成影响.
  2. 在自定义视图中, 对父视图进行透明度设置, 子视图的透明度会跟随父视图, 虽然如此,但最终显示结果却不尽人意, 这是因为最终显示结果由两个视图叠加而成, 也就是说,如果父视图透明度为50%, 子视图也为50%, 系统绘制图层时,会根据两者的透明度混合叠加. 最终重叠部分的透明度看起来像75%. 可以通过CALayershouldRasterize属性来实现组透明效果.
  3. shouldRasterize属性设置为YES会在透明度被应用之前将所有视图合成一张整体图片. 但当设置shouldRasterize属性为YES时,为了防止Retina屏幕像素化, 就需要设置rasterizationScale属性去匹配屏幕. 而一旦这两个属性同时设置时会出现性能问题.会在12章和15章讲解.

React-Native布局实战(二)

在不断深入的过程中,发现React-Native布局和样式的坑还有很多,他没有像浏览器那样灵活和有规律可循,其中的规律需要我自己踩坑的时候发现。比如:不存在zIndex,后面的元素覆盖前面的元素;内层元素覆盖外层元素等等,borderRadius的设置,需要考虑到内层元素的位置等等

一:实战的内容

这里选用携程的App首页作为栗子,随不是严格的9宫格(比9宫格稍微难点…),但是可以很好的让我们练习flexbox.最后需要完成的结果如下:

4_0_1

二:分解内容

整个页面我们可以分为几个部分,大致如下:

  • 头部
  • 图片轮播
  • 9宫格
  • 底部活动

三:头部导航栏

因为,组件还没有讲,这里只是做一个简单的介绍。在React-Native中实现头部导航栏很简单,只要使用NavigatorIOS组件即可。现在开工。
  1. 我们在index.ios.js中添加如下代码;同时创建文件夹pagaes和pages下创建Index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
        var React = require('react-native');
    var Index = require('./pages/Index');

    var {
    NavigatorIOS,
    AppRegistry,
    StyleSheet,
    } = React;

    var NV = React.createClass({
    render: function(){
    return(
    <NavigatorIOS
    style={styles.container}
    initialRoute={{
    title: '首页',
    component: Index,
    }}
    />
    );
    }
    });

    var styles = StyleSheet.create({
    container: {
    flex: 1,
    }
    });


    AppRegistry.registerComponent('HelloWorld', () => NV);

    分析代码:
    (1)require:引入外部模块,就像,引入我们自己创建的/pages/Index.js一样。
    (2)引入定义NavigatorIOS、AppRegistry、StyleSheet组件和类。
    (3)在render中调用NavigatorIOS组件,initialRoute是初始化路由,title是当前页面的头部标题;component是当前路由下显示的组件;
    (4)注意:这里NavigatorIOS的style需要设置大小,比如这里设置是flex:1,否则就不能显示内容主体;
    (5)最后我们需要注册当前应用:AppRegistry.registerComponent('HelloWorld', () => NV);
  2. 创建Index.js文件,文件的内容如下, module.exports就暴露了Index模块。

4_0_3

实现效果如下:

4_0_2

四:图片轮播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
这里图片轮播使用的是第三方组件react-native-swiper,当然React-Native是支持transform可以直接实现一套。我们启动npm命令行,在项目的根目录使用如下命令安装模块。

$ npm install react-native-swiper --save
$ npm i react-timer-mixin --save
(2)需要关闭React packager命令行和模拟器,在xcode中重启项目

安装完成后,我们需要完成轮播功能。因为可以到github看看swiper暴露的接口和参数。github地址是:https://github.com/leecade/react-native-swiper

1)引入swiper,前面也提到了require.
var Swiper = require('react-native-swiper');

2)使用swiper,将轮播图封装成单独的组件
var sliderImgs = [
'http://images3.c-ctrip.com/SBU/apph5/201505/16/app_home_ad16_640_128.png',
'http://images3.c-ctrip.com/rk/apph5/C1/201505/app_home_ad49_640_128.png',
'http://images3.c-ctrip.com/rk/apph5/D1/201506/app_home_ad05_640_128.jpg'
];
var Slider = React.createClass({
render: function(){
return (
<Swiper style={styles.wrapper} showsButtons={false} autoplay={true} height={150} showsPagination={false}>
<Image style={[styles.slide,]} source={{uri: sliderImgs[0]}}></Image>
<Image style={[styles.slide,]} source={{uri: sliderImgs[1]}}></Image>
<Image style={[styles.slide,]} source={{uri: sliderImgs[2]}}></Image>
</Swiper>
);
}
});
(3)这样我们可以直接在render的时候直接用:<Slider/>

五:完成第一个9宫格布局,后面复制拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
其实4个九宫格都是一样,这个其实可以封装成组件,这里采用拷贝的形式,开发一个,其他3个就ok的,不会偷懒的工程师,不是好工程师[偷笑]。分析下布局:
(1)其实首先是3个列在一行的布局,那么外层组件是需要flexDirection: 'row',各占据宽度的1/3,即各自flex:1;
(2)每个列内又分两行, 需要每个行都是flex:1,各占据高度的一半;
(3)第一列是文字图片组合,其余都是文字组合;
(4)所有行内元素都是水平、垂直居中;
(5)这里使用了个TouchableHighlight组件,是为了出发onPress事件,类似于click或者touch事件。

<View style={[styles.sbu_red, styles.sbu_view]}>
<TouchableHighlight underlayColor={'#FA6778'} style={{flex:1}}>
<View style={[styles.sbu_flex, styles.sbu_borderRight]}>
<View style={[styles.sub_con_flex, styles.sub_text]}>
<Text style={[styles.font16]}>酒店</Text>
</View>
<View style={[styles.sub_con_flex]}>
<Image style={[styles.sbu_icon_img]} source={{uri:BUIcon[0]}}></Image>
</View>
</View>
</TouchableHighlight>
<View style={[styles.sbu_flex, styles.sbu_borderRight]}>
<View style={[styles.sub_con_flex, styles.sub_text, styles.sbu_borderBottom]}>
<Text style={[styles.font16]}>海外</Text>
</View>
<View style={[styles.sub_con_flex, styles.sub_text]}>
<Text style={[styles.font16]}>周边</Text>
</View>
</View>
<View style={[styles.sbu_flex]}>
<View style={[styles.sub_con_flex, styles.sub_text, styles.sbu_borderBottom]}>
<Text style={[styles.font16]}>团购.特惠</Text>
</View>
<View style={[styles.sub_con_flex, styles.sub_text]}>
<Text style={[styles.font16]}>客栈.公寓</Text>
</View>
</View>
</View>

六:样式类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
说完了布局的原理,这里需要贴上样式仅供参考:
var styles = StyleSheet.create({
//container
container:{
backgroundColor:'#F2F2F2',
flex:1,
},
//slider
wrapper: {
height:80,
},
slide: {
height:80,
resizeMode: Image.resizeMode.contain,
},
//sbu
sbu_view:{
height:84,
marginLeft: 5,
marginRight:5,
borderWidth:1,
borderRadius:5,
marginBottom:10,
flexDirection:'row',
},
sbu_red:{
backgroundColor: '#FA6778',
borderColor:'#FA6778',
marginTop:-70,
},

sbu_blue:{
backgroundColor: '#3D98FF',
borderColor:'#3D98FF',
},

sbu_green:{
backgroundColor: '#5EBE00',
borderColor:'#5EBE00',
},

sbu_yellow:{
backgroundColor: '#FC9720',
borderColor:'#FC9720',
},
sbu_flex:{
flex:1,
},
sbu_borderRight:{
borderColor:'#fff',
borderRightWidth: 0.5,
},
sbu_icon_img:{
height:40,
width:40,
resizeMode:Image.resizeMode.contain,
},
sub_con_flex:{
flex:1,
justifyContent: 'center',
alignItems: 'center',
},
sub_text:{
justifyContent:'center',
},
font16:{
fontSize:17,
color:'#FFF',
fontWeight:'900',
},
sbu_borderBottom:{
borderBottomWidth:0.5,
borderBottomColor:'#fff',
},
img_view:{
height:62,
marginLeft:5,
marginRight:5,
flexDirection: 'row',
marginBottom:20,
backgroundColor:'#fff',
},
img_flex:{
flex:1,
borderWidth:1,
borderColor:'#ccc',
},
img_wh: {
height:59,
borderRightWidth:0,
resizeMode:Image.resizeMode.contain,
}
});

着重说下resizeMode:Image.resizeMode.contain。在React-Native中图片的大小是不会根据给定一个宽度或者高度而自适应大小的,因此我们需要让图片根据宽度或者高度来自适应,那么可以使用resizeMode:Image.resizeMode.contain。facebook提示错误信息的样式表中也没有提及,文档中也没有提及。所以后续还有不少的坑需要大家去一起探索。

Core Animation 阅读随笔 第三章

16/04/21 第三章图层几何学

布局

  1. UIView的三个重要布局属性frame``bounds``center, CALayer与之对应的是frame``bounds``position. UIView的三个布局属性是映射的CALayer的布局属性.
  2. frame是根据bounds``positiontransform计算而来.
  3. 当视图旋转时,可能会造成 framesize的大小与boundssize的大小不一致.
  4. anchorPoint锚点,默认为视图中心.钟表指针需改变锚点.
  5. 相对坐标的转换函数:

    UIView使用:

    - (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view;

    - (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view;

    - (CGRect)convertRect:(CGRect)rect toView:(UIView *)view;

    - (CGRect)convertRect:(CGRect)rect fromView:(UIView *)view;

    CALayer使用:

    - (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;

    - (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;

    - (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;

    - (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;

  6. zPosition

    1
    2
    3
    4
    5
    设置layer的zPosition可以更改图层显示顺序.一般设置为`n * 1px`的浮点型,最好不要设置有小数,因为浮点型的四舍五入会造成不必要的麻烦.

    需要注意的是, 通过zPosition只能更改图层的显示顺序,而不能更改事件的传递顺序.

    事件的传递顺序与视图先后添加的顺序有关,而与图层显示顺序无关.只是默认的图层显示顺序与视图先后添加的顺序相同而已.
  1. Hit Testing

    一直以为-containsPoint:-hitTest:方法是UIView的, 原来也是CALayer的.
    CALayer不关心任何响应链事件,但是提供了以上两个方法,你可以通过这两个方法来处理事件.

Core Animation 阅读随笔 第一二章

16/04/20 第一章


1. CALayerUIView的最大区别就是CALayer不能处理用户交互, 虽然CALayer提供了一些方法来判断触摸点是否在图层的范围内,但是CALayer并不清楚具体的响应链.UIView仅仅是对CALayer的封装并提供了一些处理触摸的具体功能以及Core Animation底层方法的高级接口, 所以在性能上CALayer优于UIView.
2. 每个UIView都有一个CALayer的实例属性(backing layer), 视图的职责就是创建并管理这个图层以确保当子视图在层级关系中添加或被移除时候,他们关联的图层所对应的层级关系树中也同样有相同的操作.

16/04/20 第二章 CALayer一些属性

Contents

  1. CALayercontents属性之所以为id是Mac OS的历史原因造成的,在Mac OS上contents属性CGImageNSImage都做支持,所以用了id.
  2. CALayercontents属性只能用CGImageRef来赋值. CGImageRef是一个指向CGImage结构体的指针. 可以通过UIImageCGImage属性来获得CGImageRef(image.CGImage).但是CGImageRefCore Fundation类型,并不是Cocoa的对象, 所以需要桥接一下(__bridge id _Nullable)(image.CGImage);

ContentsGravity

  1. 当给contents属性赋图时可能遇到与UIImageView相同的情况:拉伸方式不预期.而UIImageView的解决方法是更改contentMode属性,如:imageView.contentMode = UIViewContentModeScaleAspectFit;``UIView的分类中UIView(UIViewRendering)也有contentMode属性. 重点来了,事实上UIView对大多数视觉属性的操作,是对CALayer的操作.如contentMode,frame等. 而CALayer中与contentMode对应的属性是contentsGravity.相关常量值在头文件781-806行.self.view.layer.contentsGravity = kCAGravityResizeAspect;等效于imageView.contentMode = UIViewContentModeScaleAspectFit;

contentsScale

  1. CALayer中的contentsScale属性的定义:像素尺寸和视图大小的比例,默认值为1.0;如果设置为1.0则表示将会以每个点一个像素绘制图片,如果设置为2.0,那么以每个点两个像素绘制图片.当设置了contentsGravity时,会优先采纳contensGravity.

maskToBounds

  1. CALayerUIView一致,默认情况下会显示超出边界的内容.即maskToBounds属性默认为NO, 设置成YES可不显示超出部分, UIView对应的是clipsToBounds;

contentsRect

  1. 默认的contentsRect是 {0, 0, 1, 1}. 如果指定一个小一些的矩形范围,图片将会被裁剪.

Custom drawing

  1. 给layer设置图形还可以通过drawRect方法自定义绘制,drawRect方法没有默认实现, 苹果建议;如果没有自定义绘制任务的需求,不要在创建一个空的drawRect方法,会造成CPU资源和内存资源的浪费.
  2. CALayer有一个delegate属性,实现了CALayerDelegate协议.协议中所有方法都是可选的. 当需要重绘时,CALayer会请求它的代理给他一个图片来显示,会调用- (void)displayLayer:(CALayer *)layer; 如果代理没有实现- (void)displayLayer:这个方法, 就会调用- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx; 在调用此方法前,CALayer创建了一个合适尺寸的空的寄宿图(尺寸由bounds和contentsScale决定)和一个Core Graphics的图形上下文,为绘制寄宿图做准备.
  3. 使用CALayerDelegate绘制图形时,不对超出边界外的内容提供绘制支持.

React-Native布局实战(一)

前辈教导我们,掌握一门新技术的最快方法是练习。因此,我找了下比较有爱,暖气的界面。当然不是给美团打广告了,只是觉得页面蛮清新的。下面就是要显示的效果:

4_1

第三篇文章基本上对React-Native的布局基本上有个大致的认识,现在开工吧。

总体上,该页面分三个部分:

  1. 我们约会吧及其右边3栏;
  2. 1元吃大餐及其底下的4栏;
  3. 红火来袭的三栏。

一:实现第一部分

1、首先,我们创建一个项目

1
2
3
4
5
6
7
8
9
10
现在我们需要创建一个React-Native的项目,因此可以按照下面的步骤:
打开终端,开始React-Native开发的旅程吧。

(1)安装命令行工具(已经安装了就不用再安装了):sudo npm install -g react-native-cli

(2)创建一个空项目:react-native init MeiTuanHomePage

(3)找到创建的MeiTuanHomePage项目,双击MeiTuanHomePage.xcodeproj即可在xcode中打开项目。xcodeproj是xcode的项目文件。

(4)在xcode中,使用快捷键cmd + R即可启动项目。

2、清除其余多余的代码,剩下的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/


import React, {
AppRegistry,
Component,
StyleSheet,
Text,
View
} from 'react-native';

class MeiTuanHomePage extends Component {
render() {
return (
<View>

</View>
);
}
}

const styles = StyleSheet.create({

});

AppRegistry.registerComponent('MeiTuanHomePage', () => MeiTuanHomePage);

3、此时,除了闪屏外,看到应该是空白的页面。开工,分析页面:

1
2
(1)大致有4个板块
(2)先是左右两栏(1/3和2/3);后是上下两栏(1/2)。我们先用View组件布局。

4_2

4、完成初步布局

看如下代码,应该很清楚了,View里面嵌入并列的两栏。

4_3

实现效果如下:

4_4

5、添加内部图片和文字

其实做这种布局,还是有很多的细节,粗糙的效果如下,这块代码暂时不贴了,最后一并贴出来:

4_5

二:按照第一部分原理,完成整个页面

完成的效果如下:

4_6

整个代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/

'use strict';

var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
View,
Image,
} = React;


var MeiTuanHomePage = React.createClass({
render: function() {
return (
<View style={{}}>
<View style={[styles.height160, styles.row,]}>
<View style={[styles.height160, styles.part_1_left,]}>
<Text style={[styles.font14, styles.marTop18, styles.marLeft10, styles.green]}>我们约吧</Text>
<Text style={[styles.font10, styles.marTop14, styles.marLeft10]}>恋爱家人好朋友</Text>
<Image style={[styles.yue]} source={{uri: 'http://p0.meituan.net/mmc/fe4d2e89827aa829e12e2557ded363a112289.png'}}></Image>
</View>
<View style={[styles.height160, styles.part_1_right,]}>
<View style={[styles.row, {flex:1}]}>
<View style={{flex:1}}>
<Text style={[styles.font14, {marginLeft:30}, styles.red]}>超低价值</Text>
<Text style={[styles.font14, {fontSize:12, marginTop:14, marginLeft:30,color: 'black'}]}>十元惠生活</Text>
</View>
<View style={[{flex:1}, {marginTop:-13}]}>
<Image style={[styles.hanbao]} source={{uri: 'http://p0.meituan.net/mmc/a06d0c5c0a972e784345b2d648b034ec9710.jpg'}}></Image>
</View>
</View>
<View style={[{flex:1, flexDirection: 'row',borderTopWidth:0.5, borderColor:'#DDD8CE'}]}>
<View style={{flex:1, borderRightWidth:1, borderColor:'#DDD8CE',}}>
<Text style={{color:'#F742AB', marginLeft:5,fontWeight:'bold', fontSize:15, marginTop:8}}>聚餐宴请</Text>
<Text style={{fontSize:12,marginTop:4, marginLeft:5}}>朋友家人常聚聚</Text>
<Image style={{height:25,width:25, alignSelf: 'center'}} source={{uri: 'http://p1.meituan.net/mmc/08615b8ae15d03c44cc5eb9bda381cb212714.png'}}></Image>
</View>
<View style={{flex:1,}}>
<Text style={[styles.font14,{color:'#FF8601', marginTop:8, marginLeft:5}]}>名店抢购</Text>
<Text style={[{marginLeft:5, fontSize:12,marginTop:4,}]}>还有</Text>
<Text style={[{marginLeft:5, fontSize:12,marginTop:4,}]}>12:06:12分</Text>
</View>
</View>
</View>
</View>
<View>
<View style={{borderBottomWidth:1,borderTopWidth:1, borderColor:'#DDD8CE', marginTop:40,height:65, flexDirection: 'row',paddingTop:10}}>
<View style={[{flex:1}]}>
<Text style={{fontSize:17, color:'#FF7F60', fontWeight:'900', marginLeft:7}}>一元吃大餐</Text>
<Text style={{marginLeft:7, fontSize:12, marginTop:3}}>新用户专享</Text>
</View>
<View style={{flex:1}}>
<Image style={{height:50, width:120}} source={{uri:'http://p1.meituan.net/280.0/groupop/7f8208b653aa51d2175848168c28aa0b23269.jpg'}}></Image>
</View>
</View>
</View>
<View>
<View style={{flexDirection: 'row',}}>
<View style={[styles.row, {borderColor:'#DDD8CE', borderRightWidth:1}]}>
<View style={{flex:1,}}>
<Text style={{fontSize:17, color:'#EA6644', fontWeight:'bold', marginLeft:7}}>撸串节狂欢</Text>
<Text style={{fontSize:12, color:'#97979A', marginTop:3, marginLeft:7}}>烧烤6.6元起</Text>
</View>
<View style={{flex:1}}>
<Image style={{width:60,height:55}} source={{uri: 'http://p1.meituan.net/280.0/groupop/fd8484743cbeb9c751a00e07573c3df319183.png'}}></Image>
</View>
</View>
<View style={styles.row}>
<View style={{flex:1}}>
<Text style={{fontSize:17, color:'#EA6644', fontWeight:'bold', marginLeft:7}}>毕业旅行</Text>
<Text style={{fontSize:12, color:'#97979A', marginTop:3, marginLeft:7}}>选好酒店才安心</Text>
</View>
<View style={{flex:1}}>
<Image style={{width:60,height:55}} source={{uri: 'http://p0.meituan.net/280.0/groupop/ba4422451254f23e117dedb4c6c865fc10596.jpg'}}></Image>
</View>
</View>
</View>
<View style={{flexDirection: 'row',}}>
<View style={[styles.row, {borderColor:'#DDD8CE', borderRightWidth:1, marginLeft:1},]}>
<View style={{flex:1}}>
<Text style={{fontSize:17, color:'#EA6644', fontWeight:'bold', marginLeft:7}}>0元餐来袭</Text>
<Text style={{fontSize:12, color:'#97979A', marginTop:3, marginLeft:7}}>免费吃喝玩乐购</Text>
</View>
<View style={{flex:1}}>
<Image style={{width:60,height:55}} source={{uri: 'http://p0.meituan.net/280.0/groupop/6bf3e31d75559df76d50b2d18630a7c726908.png'}}></Image>
</View>
</View>
<View style={styles.row}>
<View style={{flex:1}}>
<Text style={{fontSize:17, color:'#EA6644', fontWeight:'bold', marginLeft:7}}>热门团购</Text>
<Text style={{fontSize:12, color:'#97979A', marginTop:3, marginLeft:7}}>大家都在买什么</Text>
</View>
<View style={{flex:1}}>
<Image style={{width:60,height:55}} source={{uri: 'http://p1.meituan.net/mmc/a616a48152a895ddb34ca45bd97bbc9d13050.png'}}></Image>
</View>
</View>
</View>
</View>
</View>
);
} });

var styles = StyleSheet.create({
row:{
flexDirection: 'row',
paddingTop:20
},
marTop18:{
marginTop:18,
},
marTop14:{
marginTop:14,
},
font14:{
fontSize:14,
},
font10:{
fontSize:12,
},
height160:{
height: 160
},
yue:{
height:80,
},
green:{
color:'#55A44B',
fontWeight: '900'
},
red:{
color: '#FF3F0D',
fontWeight: '900'
},
marLeft10:{
marginLeft:10,
},
part_1_left:{
flex: 1,
borderColor: '#DCD7CD',
borderRightWidth: 0.5,
borderBottomWidth: 1,
},
part_1_right:{
flex:2,
borderColor: '#DCD7CD',
borderBottomWidth: 1,
},
hanbao:{
height:55,
width:55
}
});


AppRegistry.registerComponent('MeiTuanHomePage', () => MeiTuanHomePage);