nicklockwood / iCarousel

A simple, highly customisable, data-driven 3D carousel for iOS and Mac OS
http://www.charcoaldesign.co.uk/source/cocoa#icarousel
Other
12.01k stars 2.58k forks source link

Inverted CoverFlow #786

Open ChristRm opened 7 years ago

ChristRm commented 7 years ago

Is there any way to implement "inverted" version of CoverFlow carousel as on this picture:

template

Thanks

andreyrd commented 7 years ago

I took the transform code for CoverFlow

https://github.com/nicklockwood/iCarousel/blob/master/iCarousel/iCarousel.m#L504

and modified it to be inverted

func carousel(
    _ carousel: iCarousel,
    itemTransformForOffset offset: CGFloat,
    baseTransform transform: CATransform3D
) -> CATransform3D {
    // Set up base transform
    var transform = CATransform3DIdentity
    transform.m34 = carousel.perspective
    transform = CATransform3DTranslate(
        transform,
        -carousel.viewpointOffset.width,
        -carousel.viewpointOffset.height,
        0
    )

    // You could write this a little cleaner if you have the delegate implemented in the same class, but this way
    // will work if the delegate has or has not been set
    let tilt = carousel.delegate?.carousel?(carousel, valueFor: .tilt, withDefault: 0.9) ?? 0.9
    let spacing = carousel.delegate?.carousel?(carousel, valueFor: .spacing, withDefault: 0.25) ?? 0.25
    let clampedOffset = max(-1, min(1, offset))

    let x = (clampedOffset * 0.9 * tilt + offset * spacing) * carousel.itemWidth
    let z = fabs(clampedOffset) * -carousel.itemWidth * 0.5

    if carousel.isVertical {
        transform = CATransform3DTranslate(transform, 0, x, z)
        return CATransform3DRotate(transform, clampedOffset / 2 * CGFloat.pi * tilt / 2, -1, 0, 0)
    } else {
        transform = CATransform3DTranslate(transform, x, 0, z)
        return CATransform3DRotate(transform, clampedOffset / 2 * CGFloat.pi * tilt / 2, 0, 1, 0)
    }
}

Most notable is changing (when compared to the default CoverFlow transform) from

return CATransform3DRotate(transform, clampedOffset * CGFloat.pi / 2 * tilt, -1, 0, 0)
...
return CATransform3DRotate(transform, clampedOffset * CGFloat.pi / 2 * tilt, 0, 1, 0)

to

return CATransform3DRotate(transform, clampedOffset / 2 * CGFloat.pi * tilt / 2, -1, 0, 0)
...
return CATransform3DRotate(transform, clampedOffset / 2 * CGFloat.pi * tilt / 2, 0, 1, 0)

This is what inverts the rotation.


I also had to change

let x = (clampedOffset * 0.5 * tilt + offset * spacing) * carousel.itemWidth

to

let x = (clampedOffset * 0.9 * tilt + offset * spacing) * carousel.itemWidth

to add a little more spacing so that cards would not go through each other.

You may want to play with all of the values to get something that looks better / works right for you.