在之前的有篇文章讲述了利用HeaderView来写类似QQ好友列表的表视图。
这里写的天猫抽屉其实也可以用该方法实现,具体到细节每个人也有所不同。这里采用的是点击cell对cell进行运动处理以展开“抽屉”。
最后完成的效果大概是这个样子。
主要的环节:
点击将可视的Cell动画弹开。
其他的Cell覆盖一层半透明视图,将视线焦点集中在弹出来的商品细分类别中。
再次点击选中的或其他Cell,动画恢复到点击之前所在的位置。
商品细分类别属于之前写过的九宫格实现。这里就不贴代码了。之前的文章:点击打开链接
这里的素材都来自之前版本天猫的IPA。
加载数据
- (void)loadData { NSString *path = [[NSBundle mainBundle] pathForResource:@"shops" ofType:@"plist"]; NSArray *array = [NSArray arrayWithContentsOfFile:path]; NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:array.count]; [array enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop) { ProductType *proType = [[ProductType alloc] init]; proType.name = dict[@"name"]; proType.imageName = dict[@"imageName"]; proType.subProductList = dict[@"subClass"]; [arrayM addObject:proType]; }]; self.typeList = arrayM; }
一个ProductType数据模型,记录名称,图片名称等。
单元格数据源方法
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { TypeCell *cell = [tableView dequeueReusableCellWithIdentifier:RTypeCellIdentifier]; [cell bindProductKind:_typeList[indexPath.row]]; return cell; }
将数据模型的信息绑定到自定义类中进行处理,这个类在加载视图之后由tableview进行了注册。
下面看看自定义单元格中的代码
初始化
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier]; if (self) { self.contentView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"tmall_bg_main"]]; //设置clear可以看到背景,否则会出现一个矩形框 self.textLabel.backgroundColor = [UIColor clearColor]; self.detailTextLabel.backgroundColor = [UIColor clearColor]; self.selectionStyle = UITableViewCellSelectionStyleNone; //coverView 用于遮盖单元格,在点击的时候可以改变其alpha值来显示遮盖效果 _coverView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, RScreenWidth, RTypeCellHeight)]; _coverView.backgroundColor = [UIColor whiteColor]; _coverView.alpha = 0.0; [self addSubview:_coverView]; } return self; }
绑定数据
- (void)bindProductKind:(ProductType *)productType { self.imageView.image = [UIImage imageNamed:productType.imageName]; self.textLabel.text = productType.name; NSArray *array = productType.subProductList; NSMutableString *detail = [NSMutableString string]; [array enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop) { NSString *string; if (idx < 2) { string = dict[@"name"]; [detail appendFormat:@"%@/", string]; } else if (idx == 2) { string = dict[@"name"]; [detail appendFormat:@"%@", string]; } else { *stop = YES; } }]; self.detailTextLabel.text = detail; }
遍历array然后进行判断,对string进行拼接然后显示到细节label上。
然后是对点击单元格事件的响应处理,处理过程会稍微复杂一点
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (!_animationCells) { _animationCells = [NSMutableArray array]; } if (!_open) { [self openTableView:tableView withSelectIndexPath:indexPath]; } else { [self closeTableView:tableView withSelectIndexPath:indexPath]; } }
_animationCells用于之后记录运动的单元格,以便进行恢复。
- (CGFloat)offsetBottomYInTableView:(UITableView *)tableView withIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; CGFloat screenHeight = RScreenHeight - RNaviBarHeight; CGFloat cellHeight = RTypeCellHeight; CGFloat frameY = cell.frame.origin.y; CGFloat offY = self.tableView.contentOffset.y; CGFloat bottomY = screenHeight - (frameY - offY) - cellHeight; return bottomY; }
一个私有方法,为了方便之后获取偏移的高度,这个高度记录点击的单元格的高度到屏幕底部的距离。以便进行判断。
比如我们假设弹出的抽屉视图高度为200,那么如果点击的单元格到底部的距离超过200,则点击的单元格以及以上的不用向上偏移,只要将下面的单元格向下移动即可。
但是如果距离小于200,则所有单元格都要进行响应的移动才能给抽屉视图腾出空间。
按照思路进行开闭操作
- (void)openTableView:(UITableView *)tableView withSelectIndexPath:(NSIndexPath *)indexPath { /******获取可见的IndexPath******/ NSArray *paths = [tableView indexPathsForVisibleRows]; CGFloat bottomY = [self offsetBottomYInTableView:tableView withIndexPath:indexPath]; if (bottomY >= RFolderViewHeight) { _down = RFolderViewHeight; [paths enumerateObjectsUsingBlock:^(NSIndexPath *path, NSUInteger idx, BOOL *stop) { TypeCell *moveCell = (TypeCell *)[tableView cellForRowAtIndexPath:path]; if (path.row > indexPath.row) { [self animateCell:moveCell WithDirection:RMoveDown distance:_down andStatus:YES]; [_animationCells addObject:moveCell]; } if (path.row != indexPath.row) { //遮盖视图改变透明度 让其他单元格变暗 moveCell.coverView.alpha = RCoverAlpha; } }]; } else { _up = RFolderViewHeight - bottomY; _down = bottomY; [paths enumerateObjectsUsingBlock:^(NSIndexPath *path, NSUInteger idx, BOOL *stop) { TypeCell *moveCell = (TypeCell *)[tableView cellForRowAtIndexPath:path]; if (path.row != indexPath.row) { moveCell.coverView.alpha = RCoverAlpha; } if (path.row <= indexPath.row) { [self animateCell:moveCell WithDirection:RMoveUp distance:_up andStatus:YES]; } else { [self animateCell:moveCell WithDirection:RMoveDown distance:_down andStatus:YES]; } [_animationCells addObject:moveCell]; }]; } //禁止滚动表格视图 tableView.scrollEnabled = NO; }
主要对可视的单元格进行了判断移动,
其中[self animateCell:moveCell WithDirection:RMoveDown distance:_down andStatus:YES];是一个私有的重构后的方法。
不过一般情况下,动画的方法尽量在所有需求完成后再进行重构,因为毕竟不同的情况可能处理会很不同(动画方式,动画后的处理),放到一个方法后之后可能会发生需要再改回去。
看下这个方法
- (void)animateCell:(TypeCell *)cell WithDirection:(RMoveDirection)direction distance:(CGFloat)dis andStatus:(BOOL)status { CGRect newFrame = cell.frame; cell.direction = direction; switch (direction) { case RMoveUp: newFrame.origin.y -= dis; break; case RMoveDown: newFrame.origin.y += dis; break; default:NSAssert(NO, @"无法识别的方向"); break; } [UIView animateWithDuration:RCellMoveDuration animations:^{ cell.frame = newFrame; } completion:^(BOOL finished) { _open = status; }]; }
传入参数为单元格,动画方向,运动的距离以及一个判断是否打开的标识位。
最后看下闭合操作
- (void)closeTableView:(UITableView *)tableView withSelectIndexPath:(NSIndexPath *)indexPath { [_animationCells enumerateObjectsUsingBlock:^(TypeCell *moveCell, NSUInteger idx, BOOL *stop) { if (moveCell.direction == RMoveUp) { [self animateCell:moveCell WithDirection:RMoveDown distance:_up andStatus:NO]; } else { [self animateCell:moveCell WithDirection:RMoveUp distance:_down andStatus:NO]; } }]; NSArray *paths = [tableView indexPathsForVisibleRows]; for (NSIndexPath *path in paths) { TypeCell *typeCell = (TypeCell *)[tableView cellForRowAtIndexPath:path]; typeCell.coverView.alpha = 0; } _up = 0; //对一系列成员进行处理。 _down = 0; tableView.scrollEnabled = YES; [_animationCells removeAllObjects]; }
Demo源码:点击打开链接
不过这个素材来自于之前天猫客户端的版本,现在的天猫客户端对商品列表进行了改变。也是弹出,不过弹出的列表内容更多,占据了整个屏幕。
最近一直在写TableView的博客,常用的大部分都包含到了。
传送门:
IOS详解TableView——性能优化及手工绘制UITableViewCell
IOS详解TableView——静态表格使用以及控制器间通讯
以上就是本篇博客全部内容,欢迎指正和交流。转载注明出处~