一起来学Masonry (一)
lf11111111
8年前
<p> </p> <p>在看这篇文章之前作者已经默认了你已经有了自动布局的概念,如果对自动布局还是不了解的话,可以看下这篇文章: <a href="http://www.open-open.com/lib/view/open1452753337683.html">iOS使用autolayout和sizeclass 解决适配问题</a> 。或许读完这篇文章的话你会对自动布局有新的认识。</p> <h3>那么我们使用自动布局的途径有哪些呢?</h3> <ul> <li> <p>使用系统的约束</p> </li> <li> <p>使用VFL语言</p> </li> <li> <p>使用第三方 <a href="/misc/goto?guid=4958877303436721101" rel="nofollow,noindex">Masonry</a></p> </li> <li> <p>使用第三方 <a href="/misc/goto?guid=4958977587719594399" rel="nofollow,noindex">SDAutoLayout</a></p> </li> </ul> <p>可能还有一些小众的,这里不在多做介绍。</p> <p>下面就对上面的四种方式先从写法上做一些简单比较。</p> <p>1.使用系统提供的约束写布局</p> <pre> <code class="language-objectivec">[self.view addConstraint: [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:redView attribute:NSLayoutAttributeLeft multiplier:1 constant:0]]; </code></pre> <p>这段代码的意思是:给blueview添加一条左边的约束相对于redview左边的约束。也就是说 blueview 的左边和redview的左边是对齐的。</p> <p>等价于: blueview.left = redview.left *multiplier +constant.</p> <p>2.使用vfl语言</p> <pre> <code class="language-objectivec">NSString *vfl = @“V:|-5-[_view]-10-[_imageView(20)]-10-[_backBtn]-5-|"; </code></pre> <p>这段代码的意思是:在垂直方向从上到下,view离父视图5点,imageView距离view 10点,同时imageView是20点高,backBtn离imageView底部10点,距离父视图底部5点。</p> <p>3.使用Masonry来布局</p> <pre> <code class="language-objectivec"> [self.pointImageview mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.view).with.offset(20); make.top.equalTo(self.view).with.offset(22); make.width.height.mas_equalTo(3); }]; </code></pre> <p>这是一个给pointImageview 添加一个完整约束的代码 这段代码的意思是:pointImageview 左边距离self.view 20 顶部距离self.view 是22 宽高都是3</p> <p>4.使用SDAutoLayout</p> <pre> <code class="language-objectivec">view0.sd_layout .leftSpaceToView(self.view, 10) .topSpaceToView(self.view, 80) .heightIs(100) .widthRatioToView(self.view, 0.4); </code></pre> <p>这段代码的意思是:view0 左边距离self.view的左边是10 顶部距离self.view的距离是80.高是100 宽是self.view的0.4倍。</p> <p>通过上面的几种写法相信你对这几种自动布局的方式也应该有所了解了。</p> <ul> <li> <p>使用系统的布局缺点是太繁琐,没条约束都要整这么一堆东西。优点:亲生的。</p> </li> <li> <p>使用VFL语言呢,苹果为了简化手写Autolayout代码所创建的专门负责编写约束的代码。缺点是:学习需要成本啊。并且不支持乘除。</p> </li> <li> <p>使用masonry基于系统约束的封装,代码简单易懂。需要引入第三方库。</p> </li> <li> <p>SDAutolayout 目前没使用,不做评判。</p> </li> </ul> <p>这篇文章只是讲一下masonry 的简单使用,现在笔者所在公司就在使用masonry自动布局。所以,对这块还是稍有了解的。</p> <h2>Masonry简单使用</h2> <p>ok,下面开始masonry用法简单介绍。首先我们应该看他提供给我们那些属性和方法让我们来调用。</p> <p>Masonry属性有哪些?</p> <pre> <code class="language-objectivec"> MASConstraint *left;左边距 MASConstraint *top;上边距 MASConstraint *right;右边距 MASConstraint *bottom;底部 MASConstraint *leading;首部 MASConstraint *trailing;尾部 MASConstraint *width;宽 MASConstraint *height;高 MASConstraint *centerX;横向中点 MASConstraint *centerY;纵向中点 </code></pre> <p>说明一下:其中leading与left trailing与right 在正常情况下是等价的</p> <p>一些不容易理解的点:</p> <p>centerY 纵向中点</p> <p>centerX 横向中点</p> <p>right 右边距一般为负数</p> <p>bottom 同理 距离底部的距离</p> <h2>约束的使用</h2> <p>添加约束:</p> <pre> <code class="language-objectivec">- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block; </code></pre> <p>更改约束:</p> <pre> <code class="language-objectivec">- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block; </code></pre> <p>重置约束:</p> <pre> <code class="language-objectivec">- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block; </code></pre> <p>顾名思义:添加约束是:</p> <pre> <code class="language-objectivec">mas_makeConstraints </code></pre> <p>就是说我们对一个视图进行添加约束。</p> <p>更新约束:</p> <pre> <code class="language-objectivec">mas_updateConstraints </code></pre> <p>这里需要注意的是更新的话是对于原来约束的基础上来说的。如果原来没有你update的约束的话就会add一个约束。原则上来说,这里做的只是在原有约束的基础上的改变。一般不更新没有的约束。</p> <p>重置约束:</p> <pre> <code class="language-objectivec">mas_remakeConstraints </code></pre> <p>重置约束顾名思义就是把之前的约束全部删掉,然后重新添加约束。这和make相比就多了一步,就是把已经存在的约束remove掉 重新添加约束。</p> <p>其对应在masonry 中的源代码是这样的:</p> <pre> <code class="language-objectivec">- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { self.translatesAutoresizingMaskIntoConstraints = NO; MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; block(constraintMaker); return [constraintMaker install]; } - (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block { self.translatesAutoresizingMaskIntoConstraints = NO; MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; constraintMaker.updateExisting = YES; block(constraintMaker); return [constraintMaker install]; } - (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block { self.translatesAutoresizingMaskIntoConstraints = NO; MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; constraintMaker.removeExisting = YES; block(constraintMaker); return [constraintMaker install]; } </code></pre> <p>相信这三段代码是很容易理解的吧。add就是新添加。update 就是更新已经存在的约束。remake 就是把存在的约束remove掉 然后执行 make。</p> <p>ok,下面就进入实战吧。实战提高的唯一途径。想更好的理解就去使用。</p> <p>下面就通过简单的例子来向大家介绍Masonry对应属性的用法。</p> <p>在介绍用法之前 先看下 masonry 的语法。</p> <p>我们如果要对一个view添加约束的话我们需要这样来写</p> <pre> <code class="language-objectivec"> UIView *redView = [UIView new]; [self.view addSubview:redView]; [redView mas_makeConstraints:^(MASConstraintMaker *make) { // add the Constraints redView needed }]; </code></pre> <p>在添加约束之前我们要确定我们需要加约束的redView 是否存在 是否在已经被添加到父视图。 也就是说我们要保证我们的redView 存在。只有存在的东西我们才能对其添加约束。 如果没有addsubview 就添加约束就会崩溃。就相当于我们让一个不存在的对象执行方法一样,结果肯定会报错。会crash。</p> <p>看下面这段代码:</p> <pre> <code class="language-objectivec"> [redView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.view.mas_top).with.offset(20); make.top.and.right.equalTo(self.view).with.offset(0); make.bottom.equalTo(self.view.mas_bottom).with.offset(-20); }]; </code></pre> <p>这段代码的意思是:创建的redview 的左边相对于self.view的左边的距离是20. 其中的left 指的是视图的左边equalto 就是等于。()里就是相对于xxx 这里写的是self.view 其实指的是self.view.mas_top .</p> <p>with 在这里没有什么特别的意思.offset 指的是偏移量。拿第一行代码来说 创建redview 的左边距 相对于 self.view 的左边距的偏移量是20。</p> <p>and 可以连接两个约束,如果两个约束相对视图相同并且偏移量也相同的话这样玩是没有问题的。关于with和and 可以看源码:</p> <pre> <code class="language-objectivec">- (MASConstraint *)with { return self; } - (MASConstraint *)and { return self; } </code></pre> <p>其实这两个方法什么都没做,但是用在这种链式语法中 就非常的巧妙和易懂 (我现在基本都会省略)</p> <p>现在如果要我写的话我会这样写:</p> <pre> <code class="language-objectivec"> [view mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(20); make.top.right.mas_equalTo(0); make.bottom.mas_equalTo(-20); }]; </code></pre> <p>看到这里你不禁会问 上面你用的equalto 这里为什么用mas_equalTo了呢? 其实这个问题提的很好。</p> <p>下面会解释一些这样写的原因.</p> <p>先看代码:</p> <pre> <code class="language-objectivec">#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__))) #define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__))) #define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__))) #define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__))) #ifdef MAS_SHORTHAND_GLOBALS #define equalTo(...) mas_equalTo(__VA_ARGS__) #define greaterThanOrEqualTo(...) mas_greaterThanOrEqualTo(__VA_ARGS__) #define lessThanOrEqualTo(...) mas_lessThanOrEqualTo(__VA_ARGS__) #define offset(...) mas_offset(__VA_ARGS__) #endif </code></pre> <p>我们可以看到:mas_equalTo只是对其参数进行了一个BOX(装箱) 操作,目前支持的类型:数值类型(NSNumber)、 点(CGPoint)、大小(CGSize)、边距(UIEdgeInsets),而equalTo:这个方法不会对参数进行包装。</p> <p>其实上面对于redView的约束还可以这样来写。</p> <pre> <code class="language-objectivec"> [redView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.insets (UIEdgeInsetsMake(0, 20, 20, 0)); }]; </code></pre> <p>edges就是边距 距离(top,left,bottom.right)</p> <pre> <code class="language-objectivec">- (MASConstraint *)edges { return [self addConstraintWithAttributes:MASAttributeTop | MASAttributeLeft | MASAttributeRight | MASAttributeBottom]; } </code></pre> <p>这样几行代码同样能达到相同的效果。 ok,以上的代码纯粹是为了让大家明白怎么来写约束。以及约束的完整写法和简单写法。下面就开始实战吧,首先,咱们从简单做起。</p> <p>实现一:</p> <p>我们需要创建一个view 视图宽高固定都为50 距离顶部距离是100 距离父视图左边距离是10.</p> <p>ok下面我们来实现:</p> <pre> <code class="language-objectivec"> UIView *blueView = [UIView new]; [blueView showPlaceHolder]; blueView.backgroundColor = [UIColor blueColor]; [self.view addSubview:blueView]; [blueView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.view.mas_left).with.offset(10); make.top.equalTo(self.view.mas_top).offset(100); make.width.mas_equalTo(@50); make.height.mas_equalTo(50); }]; </code></pre> <p>效果图 <img src="https://simg.open-open.com/show/4cd8d1e59b1edbda3e2b98be456bb51b.png"> 图中蓝色的view 就是我们所要创建的目的view</p> <p>分析: 如果是相对于父视图的话 我们可以这样写</p> <pre> <code class="language-objectivec">make.top.mas_equalTo(10); </code></pre> <p>left 和top 的值 如果 相对的是一个视图的left 和top 并且偏移量是一样的话 我们可以这样写</p> <pre> <code class="language-objectivec">// make.left.and.top.mas_equalTo(10); 我们的这个and 可以省略掉(上文提过这个) make.left.top.mas_equalTo(10); </code></pre> <p>make.width 和make.height 等价于</p> <pre> <code class="language-objectivec">make.size.mas_equalTo(CGSizeMake(50, 50)); </code></pre> <p>其实我们在对这个view 加约束的时候我们可以发现我们添加了四个约束。这四个约束已经可以把这view 固定了。自动布局 不同于frame 之处在于,我们写的是相对约束。我们想要固定一个视图只需要对view添加约束,然后能把view 相对位置找到就ok,就像我之前写的我理解的自动布局。(纯属个人理解)</p> <pre> <code class="language-objectivec">这里姑且把我们的手机或者模拟器比作一个竖直方向放置的的黑板,我们的控件就相当于放在黑板上的一件东西,它可以用绳子固定在上面。我所理解的加约束就是:有一个物体悬空放到黑板上与黑板接触然后用绳子能固定住,绳子不能用多也不能用少。用得多了浪费并且还可能会产生冲突。用得少了,不能正确的固定位置然后还可能导致在你移动的时候可能会找不到位置(就是说这个物体在你的约束下不见了)。 </code></pre> <p>实现二:</p> <ul> <li>centerX举例</li> </ul> <pre> <code class="language-objectivec"> UIView *orangeView = [UIView new]; [orangeView showPlaceHolder]; orangeView.backgroundColor = [UIColor orangeColor]; [self.view addSubview:orangeView]; [orangeView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(0); make.top.equalTo(blueView.mas_top); make.size.equalTo(blueView); }]; </code></pre> <ul> <li>centerY举例</li> </ul> <pre> <code class="language-objectivec"> UIView *yellowView = [UIView new]; [yellowView showPlaceHolder]; yellowView.backgroundColor = [UIColor yellowColor]; [self.view addSubview:yellowView]; [yellowView mas_makeConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(20); make.centerY.mas_equalTo(-50); make.left.equalTo(orangeView.mas_right).offset(5); make.right.mas_equalTo(-20); }]; </code></pre> <ul> <li>相对位置 通过centerY来举例</li> </ul> <pre> <code class="language-objectivec"> UIView *brownView = [UIView new]; [brownView showPlaceHolder]; [brownView setBackgroundColor:[UIColor brownColor]]; [self.view addSubview:brownView]; [brownView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(blueView); make.size.mas_equalTo(CGSizeMake(60, 60)); make.left.equalTo(blueView.mas_right).offset(10); }]; </code></pre> <ul> <li>center 举例</li> </ul> <pre> <code class="language-objectivec"> UIButton *redButton= [UIButton buttonWithType:UIButtonTypeCustom]; redButton.backgroundColor = [UIColor redColor]; [redButton showPlaceHolder]; [self.view addSubview:redButton]; [redButton mas_makeConstraints:^(MASConstraintMaker *make) { // make.centerX.mas_equalTo(0); // make.centerY.mas_equalTo(0); // 这两句等价于 make.center.mas_equalTo(0); make.size.mas_equalTo(CGSizeMake(50, 50)); }]; </code></pre> <ul> <li>Margin 举例</li> </ul> <pre> <code class="language-objectivec"> UIButton *greenButton = [UIButton new]; [greenButton setBackgroundColor: [UIColor greenColor]]; [greenButton showPlaceHolder]; [self.view addSubview:greenButton]; greenButton.tag = 1006; [greenButton addTarget:self action:@selector(buttonPressAction:) forControlEvents:UIControlEventTouchUpInside]; [greenButton mas_makeConstraints:^(MASConstraintMaker *make) { //Magain 的 make.rightMargin.mas_equalTo(-8); make.topMargin.mas_equalTo(20); make.size.mas_equalTo(CGSizeMake(30, 30)); }]; </code></pre> <p>效果图如下: <img src="https://simg.open-open.com/show/5fae4024a56ed661702772dc96ba935d.png"> 相信通过效果图你可以很清楚的看出centerx centery 以及center 的区别。 前面提到过centerx是横向中点。比较上图中 orangeview 和redview 便可以看出区别。orangeview 和redview 的centerx 是相同的。当然你可以根据需要来调整centerx 的偏移量来调整视图的位置。对于centerx而言,左边就是负值 右边就是正值。 看下图: <img src="https://simg.open-open.com/show/123b1b76b3c36c0916a752becfa50d10.png"></p> <p>我们在写约束的偏移量的时候一定要注意,约束是相对于哪个view,只有找好相对关系,才能设置正确的约束。一般而言,有这句口诀左负有正,上正下负。这句口诀是相对而言的,跟视图的相对位置是有关系的。 比如说yellowview的centery在self.view 的centery的上面 所以就有了 make.centerY.mas_equalTo(-50);</p> <pre> <code class="language-objectivec"> 总之,添加约束找相对值就一句话,把哪个view当做参照物,这个view对应的约束就是正值,对于参考系而言也就遵循了这句口诀:左负有正,上正下负。 </code></pre> <p>关于Margin</p> <p>magain是iOS8.0以后才出现的。UIView默认的layoutMargins的值为 {8, 8, 8, 8}。在iOS 8中,可以使用layoutMargins去定义view之间的间距,该属性只对AutoLayout布局生效。在我们改变View的layoutMargins这个属性时,会触发</p> <pre> <code class="language-objectivec">- (void)layoutMarginsDidChange </code></pre> <p>这个方法。我们在自己的View里面可以重写这个方法来捕获layoutMargins的变化。在大多数情况下,我们可以在这个方法里触发drawing和layout的Update。</p> <p>用处: 我们可以通过修改这个值来改变View之间的距离。</p> <p>实现3: 循环创建视图:</p> <p>我们先看实现的效果图然后再来分析是如何实现的。</p> <p>效果图:</p> <p><img src="https://simg.open-open.com/show/6d57847f0d400e7aae26d41e52f0983f.png"></p> <pre> <code class="language-objectivec"> UIButton *tempButton; for (NSInteger i = 0; i < 5; i ++) { UIButton *contentButton = [UIButton buttonWithType:UIButtonTypeCustom]; contentButton.backgroundColor = [UIColor purpleColor]; contentButton.tag = 1000 + i; [contentButton setTitle:[NSString stringWithFormat:@"第%ld个",(long)i+1] forState:UIControlStateNormal]; [self.view addSubview:contentButton]; [contentButton mas_makeConstraints:^(MASConstraintMaker *make) { make.size.mas_equalTo(CGSizeMake(50, 30)); if (tempButton) { make.left.equalTo(tempButton.mas_right).offset(10); make.bottom.equalTo(tempButton.mas_bottom).offset(-10); } else { make.left.mas_equalTo(10); make.bottom.mas_equalTo(-10); } }]; tempButton = contentButton; } </code></pre> <p>如果我们想要循环创建view 的话,我们需要找到的就是一个相对值。首先,我们可能考虑到我们在创建第一个视图的时候是相对于哪个视图,我们创建第二个视图的时候相对视图又是谁。所以,为了实现我们想要找到的相对视图。我们在这里引用了一个tempView这个view在第一次创建的时候是不存在的,所以我们就创建了一个view。然后对我们第一个视图进行添加约束。第二次的时候我们已经有了tempView也就是说我们已经有了参照值就可以对其进行添加约束。以此类推。 我们可以参考下图: <img src="https://simg.open-open.com/show/15220247111515538f6cec2e7bc106fe.png"></p> <p>通过上面的图我们可以看到:就是说我们在循环创建view的时候需要一个参照,所以我们引入一个tempview; 当我们第一次创建的时候 tempview不存在,所以我们contentview的约束就是相对于父视图了。 创建完之后我们把创建的contentview赋给 tempview, 于是乎,tempview 翻身农奴把歌唱,苦日子终于熬到头了,小三终于要当上正房了。现在有了tempview 当我们第二次进来的时候tempview已经存在了,我们创建的contentview 就有了参照物了。然后在循环体内不停的把contentview的值赋tempview 除了第一次,每次进来的时候都会有tempview 找到参照物,再加约束还不是收到擒来。。。</p> <p>demo地址在这里 <a href="/misc/goto?guid=4959672259584230535" rel="nofollow,noindex">我是传送门</a></p> <p> </p> <p>来自: <a href="/misc/goto?guid=4959672259669273825" rel="nofollow">http://shavekevin.com/2016/05/03/masonrypartone/</a></p>