技术重塑控件类恩美第二个APP项目

iOS-如何构建一个可高度自定义的分页菜单

2017-09-27  本文已影响305人  peaktan
1、我们所知的分页菜单
京东发现页.gif 天猫国际页.gif 猪猪快购分类页.gif 猪猪海外分类页.gif 几何金融投资记录页.gif
2、JHPageMenuView拽写的初衷

1、WMPageController:OC版本,菜单只支持纯文本,装饰只有内置的几种,自定义性太低,可用范围有限;
2、PageMenu:Swift版本,菜单也是只支持纯文本,装饰器只有两种,自定义性太低,可用范围有限;
3、ZJScrollPageView:OC版本,菜单支持文本和图片混合,装饰器只有内置的几种样式,自定义性太低,但大部分都够用;

目前开源的组件无法满足快速变化的需求,UI层面永远是计划赶不上变化,不可能做到未雨绸缪;基于此,可能每个开发者到项目中都会根据当前的UI来重写一个。这样可能会出现一些重复的bug,重要的是浪费时间和精力。

上面已经提到了目前开源组件无法实现UI层面的快速变化,所以这个组件的优势,就是能够以不变应万变。这个组件的UI支持高度自定义,但是接口并没有因此而变得复杂。

可能你会想,高度自定义的UI组件,内部一定封装了很多东西来自动化。其实并没有你想的那么复杂,这个组件的内部只使用了一个UICollectionView和一个UIScrollView来实现功能,可以说是完全使用UIKit内置的UI组件,并没有过多复杂的代码。

3、UI分析
UI结构.png
4、项目结构
项目结构.png
5、滚动同步

菜单视图滚动会触发装饰视图滚动,以此达到滚动同步的目的

滚动同步.gif
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // 菜单视图滚动的时候,装饰视图也要跟着一起滚动
    if (scrollView.tag == JHMENU_COLLECTION_VIEW_TAG) {
        [_decorateView.scrollView setContentOffset:scrollView.contentOffset];
    }
}
6、支持内置菜单样式+自定义菜单
JHPageMenuItem *item = [JHPageMenuItem menuView:menuView itemForIndex:index];

item.menuItemNormalStyleBlock = ^{
    // 告知item正常情况下的样式
};

item.menuItemSelectedStyleBlock = ^{
    // 告知item选中时的样式
};
内置菜单.gif
@interface CustomMenuItem ()
@property (weak, nonatomic) IBOutlet UIView *markView;
@end

- (void)menuItemSelectedStyle {
    // 告知item正常情况下的样式,自定义的菜单需要重写该方法
}

- (void)menuItemNormalStyle {
    // 告知item选中时的样式,自定义的菜单需要重写该方法
}

@end
自定义菜单.gif
7、支持内置装饰器+自定义装饰器
/**
 菜单装饰的自带样式
 */
typedef NS_ENUM(NSInteger, JHPageDecorateStyle) {
    JHPageDecorateStyleDefault, // 默认样式
    JHPageDecorateStyleLine, // 带下划线
    JHPageDecorateStyleFlood, // 涌入效果 (填充)
    JHPageDecorateStyleFloodHollow // 涌入效果 (空心的)
};
内置样式-JHPageDecorateStyleDefault.gif 内置样式-JHPageDecorateStyleLine.gif 内置样式-JHPageDecorateStyleFlood.gif 内置样式-JHPageDecorateStyleFloodHollow.gif
- (UIView *)decorateItemInMenuView:(JHPageMenuView *)menuView {
    // 在这里设置你的自定义装饰器
}
自定义装饰器.gif
8、菜单切换可嵌入自定义动画

菜单切换默认是没有动画的,不过已经提供了接口

item.menuItemNormalStyleAnimationBlock = ^{
    // 告知item 从 选中 - 未选中 时执行的动画,可以通过这个block进行设置
};

item.menuItemSelectedStyleAnimationBlock = ^{
    // 告知item 从 未选中 - 选中 时执行的动画,可以通过这个block进行设置 
};
- (void)menuItemNormalStyleAnimation {
    // 告知item 从 选中 - 未选中 时执行的动画,自定义的菜单如果需要执行动画,需要重写该方法
}

- (void)menuItemSelectedStyleAnimation {
    // 告知item 从 未选中 - 选中 时执行的动画,自定义的菜单如果需要执行动画,需要重写该方法
}
9、菜单支持垂直和水平的滚动方向

菜单支持横向和竖向的滚动,可用范围更广

/**
 菜单滚动方向
 */
typedef NS_ENUM(NSInteger, JHPageMenuScrollDirection) {
    JHPageMenuScrollDirectionHorizontal, // 水平
    JHPageMenuScrollDirectionVertical    // 垂直
};
水平滚动的菜单.gif 垂直滚动的菜单.gif
10、JHPageController

JHPageController是基于JHPageMenuVeiwUIPageViewController进行封装的一个分页控制器,控制器内部已经把这两个组件的关联进行了封装,并且暴露了代理出来,只需要简单的几行代码,就能实现一个炫酷的分页控制器,依赖于JHPageMenuVeiw高度可自定义功能,JHPageController的功能依旧强大。

项目结构.png JHPageController展示-顶部.gif JHPageController展示-左边.gif JHPageController展示-底部.gif JHPageController展示-右边.gif JHPageController展示-导航栏.gif
11、遇到的问题

一开始是有尝试用UICollectionView来承载装饰器的,直接用iOS9的新特性,对单元格进行重排来实现移动动画,因为是内置的动画,而且UICollectionView已经是比较成熟的集合视图,所以才会有这个想法。但后来经过尝试发现还是存在不少未知问题,所以就放弃了,改用UIScollView来封装一个装饰器视图。

导致这种现象的原因就是runloop在作怪;解决办法就是启动一个定时器,把定时器添加到NSRunLoopCommonModes的mode下,然后在这个定时器下定时移动View,达到一种动画的效果。

CADisplayLink 是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器,精确度相当高。一但 CADisplayLink 以特定的模式注册到runloop之后,每当屏幕需要刷新的时候,runloop就会调用CADisplayLink绑定的target上的selector。至此,动画卡顿问题解决。

上一篇下一篇

猜你喜欢

热点阅读