IOS不要AutoLayout也能实现自动布局的类(3)-MyRelativeLayout横空出世

IOS不用AutoLayout也能实现自动布局的类(3)----MyRelativeLayout横空出世

      对于IOS开发者来说,在自动布局出现前只能通过计算和设置frame的值来处理,这样设置位置时就会出现很多硬编码,同时在屏幕旋转和不同屏幕之间适配时需要编码重新调整位置和尺寸,我们也可以重载视图的layoutSubviews的函数来写代码重新布局。自动布局出现后确实在一定程度上解决了位置和尺寸硬编码的问题,但是通过代码来写自动布局非常的复杂和麻烦,而且代码量会增加很多。在自动布局领域android系统通过提供FrameLayout, LinearLayout, RelativeLayout, AbsoluteLayout等几个类来分别处理各种不同的布局需求,通过wrap_content,match_parent来自动计算尺寸。

     android系统的FrameLayout类用于进行上下左右居中填充方式的布局,而LinearLayout则是用于进行水平和垂直方向的流式布局,AbsoluteLayout则是硬编码方式的绝对布局。在我前面的2篇文章中分别介绍了MyFrameLayout, MyLinearLayout两种方式的布局,而这章我将继续介绍相对布局MyRelativeLayout.

       所谓相对布局就是指某个视图的位置和尺寸不是固定写死的而是依赖于其他关联的视图,比如一个视图在另外一个视图的左边,或者在另外一个视图的右下方,或者一个视图的宽度和另外一个视图宽度是相等的,或者视图是在父视图的顶部偏移一定的量,或者某一组视图的宽度要平分父视图等等功能。因此我们分别为子视图定义了如下的扩展属性:


@interface UIView(MyRelativeLayoutEx)


//位置

@property(nonatomic,readonlyMyRelativePos *leftPos;

@property(nonatomic,readonlyMyRelativePos *topPos;

@property(nonatomic,readonlyMyRelativePos *rightPos;

@property(nonatomic,readonlyMyRelativePos *bottomPos;

@property(nonatomic,readonlyMyRelativePos *centerXPos;

@property(nonatomic,readonlyMyRelativePos *centerYPos;


//尺寸

@property(nonatomic,readonlyMyRelativeDime *widthDime;

@property(nonatomic,readonlyMyRelativeDime *heightDime;


@end


分别来定义视图的左,上,右,下,水平中心,垂直中心,宽度,高度8个方位和尺寸的相对依赖对象,其中MyRelativePos用来定义相对的依赖位置类。它的定义如下:

@interface MyRelativePos :NSObject

//偏移

-(MyRelativePos* (^)(CGFloat val))offset;

//NSNumber, MyRelativePos对象

-(MyRelativePos* (^)(id val))equalTo;


@end


这个类中的offset用来指定某个位置的偏移值,而equalTo则用来指定依赖的某个位置和值,比如:

1.A视图的左边等于B视图的右边并偏移30个点: A.leftPos.equalTo(B.rightPos).offset(30)

2.A视图的顶部和父视图的顶部相等:A.topPos.equalTo(A.superView.topPos)

3.A视图的中间在父视图的中间:   A.centerXPos.equalTo(A.superView.centerXPos); A.centerYPos.equalTo(A.superView.centerYPos)

4.A视图的左边偏移20: A.offset(20)


而对于相对尺寸则定义了MyRelativeDime类来定义宽度和高度,这个类的定义如下:

@interface MyRelativeDime :NSObject


//

-(MyRelativeDime* (^)(CGFloat val))multiply;

//,用这个和equalTo的数组功能可以实现均分子视图宽度以及间隔的设定。

-(MyRelativeDime* (^)(CGFloat val))add;



//NSNumber, MyRelativeDime以及MyRelativeDime数组,数组的概念就是所有数组里面的子视图的尺寸平分父视图的尺寸。

-(MyRelativeDime* (^)(id val))equalTo;


@end


尺寸定义中equalTo用来指定尺寸的值,可以是NSNumber型指定绝对的尺寸,MyRelativeDime型指定相对的尺寸,NSArray[MyRelativeDime]来指定按比例分配父视图的尺寸,mutiply用来指定在equalTo上指定的值的倍数比例,add指定在equalTo上指定的值的增量值(add方法更多是用来指定间距)。

1.A视图的宽度等于B视图的宽度,A视图的高度等于B视图的高度的一半:  A.widthDime.equalTo(B.widthDime); A.heightDime.equalTo(B.heightDime).multiply(0.5)

2.A视图的高度等于B视图的高度,并增加20:   A.heightDime.equalTo(B.heightDime).add(20)

3.A,B,C三个视图平分父视图的宽度:  A.widthDime.equalTo(@[B.widthDime, C.widthDime])

4.A视图固定宽度为20, B,C视图按4:6平分剩余的宽度:   A.widthDime.equal(@20)  B.widthDime.equalTo(@[A.widthDime, C.widthDime.multiply(0.6)]).multiply(0.4)


好了介绍了上述扩展的子视图的扩展属性后,我们需要的只是建立一个MyRelativeLayout布局视图,然后设置好子视图之间的相对依赖,然后添加进去就OK了。


一、相对布局的子视图的依赖

IOS不要AutoLayout也能实现自动布局的类(3)-MyRelativeLayout横空出世

上述的布局就是用相对布局实现,代码很简单,请参考如下:
-(void)loadView
{
    
    MyRelativeLayout *rl = [MyRelativeLayout new];
    rl.padding = UIEdgeInsetsMake(10, 10, 10, 10);
    rl.backgroundColor = [UIColor grayColor];
    self.view = rl;
    
    UILabel *lb1 = [UILabel new];
    [rl addSubview:lb1];
    lb1.text = @"你好";
    [lb1 sizeToFit];
    lb1.backgroundColor = [UIColor blueColor];
   
    
    lb1.leftPos.equalTo(rl.leftPos); //和父视图左边一致
    lb1.topPos.equalTo(rl.topPos).offset(10); //和父视图顶部一致并偏移10
    lb1.widthDime.equalTo(@60); //固定宽度
    
    UILabel *lb2 = [UILabel new];
    [rl addSubview:lb2];
    lb2.text = @"我好 hello";
    lb2.backgroundColor = [UIColor redColor];
    
    lb2.leftPos.equalTo(lb1.rightPos);
    lb2.topPos.equalTo(lb1.bottomPos);
    lb2.widthDime.equalTo(lb1.widthDime).add(30); //宽度是lb1的宽度加30
    lb2.heightDime.equalTo(lb1.heightDime).multiply(2).add(-10); //高度是lb1高度的2倍再-10
    
    UILabel *lb3 = [UILabel new];
    lb3.text = @"中间";
    lb3.backgroundColor = [UIColor greenColor];
    [rl addSubview:lb3];
    
    lb3.centerXPos.equalTo(rl.centerXPos);
    lb3.centerYPos.equalTo(rl.centerYPos);
    lb3.widthDime.equalTo(rl.widthDime).multiply(0.2);
    lb3.heightDime.equalTo(rl.heightDime).multiply(0.1);
    
    UILabel *lb4 = [UILabel new];
    lb4.text = @"他好";
    lb4.backgroundColor = [UIColor orangeColor];
    [rl addSubview:lb4];
    
    //宽度和高度由左右决定
    lb4.leftPos.equalTo(rl.leftPos);
    lb4.rightPos.equalTo(rl.rightPos);
    lb4.topPos.equalTo(@100);
    lb4.bottomPos.equalTo(@120);
   
}

这段代码中请注意lb4的设置,我们会发现当我们同时指定了左右和上下的依赖视图时,宽度和高度就不需要指定出来,布局会自动算出高度和宽度。注意代码中我们还为相对布局指定了padding的值,表示里面的所有子视图都会在padding之内。


二、相对布局的子视图的平均分配
 
有时候我们的布局的有些子视图希望能按父视图的尺寸来进行按某些规则进行分配,比如下面的布局:


IOS不要AutoLayout也能实现自动布局的类(3)-MyRelativeLayout横空出世


上面的视图中,第一排的3个子视图平分父视图的宽度。第二排子视图中第一个视图宽度固定,剩余的两个平分。第三排的子视图按0.2 0.3 0.5的比例来平分父视图,代码如下:

-(void)loadView
{
    MyRelativeLayout *rl = [MyRelativeLayout new];
    rl.padding = UIEdgeInsetsMake(0, 0, 0, 10);
    rl.backgroundColor = [UIColor grayColor];
    self.view = rl;
    
    /**水平平分3个子视图**/
    UIView *v1 = [UIView new];
    v1.backgroundColor = [UIColor redColor];
    v1.heightDime.equalTo(@40);
    [rl addSubview:v1];
    
    UIView *v2 = [UIView new];
    v2.backgroundColor = [UIColor redColor];
    v2.heightDime.equalTo(@40);
    [rl addSubview:v2];

    UIView *v3 = [UIView new];
    v3.backgroundColor = [UIColor redColor];
    v3.heightDime.equalTo(@40);
    [rl addSubview:v3];

    //v1,v2,v3平分父视图的宽度。在平分前减去了30用作间距
    v1.widthDime.equalTo(@[v2.widthDime.add(-10), v3.widthDime.add(-10)]).add(-10);
    v1.leftPos.offset(10);
    v2.leftPos.equalTo(v1.rightPos).offset(10);
    v3.leftPos.equalTo(v2.rightPos).offset(10);
    

    /**某个视图固定其他平分**/
    UIView *v4 = [UIView new];
    v4.backgroundColor = [UIColor greenColor];
    v4.topPos.equalTo(v1.bottomPos).offset(80);
    v4.heightDime.equalTo(@40);
    v4.widthDime.equalTo(@260); //第一个视图宽度固定
    [rl addSubview:v4];
    
    UIView *v5 = [UIView new];
    v5.backgroundColor = [UIColor greenColor];
    v5.topPos.equalTo(v4.topPos);
    v5.heightDime.equalTo(@40);
    [rl addSubview:v5];
    
    UIView *v6 = [UIView new];
    v6.backgroundColor = [UIColor greenColor];
    v6.topPos.equalTo(v4.topPos);
    v6.heightDime.equalTo(@40);
    [rl addSubview:v6];
    
    //v1,v2,v3平分父视图的宽度。在平分前减去了30用作间距
    v5.widthDime.equalTo(@[v4.widthDime.add(-10), v6.widthDime.add(-10)]).add(-10);
    v4.leftPos.offset(10);
    v5.leftPos.equalTo(v4.rightPos).offset(10);
    v6.leftPos.equalTo(v5.rightPos).offset(10);
    
 
    
    /**子视图按比例平分**/
    UIView *v7 = [UIView new];
    v7.backgroundColor = [UIColor blueColor];
    v7.topPos.equalTo(v4.bottomPos).offset(80);
    v7.heightDime.equalTo(@40);
    [rl addSubview:v7];
    
    UIView *v8 = [UIView new];
    v8.backgroundColor = [UIColor blueColor];
    v8.topPos.equalTo(v7.topPos);
    v8.heightDime.equalTo(@40);
    [rl addSubview:v8];
    
    UIView *v9 = [UIView new];
    v9.backgroundColor = [UIColor blueColor];
    v9.topPos.equalTo(v7.topPos);
    v9.heightDime.equalTo(@40);
    [rl addSubview:v9];
    
    v7.widthDime.equalTo(@[v8.widthDime.multiply(0.3).add(-10),v9.widthDime.multiply(0.5).add(-10)]).multiply(0.2).add(-10);
    v7.leftPos.offset(10);
    v8.leftPos.equalTo(v7.rightPos).offset(10);
    v9.leftPos.equalTo(v8.rightPos).offset(10);
    
    //请分别设置每个视图.hidden = YES 并且设置布局的@property(nonatomic, assign) BOOL flexOtherViewWidthWhenSubviewHidden为YES和NO的效果

}

看代码我们发现,在分配视图时指定了视图之间的间距这需要借助offset的调用来指定间距,因为是均分视图我们又需要为视图的宽度留有间隔,因此我们需要借助add的方法来将计算出的宽度减去间距的值,而同时我们为布局视图的padding的值,我们设置了10的间距来控制最右边的间距为10。

  有的时候我们在均分子视图时,当某个子视图隐藏时其他的剩余的子视图的宽度会进行调整,比如某个子视图设置为隐藏后,右边的子视图向左边靠拢。而有的时候我们希望当某个子视图隐藏时,剩余的部分重新填充慢布局视图的某个方位的尺寸,因此我们可以为布局视图设置开关:

//均分宽度时当有隐藏子视图,是否参与宽度计算,这个属性只有在参与均分视图的子视图隐藏时才有效,默认是NO

@property(nonatomic,assign) BOOL flexOtherViewWidthWhenSubviewHidden;


//均分高度时当有隐藏子视图,是否参与高度计算,这个属性只有在参与均分视图的子视图隐藏时才有效,默认是NO

@property(nonatomic,assign) BOOL flexOtherViewHeightWhenSubviewHidden;



这两个布局视图的属性分别标明当某个子视图数组均分父视图时,而其中某个子视图隐藏时,是否其他视图会重新分配宽度和高度。

三、相对布局的高宽由子视图决定

我在线性布局的文章中有说明可以通过wrapContent来决定是否布局视图的非方向是否由子视图来决定。这时候我们就不需要手动的指定布局视图的高度和宽度,而是由布局视图里面的子视图来决定布局的尺寸,在android系统中我们可以设置matchParent, wrapContent来设置布局视图的尺寸。同样我们在布局中也分别提供了四个属性:


@property(nonatomic,assign)CGFloat matchParentWidth;

@property(nonatomic,assign)CGFloat matchParentHeight;


@property(nonatomic,assign) BOOL wrapContentWidth;

@property(nonatomic,assign) BOOL wrapContentHeight;



从上面的定义可以看出,matchParentWidth, matchParentHeight是用来指定布局视图的高度和宽度和其父视图的高度一致,或者是父视图的某个比例值,而wrapContentWidth, wrapContentHeight则是指定布局视图的宽度和高度由子视图决定。 需要注意的是matchParentWidth, wrapContentWidth是不能同时指定的,因为这会造成约束冲突。

我们这里只介绍wrapContent,如果想了解matchParentWidth请参考我的关于MyLinearLayout方面的文章。

我们先看结果界面:

 IOS不要AutoLayout也能实现自动布局的类(3)-MyRelativeLayout横空出世
-(void)loadView
{
    [super loadView];
    
    MyRelativeLayout *rl = [[MyRelativeLayout alloc] initWithFrame:CGRectMake(10, 10, 0, 0)];
    [self.view addSubview:rl];
    rl.wrapContentWidth = YES;
    rl.wrapContentHeight = YES; //设置宽度和高度由所有子视图包裹
    rl.backgroundColor = [UIColor grayColor];
    
    UILabel *lb1 = [UILabel new];
    lb1.leftPos.equalTo(rl.leftPos).offset(20);
    lb1.text = @"aaaa";
    lb1.backgroundColor = [UIColor redColor];
    [lb1 sizeToFit];
    lb1.rightPos.offset(20);
    
    [rl addSubview:lb1];
    
    
    UILabel *lb3 = [UILabel new];
    lb3.rightPos.equalTo(rl.rightPos).offset(5); //虽然这时候父视图的宽度为0,但还是可以设置离父视图的距离
    lb3.topPos.equalTo(rl.topPos).offset(30);
    lb3.bottomPos.offset(10);
    lb3.text = @"ccc";
    lb3.backgroundColor = [UIColor redColor];
    [lb3 sizeToFit];
    
    [rl addSubview:lb3];
    
    
    UILabel *lb2 = [UILabel new];
    lb2.text = @"bbbb";
    lb2.backgroundColor = [UIColor blueColor];
    
    lb2.leftPos.equalTo(lb1.centerXPos);
    lb2.topPos.equalTo(lb1.bottomPos).offset(40);
    lb2.widthDime.equalTo(@50);
    lb2.heightDime.equalTo(@50);
    lb2.bottomPos.offset(40);

   
    [rl addSubview:lb2];
    
    
    
}

上面的代码中我们可以看到布局视图是没有指定高度和宽度的,而是设置了属性wrapContentWidth = YES, wrapContentHeight = YES

那么布局视图的高度和宽度是怎么计算出来的呢。我们是通过计算出所有子视图的位置和尺寸的最大高度和宽度来得到布局视图的高度和宽度的,在上面的代码中我们看到lb3的右边和布局视图的右边相差5,但是布局视图这时候的宽度是没有计算出来的,但是我们还是可以这样设置,因为lb1, lb2的尺寸和高度已经把布局视图撑开到足够的高度和宽度了。

同时通过wrapContentXXX的布局视图的属性我们可以动态的调整布局视图本身的高度和宽度,因此我们也很适合将布局视图放入到一个UIScrollView中去。因此我们也跟MyLinearLayout一样为相对布局视图提供了一个属性:

//如果布局的父视图是UIScrollView或者子类则在布局的位置调整后是否调整滚动视图的contentsize,默认是NO

//这个属性适合与整个布局作为滚动视图的唯一子视图来使用。

@property(nonatomic,assign, getter = isAdjustScrollViewContentSize)BOOL adjustScrollViewContentSize;


用这个属性来指定当相对视图放入到UIScrollView中时,是否会根据自身的高度和宽度的变化来动态调整UIScrollView的contentSize的值。

四、总结

 好了,相对视图的介绍就布局到这里了,到这里我分别为你介绍了框架布局,线性布局和相对布局方面的东西,通过这三个布局的使用我们完全可以摆脱对IOS的自动布局和sizeClass的使用,而用更加简单清晰的方法来布局您的界面,希望我的库能对您提供非常有利的帮助,如果您需要用我的库来编码,那么就请到:

https://github.com/youngsoft/MyLinearLayout 中下载代码和DEMO吧。




版权声明:本文为博主原创文章,未经博主允许不得转载。