iOS高仿下厨房(Objective-C)

2017.5.19编辑:因为官方接口变动,所以一些需要根据返回数据进行动态调整布局的地方会崩溃,如果有兴趣可以自行抓取最新接口,然后根据返回数据进行调整。

前言

本开源项目讲解了一些App常见功能界面的搭建以及实现思路,适合新手。

为什么是下厨房?

下厨房:一个集合了工具、社区与平台电商属性的家庭美食入口。很棒的一个平台,App界面也很好看!

关于项目(Github地址在文章结尾)

  • 开发环境:Xcode 7.2,语言:Objective-C
  • 用到的工具:Charles抓包工具
  • 仿写程度:只是作为一个练习项目,除了没有接口的界面,以及一些小细节没有调整,其他大部分主要功能都实现了。其他部分童鞋们可以自己尝试实现~
  • 刚开始写的时候控件是用纯代码写的,后来发现太耗时间就改用Xib了
  • 代码如果有不合理的地方(如:命名),请见谅,学习实现思路就好,代码请不要借鉴。
  • 这个开源项目适合新手,基本的界面布局以及业务逻辑都有,看完这个基本也会写简单的App啦
  • 备注:因为当时对block有特殊偏好所以通篇没有一个protocol...代码也是比较新手,嘛主要以实现思路为主!希望能对各位有帮助!

效果预览

首页.gif
关注动态.gif
菜谱.gif
帖子与作品.gif
商品界面.gif
商品分类选择.gif
商品界面-图片展示动画.gif
购物车.gif
上传作品.gif
收货地址.gif
搜索界面.gif
菜谱-创建与删除.gif

一、首页

首页.png
布局
  • 如图,首页tableView就可以搞定
  • 日期标题为sectionHeader
  • 下面的就是cell了,下厨房返回的数据中cell有6种模板,通过自定义cell,根据不同模板显示不同效果,很简单就不描述了
思路:

顶部导航部分点击事件,通过给每个控件绑定tag,然后定义对应的枚举变量,通过闭包(Block)将事件传递到控制器后,控制器判断枚举值即可。


1. 跳转的界面控制器

① 菜谱

布局
  • 整个界面是一个UIViewController,放上一个tableView,然后添加底部的收藏、丢进菜篮子自定义view
  • 用料、做法、小贴士、被加入的菜单这四个标题是sectionHeader,其内容对应为一组,每组cell自定义即可
菜谱 - Header.png
菜谱 - 作品展示.png
  • 作品展示:
  • 这里实现的方法跟上面的用料、做法...一样,独立为一组,组内只有一个cell,cell的contentView里从上至下添加:作品个数Label作品展示CollectionView所有作品Button即可
  • collectionView手势左滑,松开会加载更多作品数据
    这里通过实现scrollView的代理方法,判断contentOffset的值是否达到预定的数值,达到即调用block,然后控制器发送网络请求加载更多数据,刷新界面即可。(这里我只是实现了一个需求,并没有进一步优化调整)
菜谱 - 底部.png
  • 布局:
    如图即可,底部加入菜单button也可以是sectionFooter,
    虽然下厨房几乎没有边框(有分割线),但仔细分析还是很好划分的

② 作品

作品.png
布局
  • 因为关注动态买买买界面跟这个差不多,需要复用到这个界面的内容,所以这整个界面是只有一个cell的tableView
  • 上面是个图片轮播器,下面添加控件即可,描述Label以及用户评论Label的高度是有内容决定的,这个只需要在模型中添加一个labelHeight属性,然后在内部计算好高度,直接返回给控件就可以了。控件部分可以根据不同界面显示的不同效果,分割成若干部分,然后给cell添加一个type属性(枚举类型),创建的时候告诉cell属于哪一种type,然后根据不同type进行调整即可。

2. 导航

① 关注动态

关注动态.gif
布局
  • 整个界面就一个tableView,一个动态为作品界面的cell
遇到的问题
  • 图片轮播器会受tableViewCell的复用机制影响,导致错乱(点赞按钮的状态是由服务器返回的数据决定的,这里我就不模拟了)
    解决办法:

  • 在控制器中添加一个记录图片轮播器滚动位置的数组属性imageViewCurrentLocationArray

  • 在图片轮播器里实现scrollView代理方法,监听记录最终的位移contentOffset.x,停止滚动后通过闭包/代理位置数据传递到控制器,控制器将位置数据添加到记录数组中即可,此方法应该同样适用其他因cell复用机制导致的数据显示混乱问题,(关于数组的操作,应考虑到:下拉刷新,上拉加载更多数据以及其他情况,详细代码见工程)
    至于实现哪个代理方法最为合理,应该视实际的业务需求以及界面效果而定,下面的textField代理也是如此

// scrollView停止滚动后记录contentOffset.x
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    !self.imageViewDidScrolledBlock ? : self.imageViewDidScrolledBlock(scrollView.contentOffset.x);
}
  • 最后图片轮播器添加一个属性接口,接收位置数据,然后在构造方法里设置图片轮播器的contentOffset即可
- (void)setImageViewCurrentLocation:(CGFloat)imageViewCurrentLocation {
    _imageViewCurrentLocation = imageViewCurrentLocation;
    
    // 恢复显示collectionView滚动的位置
    [self.collectionView setContentOffset:CGPointMake(imageViewCurrentLocation, 0)];

    // 恢复显示pageLabel的下标
    if (!self.pageLabel.hidden && self.imageArray.count) {
        NSInteger currentIndex = imageViewCurrentLocation / self.collectionView.frame.size.width + 1;
        self.pageLabel.text = [NSString stringWithFormat:@"%zd/%zd", currentIndex, self.imageArray.count];
    }
}

② 三餐

作品.png
布局
  • 如图,整个控制器是ViewController,将CollectionView以及上传button添加到viewController.view即可,比较简单

  • 导航栏的标题是自定义的view,然后self.navigationItem.titleView = view;即可

这个界面的接口号称“时时死”,如果想看效果的童鞋可以自己重新抓包


3. 功能界面

① 菜谱草稿(整个项目最难的界面)

菜谱创建 - 上半部分.png
菜谱创建 - 下半部分.png
布局

如图所示即可,需要注意的是:因为这个界面是操作本地数据,所以要时刻根据数据得变化判断控件是否显示、如何显示

思路
  • 因为是创建以及草稿功能的界面,所以操作的是本地数据,我写了一个菜谱草稿数据工具类,用来增删改查,非常方便
  • 照片上传
    点击弹出ActionSheet让用户选择是相机、还是相册,然后通过UIImagePickerController的代理方法- imagePickerController:didFinishPickingMediaWithInfo:选取照片即可
  • 因为顶部做法都有上传图片的需求,如果不做判断是谁需要设置图片,会导致数据显示错乱,这个在代理方法中通过判断代理的调用者即可解决图片错乱
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
    // 如果是顶部大图
    if (picker == self.headerPicker) {
        self.createRecipe.image = info[UIImagePickerControllerEditedImage];
    }
    // 如果是步骤图
    else if (picker == self.instructPicker) {
        self.instructionArray[self.setImageIndex].image = info[UIImagePickerControllerEditedImage];
    }
    [self.tableView reloadData];
    [picker dismissViewControllerAnimated:YES completion:^{
        // 选取完成后更新本地数据
        [self updateDarft];
    }];
}
草稿界面-做法.gif
  • 做法步骤
  • 点击添加步骤,往tableView对应位置插入一行步骤cell,并且在模型数据数组对应位置插入一个数据为空做法数据,如果不添加的话,点击编辑就会因为数据越界导致崩溃
// 增加一行点击回调
instructionFooter.addInstructionBlock = ^{
    // 添加一个空的本地数据
    [weakSelf.instructionArray addObject:[[XCFCreateInstruction alloc] init]];
    NSInteger row = weakSelf.instructionArray.count - 1;
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:1];
    // 插入cell
    [weakSelf.tableView insertRowsAtIndexPaths:@[indexPath]
                              withRowAnimation:UITableViewRowAnimationBottom];
};
  • 点击调整步骤,执行回调后tableView进入对应状态,[self.tableView setEditing:YES animated:YES];,这里需要注意的问题有:
  • tableView中只有做法这一组cell才进入编辑模式,在代理方法-tableView:canMoveRowAtIndexPath:中判断即可
  • 需要设置cell目标移动的位置,即使其他组不能进入编辑模式,但做法步骤cell还是能移动插入其中,所以需要设置:如果超过了做法步骤所在的section,不管怎么移动,最终都回到做法步骤section
  • 调整移动了cell之后,也需要同步数据中对应做法步骤的位置
// 如果不是步骤数组,就回到对应位置
- (NSIndexPath *)tableView:(UITableView *)tableView
targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
       toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath {
    NSIndexPath *finalIndexPath;
    // 如果拖动到第0组,那么松手就插入第1组第0个
    if (proposedDestinationIndexPath.section == 0) {
        finalIndexPath = [NSIndexPath indexPathForRow:0 inSection:1];
    }
    // 如果拖动到第1组,松手即插入目标位置
    else if (proposedDestinationIndexPath.section == 1) {
        finalIndexPath = proposedDestinationIndexPath;
    }
    // 如果拖动到第2组,那么松手就插入第1组最后一个
    else if (proposedDestinationIndexPath.section == 2) {
        finalIndexPath = [NSIndexPath indexPathForRow:self.instructionArray.count-1 inSection:1];
    }
    return finalIndexPath;
}
草稿界面-用料.gif
  • 用料:
    原理大致与做法步骤相同,只是用料的编辑是在一个新的控制器XCFIngredientEditController,不管有无用料,点击都进入这个控制器,那么:

  • XCFIngredientEditController中添加一个接口,接收已存在的用料数据,如果数据为空,就默认添加两个示例cell,如果不为空,就显示已存在的数据,从而达到新增、编辑的效果

  • 编辑完成或保存后,pop回创建菜谱控制器,并执行回调将编辑好的用料数据回传,刷新界面即可

  • 因为官方的效果是:只要操作了数据,即使不点击保存,也会将数据保存起来,所以我在每个数据操作后面都更新了本地数据[self updateDarft];


② 搜索

搜索界面.gif
搜索界面.png
布局
  • 进入搜索控制器时,判断本地数据中是否有已经搜索过的关键词,有则加载,没有就不显示第0组cell
  • 输入文字时,利用通知监听UITextFieldTextDidChangedNotification,然后通过闭包回传textField的文字内容给控制器,同时时刻刷新tableView即可
  • 底部流行搜索关键词是网络数据加载的,一个九宫格搞定
思路
  • 因为没有接口,所以我封装了一个本地单例类,保存搜索过的关键词,并提供数据操作的方法
  • 搜索:在本地数据中遍历是否已存在该关键词,存在就将旧关键词删除,然后穿插新关键词到第0位;如果不存在就直接插入到第0位即可(详细代码见工程)

③ 上传作品

上传作品.gif
布局

一个只有tableHeaderView的tableViewController搞定,官方App中图片、标签的添加会有动画,我这里没有实现,大概就是在改变控件frame值时添加动画即可

思路
  • 本地工具类,并不需要进行数据持久化,通过回调就可以完成数据操作
  • 需要注意的是:处理好图片、标签长度的显示以及换行

二、市集

1. 商品

商品界面.png
布局
  • 上面这部分我的实现方法是:全部作为一个tableHeaderView,然后内部细分为3个部分,控制好布局就可以了
  • 需要注意的是:商品优惠(红色边框button)、店铺优惠(橙色),服务器返回的是字符串类型数据,这里我将优惠信息以button展示(也可以Label),因为数据是动态的,所以可以通过计算字符串最大宽度,然后加上既定的长度,即可设置每个button的不同大小
商品界面.gif
商品界面-评价.gif
思路
  • 整个控制器是UIViewController,上面商品信息展示部分是tableView,下面图文详情界面是一个UIView(UIView上面放一个类似导航的标签view,下面为CollectionView,CollectionView的cell内部添加tableView),设置好对应frame即可
  • 继续拖动,查看图文详情
    这个通过实现scrollView代理方法,判断contentOffset.y的值是否达到预定值,官方效果是:不必等到松手,达到即商品展示tableView图文详情view同时进行位移动画,同时商品展示tableView.hidden = YES;,从而达到动画切换界面的效果。由图文详情切换回商品展示也是通过代理实现
// 向上拖动到一定程度,切换至图文详情界面
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // 预定值为100
    if (scrollView.contentOffset.y > self.tableView.contentSize.height - self.tableView.frame.size.height + 100) {
        // 隐藏商品信息
        self.tableView.hidden = YES;
        // 动画
        [UIView animateWithDuration:0.5 animations:^{
            self.tableView.transform = CGAffineTransformMakeTranslation(0, -(self.view.bounds.size.height-44-64));
            self.imageTextView.transform = CGAffineTransformMakeTranslation(0, -(self.view.bounds.size.height-44));
        } completion:^(BOOL finished) {
            [UILabel showStats:@"未解决webView导致的内存泄漏问题" atView:self.view];
        }];
    }
}
  • 评价CollectionView手势左滑切换控制器
    也是实现scrollView代理方法,通过回调切换控制器
  • 晒图/全部评价界面
    官方效果是:两个tableView重叠在一起,点击导航栏对应按钮设置tableView.hidden

2. 购物车

购物车.gif
布局
  • 整个控制器是UIViewController,self.view添加tableView跟底部的结算view
  • 同一个店铺的商品作为一组cell,店铺名为sectionHeader
思路(我设置了清空购物车就重新加载本地数据)
  • 官方是通过服务器接收数据显示购物车内容的,因为没接口所以我就新建了一个本地单例类,保存购物车的数据,并提供数据操作方法
  • 业务逻辑
  • 给每个商品添加一个XCFCartItemState枚举类型属性,记录是否被选中,点选商品勾选button后通过回调,在控制器更改本地数据。
  • 店铺勾选状态以及全选状态,通过遍历本地数据中对应店铺内商品所有商品是否全部选中,再通过点选商品回调刷新界面就可以达到实时更改状态的效果了
  • 商品购买数量
    在cell内部实现textField代理方法-textFieldShouldEndEditing:监听购买数量,编辑完成就通过闭包传递购买数量给控制器,控制器更改本地数据并刷新界面
    cell.itemNumberChangeBlock = ^(NSUInteger number) { // 修改商品个数回调
        // 拿到最新的数据,再修改数量
        // 如果不拿到最新数据,在编辑商品数量时点击店铺全选 会导致正在编辑的商品无法同步选中状态的bug
        NSArray *newShopArray = [XCFCartItemTool totalItems][indexPath.section];
        XCFCartItem *newItem = newShopArray[indexPath.row];
        // 修改数据中商品个数的值
        newItem.number = number;
        // 更新本地数据
        [XCFCartItemTool updateItemAtIndexPath:indexPath withItem:newItem];
        // 刷新界面
        [weakSelf.tableView reloadData];
    };
  • 切换编辑/删除模式:
    简单的闭包回调刷新界面即可,删除的话,根据商品状态是否点选,点选就删除,因为点选状态跟编辑模式是通用的,所以不需要另外计算

3. 确认订单

订单 - 上半部分.png
订单 - 下半部分.png
布局

如图,需要注意的是:如果计算结果店铺优惠价格为0,就会隐藏店铺优惠

思路
  • 直接拿到购物车界面勾选好的的商品,通过计算显示对应价格数字就可以了(运费只添加一次)
  • 选择收货地址,访问本地存储的收货地址数据,有则显示,没有则提示添加
  • 因为没钱没见过订单优惠长啥样,所以就没做了,不过应该就是tableView就可以搞定的

三、社区

社区.gif
思路
  • 点选一个评论cell,就获取该评论作者的昵称,赋值到底部的编辑框,然后在编辑控件内判断有无该字符串,有就删除,无则添加
  • 当编辑框内,只要最后一个字符串“@”,就显示用户tableView:利用通知监听UITextViewTextDidChangeNotification然后回调传递最后一个字符串到控制器,控制器判断显示

四、我

(因为我什么数据也没有,就没做那些详细界面了)

1. 个人资料

个人界面.gif

2. 收货地址

收货地址.gif
思路

本地数据工具类,修改内容闭包回调控制器更改数据,内部处理好逻辑关系就可以了


五、动画

① 图片展示

商品界面-图片展示动画.gif
思路

界面是一个UIViewController,提供接口接收数据,view中添加一个图片轮播器,present出现后执行动画(这里我只是实现了效果,详细控件分布就不做那么仔细了)
大概步骤:

  1. 点击图片
  2. 闭包回调传递 图片在当前窗口的frame值图片所在数组的下标给控制器
  3. 控制器将数值传递给图片展示控制器,并present
  4. 图片展示控制器接收图片数据赋值给图片轮播器,然后创建一个imageView(作动画用),frame设置为从上一个界面接收到的数值,然后imageView执行形变动画
  5. 动画执行完毕移除imageView,显示图片轮播器
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    // 关闭按钮
    UIButton *dismissButton = [[UIButton alloc] initWithFrame:CGRectMake(15, 15, 30, 30)];
    [dismissButton setImage:[UIImage imageNamed:@"closeLandscape"] forState:UIControlStateNormal];
    [dismissButton addTarget:self action:@selector(close) forControlEvents:UIControlEventTouchUpInside];
    dismissButton.alpha = 0;
    [self.view addSubview:dismissButton];
    
    // 图片轮播器
    CGRect displayRect = CGRectMake(0, XCFScreenHeight*0.5-175, XCFScreenWidth, 350);
    XCFImageShowView *showView = [[XCFImageShowView alloc] initWithFrame:displayRect];
    // 设置属性
    showView.type = XCFShowViewTypeDetail;
    showView.imageArray = self.imageArray;
    showView.currentIndex = self.imageIndex;
    showView.imageViewDidScrolledBlock = self.imageViewDidScrolledBlock;
    // 默认先隐藏
    showView.hidden = YES;
    [self.view addSubview:showView];
    
    // 临时添加一个imageView 作动画
    CGRect rect = [self.rectValue CGRectValue];
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:rect];
    XCFReviewPhoto *photo = self.imageArray[self.imageIndex];
    [imageView sd_setImageWithURL:[NSURL URLWithString:photo.url]];
    [self.view addSubview:imageView];
    
    // 动画
    [UIView animateWithDuration:0.3 animations:^{
        imageView.frame = displayRect;
    } completion:^(BOOL finished) {
        // 移除动画的imageView
        [imageView removeFromSuperview];
        // 显示图片轮播器
        showView.hidden = NO;
        [UIView animateWithDuration:0.3 animations:^{
            dismissButton.alpha = 1;
        }];
    }];
}

② 添加商品到购物车

商品分类选择.gif
商品分类view.png
布局
  • 可以通过添加一个window,来实现这个需求,也可以用直接在根窗口[UIApplication sharedApplication].keyWindow上添加一个自定义的UIView,我这里用的是后者。
  • 自定义一个半透明的UIView,按照官方的动画效果,我这里用一个tableView,只设置了tableHeaderView(因为懒所以没有使用UIScrollView),tableHeaderView的内容又是一个自定义UIView,控件摆放不多说了很简单,不过需要注意的是:
    • 控件之间的逻辑关系
    • 商品种类button的标题由服务器数据决定,所以整个tableView的frame并不是固定的
    • 因为没找到可以直接在计数器中间添加一个view的接口方法,所以我自定义了一个计数器XCFStepper,很简单的一个小控件,处理好逻辑关系就可以了
思路
  • 在购物车本地数据工具类XCFCartItemTool刷新数据方法中(不管是删除还是新增,都会刷新数据)发送自定义的通知,并传递商品数量totalNumber
    // 添加完成后发送通知,用处:购物车图标动画
    [[NSNotificationCenter defaultCenter] postNotificationName:XCFCartItemTotalNumberDidChangedNotification
                                                        object:nil
                                                      userInfo:@{@"goodsCount" : @([self totalNumber])}];
  • 购物车图标为自定义的UIView,[[UIBarButtonItem alloc] initWithCustomView:cartIcon];然后添加到导航栏,在view内部接收XCFCartItemTotalNumberDidChangedNotification通知后作核心动画就能实现效果了
- (void)awakeFromNib {
    // 监听“添加商品到购物车”的通知
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(cartItemTotalNumberDidChanged:)
                                                 name:XCFCartItemTotalNumberDidChangedNotification
                                               object:nil];
    NSUInteger count = [XCFCartItemTool totalNumber];
    if (count) { // 有商品才显示数量标签
        self.countButton.hidden = NO;
        [self.countButton setTitle:[NSString stringWithFormat:@"%zd", count]
                          forState:UIControlStateNormal];
    }
}

- (void)cartItemTotalNumberDidChanged:(NSNotification *)note {
    self.countButton.hidden = NO;
    NSDictionary *dict = note.userInfo;
    // 取得商品数量
    NSUInteger count = [dict[@"goodsCount"] integerValue];
    // 延时作动画
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [UIView animateWithDuration:0.5 animations:^{
            // 购物车图标执行放大动画
            self.countButton.transform = CGAffineTransformMakeScale(1.5, 1.5);
        } completion:^(BOOL finished) {
            // 改变显示的商品数
            [self.countButton setTitle:[NSString stringWithFormat:@"%zd", count]
                              forState:UIControlStateNormal];
            // 还原图标大小
            [UIView animateWithDuration:0.5 animations:^{
                self.countButton.transform = CGAffineTransformMakeScale(1, 1);
            }];
        }];
    });
}
  • 这里我模拟了一下添加效果:从本地数据中随机取出一个商品
    • 如果该商品只有一个类型,直接添加到购物车
    • 如果该商品有多种类型(如:300g、450g、600g),就以碰撞方式的动画(我用的是Facebookpop框架)弹出商品类别选择tableView,同时后面的商品信息view进行缩放,设置要购买的类别和数量后,商品图片执行核心动画,然后移除类别选择view,执行添加到购物车动画
    • 这个界面也比较简单,只是要注意各种小细节,动画的顺序,以及相应的添加到购物车(或立即购买)业务逻辑
        // 随机添加一样商品
        XCFCartItem *randomItem = [XCFCartItemTool randomItem];
        XCFGoods *randomGoods = randomItem.goods;
        // 加入购物车
        if (type == BottomViewClickedAddToShoppingCart) {
            // 如果该商品有多种类型,就弹窗让用户选择具体购买哪种类型
            if (randomGoods.kinds.count > 1) {
                UIWindow *window = [UIApplication sharedApplication].keyWindow;
                // 缩小当前界面
                [UIView animateWithDuration:0.3 animations:^{
                    window.rootViewController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
                }];
                
                // 显示商品分类view
                XCFKindsCategoryView *kindsView = [[XCFKindsCategoryView alloc] initWithFrame:window.bounds];
                // 分类view的弹出类型(购物车)
                kindsView.type = XCFKindsViewTypeCart;
                kindsView.item = randomItem;
                [window addSubview:kindsView];
                // 确认购买回调
                kindsView.confirmBlock = ^(XCFCartItem *item) {
                    // 本地购物车数据添加商品
                    [XCFCartItemTool addItem:item];
                    [UILabel showStats:[NSString stringWithFormat:@"添加:\n%@", item.kind_name] atView:weakSelf.view];
                };
                // 取消回调
                kindsView.cancelBlock = ^{
                    // 恢复界面大小
                    [UIView animateWithDuration:0.3 animations:^{
                        window.rootViewController.view.transform = CGAffineTransformMakeScale(1, 1);
                    }];
                };
                
            } else { // 如果只有一个商品,直接加入购物车
                [XCFCartItemTool addItemRandomly:^(NSString *goodsName) {
                    [UILabel showStats:[NSString stringWithFormat:@"随机添加:\n%@", goodsName] atView:weakSelf.view];
                }];
            }
        }

最后想说的话

  • 关于项目
  • 自动布局是按照iPhone6s的,其他机型可能会有偏差,见谅
  • 能实现的基本都实现了
    • 市集模块接口被加密,所以做不了,不过目测是简单的CollectionView+一些动画就能搞定的
    • 菜篮子界面应该是我的实现思路有问题所以没完成,后续如果有空的话会尝试实现
    • 一些很简单的、相同效果的界面我实在不想写了!也没做,有兴趣的童鞋可以自己尝试实现
  • 写代码过程中跑去骚扰了ManoBoo小神,在这里超级感谢ManoBoo!还帮我解决了两个bug!还有感谢维尼的小熊大神的开源App,两位前辈对界面实现思路的讲解让我受益匪浅。感谢一切开源。

Github代码下载地址

高仿下厨房App 开源咯~

  • 如果你是新手,并且我的项目对你有帮助的话,请大方的给我Star!开源的世界如此美好互帮互助感激不尽!如果你不给我Star,也不强求啦...

  • 如果你是大神...

END

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容