現(xiàn)在好多app(喜馬拉雅,新浪微博等)都有類似UIPageViewController的效果,實際開發(fā)中我們要進(jìn)行特殊效果,需要自定義,我寫了一個swift版本的.效果如下
點擊鏈接下載 github下載地址
- 多標(biāo)題,可以滑動
- 標(biāo)題較少,上面不滾動,距離等分
這種效果有很多坑點,比如,快速連續(xù)滑動時候流暢性問題,字體顏色漸變色效果,標(biāo)題盡量滑到居中位置,我要起始在非第一個控制器上等等.下面我把關(guān)鍵代碼貼出來.
UI架構(gòu)
我用的是父子控制器, parentVc.addChildViewController(vc),子控制器的視圖放在父控制器的collectionView上顯示.默認(rèn)樣式我用了一個JDPageStyle類專門進(jìn)行處理.
標(biāo)題較多時用 style.isScrollEnable = true 來讓上面的標(biāo)題可以滾動,標(biāo)題較少時style.isScrollEnable = false上面距離等分.
上面用scrollView 下面用collectionView,互為對方的代理,對方代理傳過來后自己滑動,自己滑動發(fā)送代理對方滾動,對方傳來滑動消息自己滑動時,不發(fā)送代理.
fileprivate func setupUI() {
// 1.將childVc添加到父控制器中
for vc in childVcs {
parentVc.addChildViewController(vc)
}
// 2.初始化用于顯示子控制器View的View(UIScrollView/UICollectionView)
addSubview(collectionView)
}
//設(shè)置contentView&titleView關(guān)系
titleView?.delegate = contentView
contentView?.delegate = titleView
- 設(shè)置起始index時要用多線程,主線程異步,這樣初始化完畢后才執(zhí)行這個滾動方法.
DispatchQueue.main.async {
let indexPa = IndexPath(item: self.startIndex, section: 0)
self.contentView?.collectionView.scrollToItem(at: indexPa, at: .centeredHorizontally, animated: false)
self.contentView?.delegate?.contentView((self.contentView)!, targetIndex: self.startIndex, progress: 1)
self.contentView?.delegate?.contentView( (self.contentView)!, endScroll: self.startIndex)
}
- 按鈕滾動到盡量居中位置
標(biāo)簽滑動中心位置大于屏幕一半時候,如果scrollView足夠長,scrollview滑動,使標(biāo)簽居中
var offsetX = newLabel.center.x - scrollView.bounds.width * 0.5
if offsetX < 0 {
offsetX = 0
}
//滑動到后面,scrollview不能再滑動時候,不滑動
let maxOffset = scrollView.contentSize.width - bounds.width
if offsetX > maxOffset {
offsetX = maxOffset
}
scrollView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: true)
下面控制器滑動時候
在scrollViewDidScroll里面判斷左滑右滑,并判斷進(jìn)度,把信息傳遞給上面.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x == startOffsetX || isForbidDelegate { return }
// 1.定義目標(biāo)的index早芭、進(jìn)度
var targetIndex : Int = 0
var progress : CGFloat = 0
// 2.判斷用戶是左滑還是右滑
if scrollView.contentOffset.x > startOffsetX { // 左滑 右移動
targetIndex = Int(startOffsetX / scrollView.bounds.width) + 1
if targetIndex >= childVcs.count {
targetIndex = childVcs.count - 1
}
progress = (scrollView.contentOffset.x - startOffsetX) / scrollView.bounds.width
} else { // 右滑
targetIndex = Int(startOffsetX / scrollView.bounds.width) - 1
if targetIndex < 0 {
targetIndex = 0
}
progress = (startOffsetX - scrollView.contentOffset.x) / scrollView.bounds.width
}
// 3.將數(shù)據(jù)傳遞給titleView
delegate?.contentView(self, targetIndex: targetIndex, progress: progress)
}}
快速滑動細(xì)節(jié)處理 如果開始拖拽時候下面view一頁沒滑動完畢,正在滑動,應(yīng)計算他應(yīng)該在哪里結(jié)束,讓它滑動到它應(yīng)該結(jié)束的地方,以此為其實值,要不會有上面標(biāo)題底部條顯示錯亂
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// 記錄開始的位置
isForbidDelegate = false
if Int(collectionView.contentOffset.x) % Int(collectionView.bounds.width) != 0 {
let theNum = collectionView.contentOffset.x / collectionView.bounds.width
let res = self.numFormat(num: Float(theNum ), format: "0")
collectionView.scrollToItem(at: IndexPath(item: Int(res)!, section: 0), at: .left, animated: false)
startOffsetX = collectionView.bounds.width*CGFloat(Float(res)!)
delegate?.contentView(self, targetIndex: Int(res)!, progress: 1)
delegate?.contentView(self, endScroll: Int(res)!)
}else{
startOffsetX = scrollView.contentOffset.x
}
}
文字顏色漸變處理
文字顏色不能有透明度,要可以轉(zhuǎn)成純的rgb顏色才可以
在上面view里面接受到下面正在滑動中的progress時候,根據(jù)新舊兩個index,取出兩個頭部label,設(shè)置顏色字體等
func contentView(_ contentView: JDContentView, targetIndex: Int, progress: CGFloat) {
if progress < 1 {
self.isUserInteractionEnabled = false
}else{
self.isUserInteractionEnabled = true
}
// 1.取出兩個Label
let oldLabel = titleLabels[currentIndex]
let newLabel = titleLabels[targetIndex]
// 2.漸變文字顏色
let selectRGB = getGRBValue(style.selectColor)
let normalRGB = getGRBValue(style.normalColor)
let deltaRGB = (selectRGB.0 - normalRGB.0, selectRGB.1 - normalRGB.1, selectRGB.2 - normalRGB.2)
oldLabel.textColor = UIColor(r: selectRGB.0 - deltaRGB.0 * progress, g: selectRGB.1 - deltaRGB.1 * progress, b: selectRGB.2 - deltaRGB.2 * progress)
newLabel.textColor = UIColor(r: normalRGB.0 + deltaRGB.0 * progress, g: normalRGB.1 + deltaRGB.1 * progress, b: normalRGB.2 + deltaRGB.2 * progress)
if progress > 0.5 {
oldLabel.font = style.titleFont
newLabel.font = style.selectedTitleFont
}else{
oldLabel.font = style.selectedTitleFont
newLabel.font = style.titleFont
}}
private func getGRBValue(_ color : UIColor) -> (CGFloat, CGFloat, CGFloat) {
guard let components = color.cgColor.components else {
fatalError("文字顏色請按照RGB方式設(shè)置 顏色不能有透明度")
}
return (components[0] * 255, components[1] * 255, components[2] * 255)
}