-
视觉样式上很简单:半屏拉起时,播放器跟着一起上移,半屏回收时,播放器跟着一起收下来。
需求用例
首先来陈述一下这个需求涉及到的用例,这决定后面我们代码怎么设计。
首先在用例上可以分为:打开半屏和关闭半屏,
微信体系内目前有两种方法触发半屏的展示:修改frame(比如:评论半屏、公众号半屏)和手动触发transform(使用了halfScreen半屏控件的业务类:点赞列表)。
它俩的区别在修改frame的半屏使用的是view——containerView——subViews的架构,而使用transform的半屏使用的是view——subviews架构,页面层级不同。
接着是关闭半屏,关闭半屏的实现方式更加多样一些,有修改frame变更的,有修改transform变更的,也有使用交互动画(Interactive Transition实现的),甚至于类似小程序WebView的框架,是使用JSAPI通知告知业务类的。
我们进行一次归类,左边这部分是ViewController,右边这部分是JSAPI相关。
陈述完半屏的业务行为,我们来看一下 业务类 和 半屏之间沟通需要哪些数据,从交互上看:第一点半屏进度有变更,播放器要同步跟着变更,这说明需要滑动的进度;第二点半屏打开与关闭会影响导航栏和气泡等元素的展示。
所以我们需要两个数据:进度 和 显示状态,而且这两个数据要设计成通用的,任何业务类最好都能获取到这两个状态。
接下来说一下代码是怎么写的:首先我们明确一下半屏上移这个需求有哪些人、哪些对象参与玩耍,其实是两类:监听半屏进度的对象:viewController或者Cell,我们称之为VideoView,另外一类就是半屏了,我们称之为HalfView。
代码架构
关于VideoVew移动的细节这里就不展开说了,VideoView需要监听两个数据:progress和show。
接着是比较有趣的HalfView,我们的目标是:尽可能无侵入性的获取半屏移动的状态,在这方面可以使用监听的两大利器:KVO和AOP。
在获取进度时,如果是操作View进行上下移动,我们可以KVO View的frame。
如果是使用交互转场做的上下移,我们怎么获取进度呢?难道我们要监听业务类的边缘手势吗?如果业务类不是通过手势触发的转场,那岂不是要针对半屏的每个手势都要做处理?
既然是通用,我们梳理了转场交互的本质,无论是什么手势在操作转场,其本质都是InteractiveTransition,而交互转场系统有一个updateProgress的系统方法,通过AOP这个系统方法,我们就可以获取转场的进度了。
进度获取搞定了,接下来我们需要看看怎么样比较完备的获取半屏的上移和下移状态,这方面就比较简单了,我们只要去AOP它的几个生命周期就可以完成任务。
接着我们还有一件大事要做,就是要把从半屏控件获取到的progress和showState传递给监听的VideoView,这里考虑了三种方式:
第一种是寻找VideoView的共同节点,比如播放器Cell实际上是被viewController承载的,且ViewController也是VideoCell的一个属性,那是不是只要ViewController监听进度,同步告知Cell就可以了?
这种方式经过考虑后被弃用了,因为如果这么做就必须在所有ViewController去告知Cell,比如要做的是:3tab -> cell、feedListView -> cell,以及后续如果想使用cell又想监听进度的viewController,都需要传递告知Cell,而最监听半屏移动的就是Cell,很多时候ViewController绝大部分情况下,ViewController.view不需要知晓进度。
第二种方式就是把需要监听的对象看成独立的个体,有一个弱引用的容器记录着需要监听的对象,当进度发生变更时,就分别通知他们,可以理解成一种观察者模式。
但在写需求的时候发现这种观察者模式还不够用,比如这么一个场景,刚进入视频号触发loading的时候就打开评论或者profile半屏,假设它是FeedA的评论内容,接着刷新回包,cell被复用,数据源被替换成了FeedB,但实际上之前FeedA和现在FeedB是同一个Cell。FeedB的cell不应该响应FeedA评论半屏的移动,所以这里靠对象订阅的粒度还不够,需要演化成订阅者模式。
需要被监听的对象要提供监听时的Key,在我们业务里也就是FeedId,如果VideoCell订阅时传入的Key和HalfView通知时获取的Key不一致,我们就认为这个对象取消了订阅。