關(guān)鍵詞
轉(zhuǎn)場動(dòng)畫,手勢監(jiān)聽,核心動(dòng)畫
運(yùn)行效果

浮窗運(yùn)行效果.gif
使用簡介
// []中存放需要懸浮的類,vcname指類名
FloatViewManager.manager.addFloatVcsClass(vcs: [vcname])
主要使用類目及功能
整體涉及以下幾個(gè)主要的類,并注明其功能點(diǎn)
-
FloatViewManager單例,用來管理懸浮窗信息以及在window上的視圖。 -
TransitionPush / TransitionPop自定義導(dǎo)航轉(zhuǎn)場動(dòng)畫 -
FloatBallView屏幕上圓形浮標(biāo),可拖動(dòng) -
BottomFloatView底部繪制黑色或者紅色視圖
思路
- 首先初始化項(xiàng)目時(shí),為了監(jiān)聽手勢移動(dòng)變化
,自定義轉(zhuǎn)場,手勢代理
交由FloatViewManager來管理。
currentNavtigationController()?.interactivePopGestureRecognizer?.delegate = self
currentNavtigationController()?.delegate = self
- 當(dāng)進(jìn)入可支持懸浮的控制器時(shí),需要根據(jù)手勢的偏移來計(jì)算底部黑色半透明框的移動(dòng),這里我們使用以下來做監(jiān)聽,注意這里一定要進(jìn)行安全判斷。
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
// 當(dāng)前導(dǎo)航控制器是否存在子集合
guard let vcs = currentNavtigationController()?.viewControllers else{
return false
}
// 如果是根控制器,不做處理
guard vcs.count > 1 else {
return false
}
// 判斷當(dāng)前的控制器與開始數(shù)組中的支持懸浮的控制器是否一致,只有一致才執(zhí)行下一步,并開啟監(jiān)聽
if let currentVisiableVC = currentViewController() {
let currentVCClassName = "\(currentVisiableVC.self)"
if currentVCClassName.contains(floatVcClass.first!){
startDisplayLink()
edgeGesture = (gestureRecognizer as? UIScreenEdgePanGestureRecognizer) ?? nil
tempCurrentFloatVC = currentVisiableVC
}
}
return true
}
}
- 根據(jù)監(jiān)聽的結(jié)果更新底部的半透明視圖,這里詳細(xì)代碼請參見源代碼。
- 在手勢結(jié)束完之后,判斷是否懸浮,若最終結(jié)束手勢在底部黑色透明內(nèi),懸浮并展示圓形浮標(biāo),反之隱藏。
@objc func displayLinkLoop() {
if edgeGesture?.state == UIGestureRecognizerState.changed{
guard let startP = edgeGesture?.location(in:kWindow) else {
return
}
let orx : CGFloat = max(screenWidth - startP.x, kBvfMinX)
let ory : CGFloat = max(screenHeight - startP.x, kBvMinY)
bFloatView.frame = CGRect(x: orx, y: ory, width: kBottomViewFloatWidth, height: kBottomViewFloatHeight)
// 將點(diǎn)轉(zhuǎn)化到底部視圖上,計(jì)算是否在黑色圓內(nèi)
guard let transfomBottomP = kWindow?.convert(startP, to: bFloatView) else{
return
}
// print(transfomBottomP)
if transfomBottomP.x > 0 && transfomBottomP.y > 0{
let arcCenter = CGPoint(x: kBottomViewFloatWidth, y: kBottomViewFloatHeight)
let distance = pow((transfomBottomP.x - arcCenter.x),2) + pow((transfomBottomP.y - arcCenter.y),2)
let onArc = pow(arcCenter.x,2)
if distance <= onArc{
if(!bFloatView.insideBottomSeleted){
bFloatView.insideBottomSeleted = true
}
}else{
if(bFloatView.insideBottomSeleted){
bFloatView.insideBottomSeleted = false
}
}
}else{
if(bFloatView.insideBottomSeleted){
bFloatView.insideBottomSeleted = false
}
}
}else if(edgeGesture?.state == UIGestureRecognizerState.possible){
//結(jié)束的時(shí)候判斷最終手指的位置即黑色透明視圖是否是選中狀態(tài)。若選中,存儲(chǔ)當(dāng)前控制器,并暫停掉定時(shí)器(這里一定要暫停,不然浪費(fèi)資源)
if(bFloatView.insideBottomSeleted){
currentFloatVC = tempCurrentFloatVC
tempCurrentFloatVC = nil
ballView.show = true
if let newDetailVC = currentFloatVC as? NewDetailController{
ballView.backgroundColor = newDetailVC.themeColor
}
}
// 隱藏底部黑色透明視圖
UIView.animate(withDuration: animationConst().animationDuration, animations: {
self.bFloatView.frame = CGRect(x: screenWidth, y: screenHeight, width: kBottomViewFloatWidth, height:kBottomViewFloatHeight)
}) { (_) in
}
stopDisplayLink()
}
}
- 圓形浮標(biāo)支持拖動(dòng),并且提供點(diǎn)擊,拖動(dòng)手勢代理方法供FloatViewManager使用更新相關(guān)視圖,參見源代碼
- 當(dāng)用戶返回到其它界面,只要保證能找到最頂部導(dǎo)航,就可以再次打開懸浮窗控制器。這里主要是自定義轉(zhuǎn)場動(dòng)畫push/pop。
- 當(dāng)用戶手指手動(dòng)懸浮窗取消懸浮時(shí),將單例中保存所有的數(shù)據(jù)清空,保證再次可以正常使用。
缺陷
微信此功能,手指側(cè)滑至大于0.5松開,也會(huì)執(zhí)行pop的轉(zhuǎn)場動(dòng)畫,但我一直沒有找到合適的有效的解決方案,如有解決或者知曉方案的可以一起交流一下。
個(gè)人理解
看到技術(shù)論壇有人仿寫,于是自己也好奇嘗試著用swift做了一下,主題功能不難,只是有點(diǎn)繁瑣。
源碼
感謝作者及其以下博客,如有問題歡迎私信批評(píng)指正
Customizing the Transition Animations
UINavigationController內(nèi)的轉(zhuǎn)場動(dòng)畫
iOS浮窗
世界杯,中國隊(duì),加油~~~~