Masonry布局源码精讲(三) -- 链式布局

链式布局

之前我们分析了如何实现链式编程,并且大致了解了链式编程思想在开发中的好处。通过这篇文章,我们来看一下Masonry作为经典的链式编程思想是如何设计的,我们拭目以待。

Masonry布局设置
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}

- (MASConstraint *)right {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}

- (MASConstraint *)bottom {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}

- (MASConstraint *)leading {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeading];
}

- (MASConstraint *)trailing {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailing];
}

- (MASConstraint *)width {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}

- (MASConstraint *)height {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}

- (MASConstraint *)centerX {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX];
}

- (MASConstraint *)centerY {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterY];
}

- (MASConstraint *)baseline {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBaseline];
}

这里面提供了上下左右,长宽和中心位置等的定位方法。我们不难发现这些方法提供了MASConstraint返回值,这样就可以调用MASConstraint的成员属性。

@interface MASConstraintMaker : NSObject

/**
 *	The following properties return a new MASViewConstraint
 *  with the first item set to the makers associated view and the appropriate MASViewAttribute
 *  布局设置制造者 定义了left,top,right等属性成员
 */
@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
@property (nonatomic, strong, readonly) MASConstraint *width;
@property (nonatomic, strong, readonly) MASConstraint *height;
@property (nonatomic, strong, readonly) MASConstraint *centerX;
@property (nonatomic, strong, readonly) MASConstraint *centerY;
@property (nonatomic, strong, readonly) MASConstraint *baseline;

@property (nonatomic, strong, readonly) MASConstraint *firstBaseline;
@property (nonatomic, strong, readonly) MASConstraint *lastBaseline;

#if TARGET_OS_IPHONE || TARGET_OS_TV

@property (nonatomic, strong, readonly) MASConstraint *leftMargin;
@property (nonatomic, strong, readonly) MASConstraint *rightMargin;
@property (nonatomic, strong, readonly) MASConstraint *topMargin;
@property (nonatomic, strong, readonly) MASConstraint *bottomMargin;
@property (nonatomic, strong, readonly) MASConstraint *leadingMargin;
@property (nonatomic, strong, readonly) MASConstraint *trailingMargin;
@property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;

#endif

/**
 *  Returns a block which creates a new MASCompositeConstraint with the first item set
 *  to the makers associated view and children corresponding to the set bits in the
 *  MASAttribute parameter. Combine multiple attributes via binary-or.
 */
@property (nonatomic, strong, readonly) MASConstraint *(^attributes)(MASAttribute attrs);

/**
 *	Creates a MASCompositeConstraint with type MASCompositeConstraintTypeEdges
 *  which generates the appropriate MASViewConstraint children (top, left, bottom, right)
 *  with the first item set to the makers associated view
 */
@property (nonatomic, strong, readonly) MASConstraint *edges;

/**
 *	Creates a MASCompositeConstraint with type MASCompositeConstraintTypeSize
 *  which generates the appropriate MASViewConstraint children (width, height)
 *  with the first item set to the makers associated view
 */
@property (nonatomic, strong, readonly) MASConstraint *size;

/**
 *	Creates a MASCompositeConstraint with type MASCompositeConstraintTypeCenter
 *  which generates the appropriate MASViewConstraint children (centerX, centerY)
 *  with the first item set to the makers associated view
 */
@property (nonatomic, strong, readonly) MASConstraint *center;

从这可以证明我们作为链式操作的方法,通过对成员属性的方法进行重写,就可以按照自己的方式去链式调用。多种布局方式都是调用addConstraintWithLayoutAttribute实现的。

-(MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

传入的布局属性,传入的属性是系统定义的枚举法。

NSLayoutAttributeLeft = 1,
NSLayoutAttributeRight,
NSLayoutAttributeTop,
NSLayoutAttributeBottom,
NSLayoutAttributeLeading,
NSLayoutAttributeTrailing,
NSLayoutAttributeWidth,
NSLayoutAttributeHeight,
NSLayoutAttributeCenterX,
NSLayoutAttributeCenterY,
NSLayoutAttributeLastBaseline,

添加约束
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        //replace with composite constraint
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

在上面看到,传入的constraint是nil,因此,我们直接看这一部分。

if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
}
  1. 协议
  2. 添加的布局数组

第二步中的newConstraint是如何定义的,我们来看一下初始化的方法。

MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];

下面方法提供了MASViewAttribute初始化视图的操作。

- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
    return self;
}

- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [super init];
    if (!self) return nil;
    
    /* _view赋值view _item也是view 布局属性layoutAttribute */
    _view = view;
    _item = item;
    _layoutAttribute = layoutAttribute;
    
    return self;
}

保存了传入的视图(这里就是MASConstraintMaker定义的view)

@property (nonatomic, weak) MAS_VIEW *view;

布局属性是传入的系统的布局属性,这样就将视图和绑定的布局属性联系起来,最后,统一保存在constraints中。

- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
    self = [super init];
    if (!self) return nil;
    
    _firstViewAttribute = firstViewAttribute;
    self.layoutPriority = MASLayoutPriorityRequired;
    self.layoutMultiplier = 1;
    
    return self;
}

在初始化视图属性中,设置了优先级MASLayoutPriorityRequired。

static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;

初始化时,当约束的priority的值是UILayoutPriorityRequired (1000)时,是无法改变priority值而生效的。也就是说,想要使用优先级属性,就必须初始化一个不是1000的priority的约束

这样,每一次添加一个一个约束,都会保存在布局数组中。

equalTo OR mas_equalTo
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        /**
         * Relation -- 关系
         * 传入的属性,用Equal来使用
         * self.equalToWithRelation(attribute, NSLayoutRelationEqual)返回值为MASConstraint类型
         */
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

- (MASConstraint * (^)(id))mas_equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

我们来看一下,经常会用到的两种方法,这里面用到了Block块的返回值,和我们前面篇幅使用方法一致。

-(MASConstraint * (^)(id))
这一点说明了返回值MASConstraint类型,这样就可以继续使用点语法方式,传入的参数是id类型,这样说明,不论传入数值还是传入对象,都要是id类型的

在系统传统布局时候,我们会用到NSLayoutRelationEqual来设置参考,因此这里也是同样的操作。

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if ([attribute isKindOfClass:NSArray.class]) {
            NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
            NSMutableArray *children = NSMutableArray.new;
            for (id attr in attribute) {
                MASViewConstraint *viewConstraint = [self copy];
                viewConstraint.layoutRelation = relation;
                viewConstraint.secondViewAttribute = attr;
                [children addObject:viewConstraint];
            }
            MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
            compositeConstraint.delegate = self.delegate;
            [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
            return compositeConstraint;
        } else {
            NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}

首先,这个也是一个返回值Block块,传入的参数含有两个,一个是设置的attribute和关系,关系默认传入的是NSLayoutRelationEqual。
虽然代码很长,但是我们发现分为两部分,一部分是数组的操作,一部分是非数组的操作,我们先看else部分(去掉错误处理,核心部分仅仅只有三句)。

 self.layoutRelation = relation;
 self.secondViewAttribute = attribute;
 return self;

操作上,保存了关系和传入的属性,最后返回self,也就是说当前的self是携带着NSLayoutRelationEqual和传入的attribute继续使用链式编程,在最后才会调用实现。

思考: 如果attribute是数组该如何操作???
根据else中的代码,不难设计,可以使用forin来遍历attribute数组,逐一赋值,在利用一个数组存储,最后返回。

的确,if中的操作正是做了遍历操作。

 for (id attr in attribute) {
                MASViewConstraint *viewConstraint = [self copy];
                viewConstraint.layoutRelation = relation;
                viewConstraint.secondViewAttribute = attr;
                [children addObject:viewConstraint];
}

类比equalTo,mas_equalTo也就显得十分亲切了。
至此,我们已经分析了大部分的操作,并且对于链式操作也有了更深刻的认识。现在回顾一下Maker将链式操作作为Block是如何安装到目标视图上的。

Install 安装布局

还记得刚才分析的保存的内容吗,安装第一个步就先取出来这些保存的数据。

    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

这里我们之前看到了,就是用于安装布局,前面是forin循环,所以逐个进行布局设置。

    MASLayoutConstraint *layoutConstraint
        = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];

刚才设置的优先级默认为1000,这里面也进行保存。

layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;

所有这些安装完成后,我们最后刷新一下界面就大功告成了。

总结

到这里,恭喜你,Masonry已经分析完成了,我们做一个小小的总结。
作为链式编程经典范例,Masonry提供了至少两种的链式操作,一种是普通无Block返回的方法和含有Block操作的方法。在这里最值得关注的是Block里面嵌套Block,大的Block交给了Maker这个管理者,剩下的交给了left等的操作,最后实现大的Block块,进行逐一安装操作,这是一个非常有借鉴价值的设计思路。

中文源码下载

Masonry中文源码分析: GitHub.
根据MasonryBlock设置,小编写了一个关于Block的链式代码,如果您喜欢,记得给个小星星哦。(具体使用详情,在上一篇有所介绍,喜欢的小伙伴可以了解了解)
JQueryWeb一个神奇的框架.
JQueryWeb源码: JQueryWeb.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值