用Reveal分析网易云音乐的导航控制器切换效果

随着现在应用的UI越来越多样化,导航栏的颜色不再是单一颜色,还有些需要隐藏navigationBar的需求,所以UINavigationControllernavigationBar在不同颜色或隐藏的情况下切换效果显得不够平滑,越来越多的应用也开始使用类似网易新闻,网易云音乐,淘宝这种类似的切换效果.

想知道这些App都是如何实现这个效果的,所以就打算用Reveal来看看,Reveal的用法这里就不多说,网上教程很多.这里就只提一个越狱插件Reveal Loader,在Cydia商店可以搜到.这个插件可以方便的开启和关闭需要用Reveal查看的应用,省去了手动拷贝framework和创建配置文件的麻烦.

用Reveal观察了几个常用App,发现了这种效果的实现大致分3种:

  • 第一种是使用自定义navigationBar.淘宝,网易新闻,达令等使用的是这种.
  • 第二种是用截图的办法,在push到下一个页面时,截取屏幕,在使用edgePan来pop时看到的就是背后的截图,也能实现这种效果.京东,天猫等使用的是这种.
  • 第三种是使用了一种比较特别,比较巧妙的办法实现的,也就是网易云音乐的实现方法,后面会分析一下这种实现.

先说说这几种方法的优缺点:

  • 首先第一种的优点是灵活度高,实现的效果好.但是缺点也明显,自定义的navigationBar使用起来没有原生的使用起来方便.如果是已有项目要转换的话会有点麻烦.
  • 第二种优点是实现起来相对比较简单,缺点是截图是静态的,在侧滑返回时看到的不是即时的内容.而且这种方法只是在pop时有效果,push时并没有,还是原生push的渐变效果.
  • 第三种集合了前两种的优点,有易集成,效果也好的特点.并且不影响原有的导航栏的navigationItem和其他navigationBar的属性,包括设置的barTintColor,backgroundImage等.

下面来分析下网易云音乐究竟是如何实现的:

网易云音乐中的这个界面:

它的结构是这样的:

第一眼就能看到很多的UINavigationTransitionView,足足有3个那么多,为什么需要这么多的UINavigationController呢,下面会慢慢分析,先简化一下这个结构,其实就是:

     UIWindow
         |
UINavigationController
         |
  UIViewController
         |
 UITabBarController
         |
UINavigationController
         |
  UIViewController
         |
UINavigationController
         |
  UIViewController

然后再看看push后的页面的结构:

可以看到结构瞬间简单了很多,但是依旧出现了两个UINavgationController,现在的结构是这样的:

     UIWindow
         |
UINavigationController
         |
  UIViewController
         |
UINavigationController
         |
  UIViewController

现在就能打开看出来个大概了,看到这的时候我就知道网易云音乐原来是使用了一个比较巧的方法,简单来说这个方法具体的流程就是:

  1. 首先根控制器是一个导航控制器,这个导航控制器管理所有的页面的push和pop操作,并且这个导航控制器的导航栏navigationBar是隐藏的,并且是用setNavigationBarHidden:的方式隐藏,而不是navgationBar.hidden = YES,因为setNavigationBarHidden:的方式其实是直接将navigationBar给移除了,而navgationBar.hidden = YES只是让navigationBar变透明了.
  • 这个根的UINavigationControler下面还有一个UIViewController,是因为如果我们需要显示的界面本身也是一个UINavigationControler,那就会出问题了,UINavigationControler嵌套UINavigationControler的方式是不被允许的,和后面第三条相似.所以要进行包装.

  • 然后每当push一个ViewControler时,给这个ViewController进行了包装,先在外面包了一个UINavigationController,然后再在UINavigationControler外包一个UIViewController,这样做的原因是UINavigationControler不能push另一个UINavigationControler,所以需要再包一层UIViewController.

  • 最后我们真真正正在界面上看到的导航栏就是属于这个包在UIViewControler外的UINavigationControler的.因为每个页面都有属于自己的导航控制器,然后再有最外层的UINavigationControler来管理页面的push和pop操作,也就实现了我们想要的效果.

这个页面分析完,首页的结构也就不难理解了:

     UIWindow
         |
UINavigationController
         |
  UIViewController
         |
 UITabBarController
         |
UINavigationController
         |
  UIViewController
         |
UINavigationController
         |
  UIViewController

最外层的UINavigationControler是固定不变的,用来操作push和pop操作,UITabBarControllerviewControllers也存放的是UINavigationControler,那么这个UINavigationControlerrootViewController也就需要进行一次UIViewController-UINavgationController的包装.也就形成了目前的这个结构.

例子

分析完网易云音乐的结构,我自己也试着写了一下这个Demo,简单的实现了一下,实现的可能不是很完美,代码也比较粗糙,但是也算是实现了这个结构和想要的效果.

已知问题

目前暂未实现Push不隐藏TabBar的功能,网易云音乐是可以不隐藏TabBar的,不隐藏TabBar其实就是用控制器本身的NavigationController来Push就可以实现不隐藏,而不用
根控制器NavigationController来Push,这个坑先留着,以后再填。

更新

现在采用了新的方式和结构来实现,去除了以前需要替换rootViewController的方式,现在只需要替换所有UINavigationControllerJTNavigationController即可.

效果如下:

源码见:https://github.com/JNTian/JTNavigationController

欢迎指出问题,或者有什么更好的实现方法也希望能交流一下.