[关闭]
@flowyears 2016-12-20T02:05:41.000000Z 字数 4232 阅读 1620

详解自动布局(Masonry)实现九宫格

自动布局


以前写TimeLine中照片九宫格布局是直接计算frame,今天想用自动布局实现。

九宫格布局

使用自动布局,首先就必须知道给出了哪些条件。一般在TimeLine中照片九宫格布局给出的已知条件为:
1. 每个单元的宽cellWidth;
2. 每个单元的高cellHeight;
3. 每行有几个单元numPerRow;
4. 总共单元个数totalNum;
5. 每个单元与边界间距viewPadding;
6. 每个单元之间的间距viewPaddingCell。

图 1

上图是一个九宫格,黄色区域为父视图,由已知条件可知,它的大小是由里面的单元格布局决定的,所以,只要固定住里面的单元格,父视图就会自动固定住。
下面我们来添加约束:
1.所有单元格添加高度(height)和宽度(width)约束,如下图所示,

图 2

2.第一行相对父视图,添加top约束,如下图中紫色箭头所示,

图 3
3.非第一行添加对上一行单元的top约束,如下图红色箭头所示,

图 4

4.第一列添加对父视图的left约束,如下图墨绿色箭头所示

图 5

5.非第一列添加对上一个view的left约束,如下图深蓝色箭头所示

图 6

这时候你会发现所有单元格都固定了,但是父视图的大小却不能固定,因为还不能得出父视图的宽和高

6.右上角(第一行&最后一列)添加对父视图right约束,如下图所示,2号单元格右侧绿色箭头

图 7

7.左下角(最后一行&第一列)添加对父视图的bottom约束,如下图所示,6号单元格底部紫色箭头

图 8

talk is cheap show me the code.

doge.gif

  1. /**
  2. 九宫格布局(不限于九宫格,可以是N个格子),每个格子给定高(cellHeight)宽(cellWidth),
  3. 每行格子数量(numPerRow),格子总数量(totalNum),格子与边界距离(viewPadding),格
  4. 子之间的距离(viewPaddingCell)。
  5. @param cellWidth 格子宽度
  6. @param cellHeight 格子高度
  7. @param numPerRow 每行格子数量
  8. @param totalNum 格子总数量
  9. @param viewPadding 格子与边界距离
  10. @param viewPaddingCell 格子之间的距离
  11. @param superView 父视图
  12. */
  13. - (void)gridWithCellWidth:(CGFloat)cellWidth
  14. cellHeight:(CGFloat)cellHeight
  15. numPerRow:(NSInteger)numPerRow
  16. totalNum:(NSInteger)totalNum
  17. viewPadding:(CGFloat)viewPadding
  18. viewPaddingCell:(CGFloat)viewPaddingCell
  19. superView:(UIView *)superView
  20. {
  21. __block UILabel *lastView = nil;// 创建一个空view 代表上一个view
  22. __block UILabel *lastRowView;// 创建一个空view 代表上一行view
  23. __block NSInteger lastRowNo = 0;//上一行的行号
  24. NSMutableArray *cells = [[NSMutableArray alloc] init];
  25. for (int i = 0; i < totalNum; i++) {
  26. UILabel *aLabel = [UILabel new];
  27. aLabel.text = [NSString stringWithFormat:@"%d",i];
  28. [superView addSubview:aLabel];
  29. aLabel.backgroundColor = [UIColor colorWithHue:(arc4random() % 256 / 256.0 ) saturation:( arc4random() % 128 / 256.0 ) + 0.5
  30. brightness:( arc4random() % 128 / 256.0 ) + 0.5 alpha:1.0];
  31. [cells addObject:aLabel];
  32. }
  33. // 循环创建view
  34. for (int i = 0; i < cells.count; i++)
  35. {
  36. UILabel *lb = cells[i];
  37. BOOL isFirstRow = [self isFirstRowWithIndex:i numOfRow:numPerRow];
  38. BOOL isFirstCol = [self isFirstColumnWithIndex:i numOfRow:numPerRow];
  39. BOOL isLastCol = [self isLastColumnWithIndex:i numOfRow:numPerRow totalNum:totalNum];
  40. BOOL isLastRow = [self isLastRowWithIndex:i numOfRow:numPerRow totalNum:totalNum];
  41. NSInteger curRowNo = i/numPerRow;
  42. if (curRowNo != lastRowNo)
  43. {//如果当前行与上一个view行不等,说明换行了
  44. lastRowView = lastView;
  45. lastRowNo = curRowNo;
  46. }
  47. // 添加约束
  48. [lb mas_makeConstraints:^(MASConstraintMaker *make) {
  49. make.width.equalTo(@(cellWidth));
  50. make.height.equalTo(@(cellHeight));
  51. if (isFirstRow)
  52. {
  53. make.top.equalTo(superView.mas_top).with.offset(viewPadding);
  54. }
  55. else
  56. {
  57. if (lastRowView)
  58. {
  59. make.top.equalTo(lastRowView.mas_bottom).with.offset(viewPaddingCell);
  60. }
  61. }
  62. if (isFirstCol)
  63. {
  64. make.left.equalTo(superView.mas_left).with.offset(viewPadding);
  65. }
  66. else
  67. {
  68. if (lastView)
  69. {
  70. make.left.equalTo(lastView.mas_right).with.offset(viewPaddingCell);
  71. }
  72. }
  73. if (isFirstRow && isLastCol)
  74. {
  75. make.right.equalTo(superView.mas_right).with.offset(-viewPadding);
  76. }
  77. if (isLastRow && isFirstCol)
  78. {
  79. make.bottom.equalTo(superView.mas_bottom).with.offset(-viewPadding);
  80. }
  81. }];
  82. // 每次循环结束 此次的View为下次约束的基准
  83. lastView = lb;
  84. }
  85. }

代码中有一些判断,比如是否为第一行,

  1. /**
  2. 是否第一行
  3. @param index 当前下标
  4. @param numOfRow 每行个数
  5. @return YES OR NO
  6. */
  7. - (BOOL)isFirstRowWithIndex:(NSInteger)index numOfRow:(NSInteger)numOfRow
  8. {
  9. if (numOfRow != 0)
  10. {
  11. return index/numOfRow == 0;
  12. }
  13. return NO;
  14. }

是否为第一列,

  1. /**
  2. 是否第一列
  3. @param index 当前下标
  4. @param numOfRow 每行个数
  5. @return YES OR NO
  6. */
  7. - (BOOL)isFirstColumnWithIndex:(NSInteger)index numOfRow:(NSInteger)numOfRow
  8. {
  9. if (numOfRow != 0)
  10. {
  11. return index%numOfRow == 0;
  12. }
  13. return NO;
  14. }

是否为最后一行,

  1. /**
  2. 是否最后一行
  3. @param index 当前下标
  4. @param numOfRow 每行个数
  5. @return YES OR NO
  6. */
  7. - (BOOL)isLastRowWithIndex:(NSInteger)index numOfRow:(NSInteger)numOfRow totalNum:(NSInteger)totalNum
  8. {
  9. NSInteger totalRow = ceil(totalNum/((CGFloat)numOfRow));//总行数
  10. if (numOfRow != 0)
  11. {
  12. return index/numOfRow == totalRow - 1;
  13. }
  14. return NO;
  15. }

是否为最后一列

  1. /**
  2. 是否最后一列
  3. @param index 当前下标
  4. @param numOfRow 每行个数
  5. @return YES OR NO
  6. */
  7. - (BOOL)isLastColumnWithIndex:(NSInteger)index numOfRow:(NSInteger)numOfRow totalNum:(NSInteger)totalNum
  8. {
  9. if (numOfRow != 0)
  10. {
  11. if (totalNum < numOfRow)
  12. {//总数小于每行最大个数时,如果index是最后一个,那么也是最后一列
  13. return index == totalNum-1;
  14. }
  15. return index%numOfRow == numOfRow - 1;
  16. }
  17. return NO;
  18. }

注意:这里有个地方要注意,当你的单元格总数(totalNum )小于每行个数 (numOfRow),比如总共有2个单元格,每行排三个,那么最后一个即为最后一列。

上面这四个判断在很多地方都可以用到,可以记下备用🙂。

然后是上一行的view判断也需要注意。

其实这不单单只是九宫格布局,N个单元格布局也是可以的,感兴趣的小伙胖可以自行测试(好吧,估计你从我写的方法名已经看出来了,每行个数和总个数我都没有写死😂)。

另一种九宫格

这里的九宫格布局是子视图固定,而父视图由子视图决定,还有另一种情况:父视图高宽固定,子视图与父视图边界距离给定,子视图间距给定。
知道怎么布局吗?可以先思考一下。
|
|
|
|
|
|
好吧,揭晓答案:只要按照第一种九宫格前5个步骤来添加约束即可,去掉最后两步。

总结

一开始我只是想布局一个九宫格,但是后来又想,如果需求扩展到了N个单元,该如何实现呢,我的办法是从九宫格开始,由小及大来推导,然后就是要知道自动布局需要添加哪些约束,能够完整的固定视图,不能多,也不要少,这是很重要的。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注