215 lines
9.5 KiB
Swift
215 lines
9.5 KiB
Swift
//
|
|
// BRVideoDetailRecommendTransformer.swift
|
|
// BeeReel
|
|
//
|
|
// Created by 长沙鸿瑶 on 2025/7/29.
|
|
//
|
|
|
|
import UIKit
|
|
import FSPagerView
|
|
|
|
class BRVideoDetailRecommendTransformer: FSPagerViewTransformer {
|
|
|
|
|
|
override func applyTransform(to attributes: FSPagerViewLayoutAttributes) {
|
|
guard let pagerView = self.pagerView else {
|
|
return
|
|
}
|
|
let position = attributes.position
|
|
let scrollDirection = pagerView.scrollDirection
|
|
let itemSpacing = (scrollDirection == .horizontal ? attributes.bounds.width : attributes.bounds.height) + self.proposedInteritemSpacing()
|
|
switch self.type {
|
|
case .crossFading:
|
|
var zIndex = 0
|
|
var alpha: CGFloat = 0
|
|
var transform = CGAffineTransform.identity
|
|
switch scrollDirection {
|
|
case .horizontal:
|
|
transform.tx = -itemSpacing * position
|
|
case .vertical:
|
|
transform.ty = -itemSpacing * position
|
|
}
|
|
if (abs(position) < 1) { // [-1,1]
|
|
// Use the default slide transition when moving to the left page
|
|
alpha = 1 - abs(position)
|
|
zIndex = 1
|
|
} else { // (1,+Infinity]
|
|
// This page is way off-screen to the right.
|
|
alpha = 0
|
|
zIndex = Int.min
|
|
}
|
|
attributes.alpha = alpha
|
|
attributes.transform = transform
|
|
attributes.zIndex = zIndex
|
|
case .zoomOut:
|
|
var alpha: CGFloat = 0
|
|
var transform = CGAffineTransform.identity
|
|
switch position {
|
|
case -CGFloat.greatestFiniteMagnitude ..< -1 : // [-Infinity,-1)
|
|
// This page is way off-screen to the left.
|
|
alpha = 0
|
|
case -1 ... 1 : // [-1,1]
|
|
// Modify the default slide transition to shrink the page as well
|
|
let scaleFactor = max(self.minimumScale, 1 - abs(position))
|
|
transform.a = scaleFactor
|
|
transform.d = scaleFactor
|
|
switch scrollDirection {
|
|
case .horizontal:
|
|
let vertMargin = attributes.bounds.height * (1 - scaleFactor) / 2;
|
|
let horzMargin = itemSpacing * (1 - scaleFactor) / 2;
|
|
transform.tx = position < 0 ? (horzMargin - vertMargin*2) : (-horzMargin + vertMargin*2)
|
|
case .vertical:
|
|
let horzMargin = attributes.bounds.width * (1 - scaleFactor) / 2;
|
|
let vertMargin = itemSpacing * (1 - scaleFactor) / 2;
|
|
transform.ty = position < 0 ? (vertMargin - horzMargin*2) : (-vertMargin + horzMargin*2)
|
|
}
|
|
// Fade the page relative to its size.
|
|
alpha = self.minimumAlpha + (scaleFactor-self.minimumScale)/(1-self.minimumScale)*(1-self.minimumAlpha)
|
|
case 1 ... CGFloat.greatestFiniteMagnitude : // (1,+Infinity]
|
|
// This page is way off-screen to the right.
|
|
alpha = 0
|
|
default:
|
|
break
|
|
}
|
|
attributes.alpha = alpha
|
|
attributes.transform = transform
|
|
case .depth:
|
|
var transform = CGAffineTransform.identity
|
|
var zIndex = 0
|
|
var alpha: CGFloat = 0.0
|
|
switch position {
|
|
case -CGFloat.greatestFiniteMagnitude ..< -1: // [-Infinity,-1)
|
|
// This page is way off-screen to the left.
|
|
alpha = 0
|
|
zIndex = 0
|
|
case -1 ... 0: // [-1,0]
|
|
// Use the default slide transition when moving to the left page
|
|
alpha = 1
|
|
transform.tx = 0
|
|
transform.a = 1
|
|
transform.d = 1
|
|
zIndex = 1
|
|
case 0 ..< 1: // (0,1)
|
|
// Fade the page out.
|
|
alpha = CGFloat(1.0) - position
|
|
// Counteract the default slide transition
|
|
switch scrollDirection {
|
|
case .horizontal:
|
|
transform.tx = itemSpacing * -position
|
|
case .vertical:
|
|
transform.ty = itemSpacing * -position
|
|
}
|
|
// Scale the page down (between minimumScale and 1)
|
|
let scaleFactor = self.minimumScale
|
|
+ (1.0 - self.minimumScale) * (1.0 - abs(position));
|
|
transform.a = scaleFactor
|
|
transform.d = scaleFactor
|
|
zIndex = 0
|
|
case 1 ... CGFloat.greatestFiniteMagnitude: // [1,+Infinity)
|
|
// This page is way off-screen to the right.
|
|
alpha = 0
|
|
zIndex = 0
|
|
default:
|
|
break
|
|
}
|
|
attributes.alpha = alpha
|
|
attributes.transform = transform
|
|
attributes.zIndex = zIndex
|
|
case .overlap,.linear:
|
|
guard scrollDirection == .horizontal else {
|
|
// This type doesn't support vertical mode
|
|
return
|
|
}
|
|
let scale = max(1 - (1-self.minimumScale) * abs(position), self.minimumScale)
|
|
let translate = 1 - (scale - self.minimumScale) / (1 - self.minimumScale);
|
|
|
|
var transform = CATransform3DIdentity
|
|
transform = CATransform3DTranslate(transform, 0, 28 * translate, 0);
|
|
transform = CATransform3DScale(transform, scale, scale, 1.0);
|
|
attributes.transform3D = transform
|
|
|
|
let alpha = (self.minimumAlpha + (1-abs(position))*(1-self.minimumAlpha))
|
|
attributes.alpha = alpha
|
|
let zIndex = (1-abs(position)) * 10
|
|
attributes.zIndex = Int(zIndex)
|
|
case .coverFlow:
|
|
guard scrollDirection == .horizontal else {
|
|
// This type doesn't support vertical mode
|
|
return
|
|
}
|
|
let position = min(max(-position,-1) ,1)
|
|
let rotation = sin(position*(.pi)*0.5)*(.pi)*0.25*1.5
|
|
let translationZ = -itemSpacing * 0.5 * abs(position)
|
|
var transform3D = CATransform3DIdentity
|
|
transform3D.m34 = -0.002
|
|
transform3D = CATransform3DRotate(transform3D, rotation, 0, 1, 0)
|
|
transform3D = CATransform3DTranslate(transform3D, 0, 0, translationZ)
|
|
attributes.zIndex = 100 - Int(abs(position))
|
|
attributes.transform3D = transform3D
|
|
case .ferrisWheel, .invertedFerrisWheel:
|
|
guard scrollDirection == .horizontal else {
|
|
// This type doesn't support vertical mode
|
|
return
|
|
}
|
|
// http://ronnqvi.st/translate-rotate-translate/
|
|
var zIndex = 0
|
|
var transform = CGAffineTransform.identity
|
|
switch position {
|
|
case -5 ... 5:
|
|
let itemSpacing = attributes.bounds.width+self.proposedInteritemSpacing()
|
|
let count: CGFloat = 14
|
|
let circle: CGFloat = .pi * 2.0
|
|
let radius = itemSpacing * count / circle
|
|
let ty = radius * (self.type == .ferrisWheel ? 1 : -1)
|
|
let theta = circle / count
|
|
let rotation = position * theta * (self.type == .ferrisWheel ? 1 : -1)
|
|
transform = transform.translatedBy(x: -position*itemSpacing, y: ty)
|
|
transform = transform.rotated(by: rotation)
|
|
transform = transform.translatedBy(x: 0, y: -ty)
|
|
zIndex = Int((4.0-abs(position)*10))
|
|
default:
|
|
break
|
|
}
|
|
attributes.alpha = abs(position) < 0.5 ? 1 : self.minimumAlpha
|
|
attributes.transform = transform
|
|
attributes.zIndex = zIndex
|
|
case .cubic:
|
|
switch position {
|
|
case -CGFloat.greatestFiniteMagnitude ... -1:
|
|
attributes.alpha = 0
|
|
case -1 ..< 1:
|
|
attributes.alpha = 1
|
|
attributes.zIndex = Int((1-position) * CGFloat(10))
|
|
let direction: CGFloat = position < 0 ? 1 : -1
|
|
let theta = position * .pi * 0.5 * (scrollDirection == .horizontal ? 1 : -1)
|
|
let radius = scrollDirection == .horizontal ? attributes.bounds.width : attributes.bounds.height
|
|
var transform3D = CATransform3DIdentity
|
|
transform3D.m34 = -0.002
|
|
switch scrollDirection {
|
|
case .horizontal:
|
|
// ForwardX -> RotateY -> BackwardX
|
|
attributes.center.x += direction*radius*0.5 // ForwardX
|
|
transform3D = CATransform3DRotate(transform3D, theta, 0, 1, 0) // RotateY
|
|
transform3D = CATransform3DTranslate(transform3D,-direction*radius*0.5, 0, 0) // BackwardX
|
|
case .vertical:
|
|
// ForwardY -> RotateX -> BackwardY
|
|
attributes.center.y += direction*radius*0.5 // ForwardY
|
|
transform3D = CATransform3DRotate(transform3D, theta, 1, 0, 0) // RotateX
|
|
transform3D = CATransform3DTranslate(transform3D,0, -direction*radius*0.5, 0) // BackwardY
|
|
}
|
|
attributes.transform3D = transform3D
|
|
case 1 ... CGFloat.greatestFiniteMagnitude:
|
|
attributes.alpha = 0
|
|
default:
|
|
attributes.alpha = 0
|
|
attributes.zIndex = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
open override func proposedInteritemSpacing() -> CGFloat {
|
|
return pagerView?.interitemSpacing ?? 0
|
|
}
|
|
}
|