tableviewUI 搭建

iOS - 仿美团下拉列表

2016-07-18  本文已影响9401人  啊啊啊啊锋

很早之前就打算写个仿美团的下拉列表,但因为最近一直在忙其他的事情所以一直没有着手做,上周还算有时间,于是抽了一天的时间简单封装了下,当然到目前为止实现的功能还只是只能显示一组下拉列表,如果以后有时间,这个demo我会继续更新和维护,到时候会放在这个博客里边,笔者写这个demo主要是自己学习之用,当然能够帮到对这方面有需求的用户那更好。先看下效果图:

下拉列表菜单

每点击一下就要改变标题颜色,而且文字右边那个朝下的三角形也要换成对应颜色的朝上的箭头,同时还要弹出对应的下拉列表。但这里我们就应该想,要实现这样的按钮效果,可能需要自己的封装,毕竟系统的UIButton是没有这样的效果的,同时我们可以解压下美团官方的APP报,如果能找找到对应的图片那么我们的思路就八九不离十了(经过笔者查看,官方包里边确实有对应的两个三角形图片),我们看到美团的下拉出来的TableView在切换不同的列表的时候,如果拉出来的TableView高度不同,列表会有个缓慢的改变高度的过程,那么我们就需要每次切换数据源时让TableView执行一次reloadData的操作。

代码实现

封装菜单按钮

我们创建MenuButton类,让其继承自UIView。(笔者本来想让其继承自UIButton的,但是想到UIButton里边已经有UILabelUIImageView控件了,为了本着简洁高效的原则,所以就没有继承自UIButton

.h里边:

/** 当按钮标题改变时,要触发`setTitle:`这个方法 */
@property (nonatomic, copy) NSString *title;

/**
 *  选中某个按钮时回调方法
 *
 *  @param button    用来标记选中的按钮
 *  @param index     用来标记`选中了第几个按钮`
 *  @param selected  用来标记`这个按钮选中状态`
 */
@property (nonatomic, copy) void (^clickMenuButton) (MenuButton *button, NSString *title, BOOL selected);

/**
 *  按钮初始化方法
 *
 *  @param frame    按钮frame
 *  @param title    按钮标题
 *  @param defImage 按钮上的默认图片
 *  @param selImage 按钮选中时的图片
 */
- (instancetype)initWithFrame:(CGRect)frame
                        title:(NSString *)title
                     defImage:(UIImage *)defImage
                     selImage:(UIImage *)selImage

经过我们的分析我只,我们需要两个控件一个UILabel用来显示按钮的标题,一个UIImageView用来设置标题右边那个三角形图片,在.m里边:

@interface MenuButton ()
{
    NSString *_title;  // 按钮标题
    UIImage *_defImg;  // 按钮没有选中时右边图片
    UIImage *_selImg;  // 按钮选中时右边图片
}
/** 用来设置按钮标题 */
@property (nonatomic, strong) UILabel *titLabel;  
/** 用来设置图片 */
@property (nonatomic, strong) UIImageView *imgView;
@end

定义按钮初始化方法:

- (instancetype)initWithFrame:(CGRect)frame
                        title:(NSString *)title
                     defImage:(UIImage *)defImage
                     selImage:(UIImage *)selImage
{
    self = [super initWithFrame:frame];
    if (self) {
        _title = title;
        _defImg = defImage;
        _selImg = selImage;
        [self setupSubViews];
    }
    return self;
}

- (void)setupSubViews
{
    self.selected = NO;

    [self addSubview:self.titLabel];
    [self addSubview:self.imgView];
    // self.backgroundColor = [UIColor colorWithWhite:0.960 alpha:1.000];

    // 给视图添加手势,当我们点击视图是触发`menuButtonClicked`方法
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuButtonClicked)];
    [self addGestureRecognizer:tap];
}

#pragma mark - 手势点击事件
- (void)menuButtonClicked
{
    _selected = ! _selected;

    if (_selected) {
        self.titLabel.textColor = kSelTitleCor;
        self.imgView.image = _selImg;
    }
    else {
        self.titLabel.textColor = kDefTitleCor;
        self.imgView.image = _defImg;
    }

    if (self.clickMenuButton) {
        self.clickMenuButton(self, self.titLabel.text,_selected);
    }
}

当按钮的标题改变时,我们要实现视图的setTitle:方法:

- (void)setTitle:(NSString *)title
{
    self.titLabel.text = title;
    UIFont *font = [UIFont systemFontOfSize:kFontSize];
    _titLabel.font = font;
    
    // 为了不至于让标题和图片由于位置变化而太难看,我们需要在按钮标题每次改变时重新设置它的frame
    CGSize size = [title sizeWithFont:font maxSize:CGSizeMake(self.w, self.h)];
    _titLabel.center = CGPointMake(self.w/2 - kTriangleWH/2, self.h/2);
    _titLabel.bounds = CGRectMake(0, 0, size.width, size.height);
    _imgView.frame = CGRectMake(CGRectGetMaxX(self.titLabel.frame), (self.h-kTriangleWH)/2, kTriangleWH, kTriangleWH);
    [self setNeedsLayout];
    [self setIsSeled:NO];
}

封装下拉菜单

我们创建MenuButton类,让其继承自UIView,在.h里边:

/**
 *  数据源
 */
@property (nonatomic, strong) NSArray *dataSource;

/**
 *  选中某个按钮时回调方法(这个回调其实可以不写)
 *
 *  @param button    用来标记选中的按钮
 *  @param index     用来标记`选中了第几个按钮`
 *  @param selected  用来标记`这个按钮选中状态`
 */
@property (nonatomic, copy) void (^clickMenuButton) (MenuButton *button, NSInteger index, BOOL selected);


 /**
 *  选中下拉列表某行的回调方法
 *
 *  @param index  用来标记`选中了第几行`
 *  @param title  用来标记`这个这一行的标题`
 */
@property (nonatomic, copy) void (^clickListView) ( NSInteger index, NSString *title);


/**
 *  初始化菜单视图
 *
 *  @param frame    菜单视图frame(推荐x:0.f y:自定义 width:屏幕宽度 height:>=25)
 *  @param titles   要显示的按钮标题数组
 *  @param defImage 按钮没有点击之前右边那个小图片
 *  @param selImage 按钮点击之后右边那个小图片
 *
 *  @return 菜单视图
 */
- (instancetype)initWithFrame:(CGRect)frame
                   Titles:(NSArray <NSString *>*)titles
                 defImage:(UIImage *)defImage
                 selImage:(UIImage *)selImage;

.m里边我们初始化界面:

- (instancetype)initWithFrame:(CGRect)frame Titles:(NSArray<NSString *> *)titles defImage:(UIImage *)defImage selImage:(UIImage *)selImage
{
    self = [super initWithFrame:frame];
    if (self) {
        [self subViewsWithTitles:titles defImage:defImage selImage:selImage];
    }
    return self;
}

- (void)subViewsWithTitles:(NSArray *)titles defImage:(UIImage *)defImage selImage:(UIImage *)selImage
{
    self.backgroundColor = [UIColor whiteColor];

    // 上边的分割线
    CGRect topLineFrame = CGRectMake(0, 1.f, kS_W, 0.5f);
    UIView *topLine = [[UIView alloc] initWithFrame:topLineFrame];
    topLine.backgroundColor = kLineCor;
    [self addSubview:topLine];

    // 下边的分割线
    CGRect bottomLineFrame = CGRectMake(0, self.h-1.f, kS_W, 0.5f);
    UIView *bottomLine = [[UIView alloc] initWithFrame:bottomLineFrame];
    bottomLine.backgroundColor = kLineCor;
    [self addSubview:bottomLine];

    NSInteger count = [titles count];
    for (int i=0; i<count; i++) {
    // 创建按钮
        CGFloat buttonW = (self.w - (count-1)*kLineW)/count;
        CGFloat buttonH = 40.f;
        CGFloat buttonX = (buttonW+kLineW) * i;
        CGRect btnFrame = CGRectMake(buttonX, 0.f, buttonW, buttonH);
        MenuButton *button = [[MenuButton alloc] initWithFrame:btnFrame title:titles[i] defImage:defImage selImage:selImage];
        [self addSubview:button];
    
    
        __weak typeof(self)weakSelf = self;
        button.clickMenuButton = ^(MenuButton *button, NSString *title, BOOL selected){
            if (weakSelf.clickMenuButton) {
                weakSelf.clickMenuButton( button,i, selected);
            }
        
            if (!_button) {
                _button = button;
            }
            if (button != _button) {
                [_button resetStatus:_button];
            }
            else {
            }
        
            _currenTitle = title;
            // ------------------------
    
            _button = button;
            if (selected) {
                [self showListViewAnimation];
            }
            else {
                [self hideListViewAnimation];
            }
        
            // ----------------------
        
        };
    
        // 按钮之间的竖直分割线
        if (i < count-1) {
            CGFloat lineX = buttonX + buttonW;
            CGFloat lineY = (self.h-kLineH)/2;
            CGRect lineFrame = CGRectMake(lineX, lineY, kLineW, kLineH);
            UIView *line = [[UIView alloc] initWithFrame:lineFrame];
            line.backgroundColor = kLineCor;
            [self addSubview:line];
        }
    }
}

// 一定要重写这个方法,在这里`reloadData`,这样当我们点击不同的按钮时TableView高度将会发生变化
- (void)setDataSource:(NSArray *)dataSource
{
    _dataSource = dataSource;
    [self.lTableView reloadData];
}

// 更具数据源里边数据情况,返回TableView高度
- (CGFloat)maxListHeightWithModel:(NSArray *)dataSource
{
    NSInteger count = dataSource.count;
    CGFloat height = 0.f;
    CGFloat oriHeight = kRowH*count;
    oriHeight > kS_H/3*2 ? (height = kS_H/3*2) : (height = kRowH*count);

    return height;
}

#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.dataSource.count;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return kRowH;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ListCell *cell = [ListCell cellWithTableView:tableView];
    cell.title = self.dataSource[indexPath.row];
    cell.selected = [self.dataSource[indexPath.row] isEqualToString:_currenTitle];
    return cell;
}

#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 当点击下拉菜单某一行时移除下菜单和遮罩层
    [UIView animateWithDuration:0.2f animations:^{
        self.shadow.alpha = 0.f;
        self.lTableView.h = 0.f;
    } completion:^(BOOL finished) {
        [self.shadow removeFromSuperview];
        [self.lTableView removeFromSuperview];
    }];

    _button.isSeled = NO;
    _button.title = self.dataSource[indexPath.row];

    // 当点击下拉菜单某一行时重置按钮状态
    [_button resetStatus:_button];

    // 实现回调方法
    if (self.clickListView) {
        self.clickListView(indexPath.row, self.dataSource[indexPath.row]);
    }
}

到这里几个主要的方法差不多了,由于现在只是实现了单列下拉列表的情况,所以代码还是比较简单的。

我们在ViewController里边测试下代码效果:

// 在TableView的这个方法里边我们返回下拉菜单视图
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    // 推荐将`MenuListView`设置为tableView的第一组的组头视图

    UIImage *defImg = [UIImage imageNamed:@"gc_navi_arrow_down"];
    UIImage *selImg = [UIImage imageNamed:@"gc_navi_arrow_up"];
    CGRect frame = CGRectMake(0.f, 0.f, kS_W, 40.f);
    NSArray *titles = @[@"自助餐", @"附近", @"智能排序", @"筛选"];
    MenuListView *menu = [[MenuListView alloc] initWithFrame:frame Titles:titles defImage:defImg selImage:selImg];

    __weak typeof (menu)weakMenu = menu;
    menu.clickMenuButton = ^(MenuButton *button, NSInteger index, BOOL selected){
    // NSLog(@"点击了第 %ld 个按钮,选中还是取消?:%d", index, selected);
    if (index == 0) {
        weakMenu.dataSource = @[@"自助餐",@"火锅",@"海鲜",@"烧烤啤酒",@"甜点饮食",@"生日蛋糕",@"小吃快餐",@"日韩料理",@"西餐",@"聚餐宴请",@"川菜",@"江浙菜",@"香锅烤鱼",@"粤菜",@"中式烧烤/烤串",@"西北菜",@"咖啡酒吧",@"京菜鲁菜",@"湘菜",@"生鲜蔬果",@"东北菜",@"云贵菜",@"东南亚菜",@"素食",@"创意菜",@"躺/粥/炖菜",@"新疆菜",@"其他美食"];
    }
    else if (index == 1) {
        weakMenu.dataSource = @[@"附近",@"新津县",@"都江堰",@"温江区",@"郫县",@"龙泉驿区",@"锦江区",@"金牛区",@"成华区",@"青羊区",@"武侯区"];
    }
    else if (index == 2) {
        weakMenu.dataSource = @[@"智能排序", @"离我最近", @"评价最高", @"最新发布", @"人气最高", @"价格最低", @"价格最高"];
    }
    else if (index == 3) {
        weakMenu.dataSource = @[@"只看免预约",@"节假日可用",@"用餐时间段",@"用餐人数",@"餐厅地点"];
    }
};

    // 选中下拉列表某行时的回调(这个回调方法请务必实现!)
    menu.clickListView = ^(NSInteger index, NSString *title){
        NSLog(@"选中了-> %d   标题-> %@", index, title);
    };

    return menu;
}

运行结果:

写在后面

目前这个demo实现的功能还比较简单,如果以后有时间我会继续更新。

Demo地址

上一篇 下一篇

猜你喜欢

热点阅读