iPad 上的 3D 轮播效果 [英] 3D Carousel effect on the iPad

查看:29
本文介绍了iPad 上的 3D 轮播效果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在 iPad 上实现一个 3D 轮播,由 UIViews 组成,效果类似于 这里.

I am trying to implement a 3D Carousel on the iPad, consisting of UIViews, an effect like what is shown over here.

我在 SO 上经历了许多类似的问题,但没有找到任何令人满意的答案或根本没有答案.

I have gone through many similar questions on SO, but didn't find any staisfactory answers or no answers at all.

我试图通过修改coverflow动画来实现效果,但它并没有给出那种光滑的效果.

I am trying to achieve the effect through modifying the coverflow animation but it just doesn't give that slick effect.

有没有人实现过这个?(通过quartz和openGL公开征求建议)

Has anyone implemented this?(open for suggestions through quartz and openGL both)

推荐答案

如果您不介意前面的模糊,则无需深入研究 Quartz 或 OpenGL.您链接到的页面的视角有误(这就是为什么背景中的图像比前景中的图像移动得更快的原因),所以数学可能有点像雾里看花.

No need to dive into Quartz or OpenGL, assuming you don't mind foregoing the blur. The page you link to gets the perspective wrong (that's why the images in the background appear to move more quickly than those in the foreground), so the maths can be a little bit of smoke and mirrors.

底部有完整的示例代码.我所做的是使用正弦和余弦来移动一些视图.其背后的基本理论是,位于原点的半径为 r 的圆外侧的角度为 a 的点在 (a*sin(r), a*cos(r)) 处.这是一个简单的极坐标到笛卡尔转换,从大多数国家教给青少年的三角学中应该很清楚;考虑一个斜边长度为 a 的直角三角形——另外两条边的长度是多少?

There's full example code at the bottom. What I've done is used sine and cosine to move some views about. The basic theory behind it is that the point at angle a on the outside of a circle of radius r positioned on the origin is at (a*sin(r), a*cos(r)). That's a simple polar to Cartesian conversion, and should be clear from the trigonometry most countries teach to their teens; consider a right angled triangle with a hypotenuse of length a — what lengths are the other two sides?

然后您可以做的是减小 y 部分的半径以将圆转换为椭圆.椭圆看起来有点像你从一个角度看的圆.这忽略了透视的可能性,但要顺其自然.

What you can then do is reduce the radius of the y part to convert the circle into an ellipse. And an ellipse looks a bit like a circle that you're looking at from an angle. That ignores the possibility of perspective, but go with it.

然后我通过使大小与 y 坐标成比例来伪造透视图.我正在调整 alpha 的方式,就像您链接到的网站一样模糊,希望这对您的应用程序来说足够好.

I then fake perspective by making size proportional to the y coordinate. And I'm adjusting alpha in a way like the site you link to does blur, in the hope that's good enough for your application.

我通过调整要操作的 UIView 的仿射变换来影响位置和缩放.我直接在 UIView 上设置了 alpha.我还调整了视图层上的 zPosition(这就是导入 QuartzCore 的原因).zPosition 类似于 CSS z 位置;它不影响比例,只影响绘图顺序.因此,通过将其设置为等于我计算的比例,它只是说在较小的事物之上绘制较大的事物",从而为我们提供正确的绘制顺序.

I effect position and scale by adjusting the affine transform of the UIViews I want to manipulate. I set the alpha directly on the UIView. I also adjust the zPosition on the view's layers (which is why QuartzCore is imported). The zPosition is like the CSS z position; it doesn't affect scale, only drawing order. So by setting it equal to the scale I've computed, it just says "draw larger things on top of smaller things", giving us the correct draw order.

手指跟踪是通过 touchesBegan/touchesMoved/touchesEnded 循环一次跟随一个 UITouch 来完成的.如果没有手指被跟踪并且一些触摸开始,其中一个成为被跟踪的手指.如果它移动,则轮播会旋转.

Finger tracking is done by following one UITouch at a time through the touchesBegan/touchesMoved/touchesEnded cycle. If no finger is being tracked and some touches begin, one of them becomes the finger being tracked. If it moves then the carousel is rotated.

为了创造惯性,我有一个附加到计时器的小方法,可以跟踪当前角度与前一个刻度的角度.这种差异就像速度一样被使用,同时向下缩放以产生惯性.

To create inertia, I have a little method that attaches to a timer keeps track of the current angle versus the angle one tick before. That difference is used like a velocity and simultaneously scaled downward to produce inertia.

计时器在手指向上时启动,因为此时旋转木马应该开始自行旋转.如果旋转木马停止或放下新手指,它就会停止.

The timer is started on finger up, since that's when the carousel should start spinning of its own volition. It is stopped either if the carousel comes to a standstill or a new finger is placed down.

让你填空,我的代码是:

Leaving you to fill in the blanks, my code is:

#import <QuartzCore/QuartzCore.h>

@implementation testCarouselViewController

- (void)setCarouselAngle:(float)angle
{
    // we want to step around the outside of a circle in
    // linear steps; work out the distance from one step
    // to the next
    float angleToAdd = 360.0f / [carouselViews count];

    // apply positions to all carousel views
    for(UIView *view in carouselViews)
    {
        float angleInRadians = angle * M_PI / 180.0f;

        // get a location based on the angle
        float xPosition = (self.view.bounds.size.width * 0.5f) + 100.0f * sinf(angleInRadians);
        float yPosition = (self.view.bounds.size.height * 0.5f) + 30.0f * cosf(angleInRadians);

        // get a scale too; effectively we have:
        //
        //  0.75f   the minimum scale
        //  0.25f   the amount by which the scale varies over half a circle
        //
        // so this will give scales between 0.75 and 1.25. Adjust to suit!
        float scale = 0.75f + 0.25f * (cosf(angleInRadians) + 1.0);

        // apply location and scale
        view.transform = CGAffineTransformScale(CGAffineTransformMakeTranslation(xPosition, yPosition), scale, scale);

        // tweak alpha using the same system as applied for scale, this time
        // with 0.3 the minimum and a semicircle range of 0.5
        view.alpha = 0.3f + 0.5f * (cosf(angleInRadians) + 1.0);

        // setting the z position on the layer has the effect of setting the
        // draw order, without having to reorder our list of subviews
        view.layer.zPosition = scale;

        // work out what the next angle is going to be
        angle += angleToAdd;
    }
}

- (void)animateAngle
{
    // work out the difference between the current angle and
    // the last one, and add that again but made a bit smaller.
    // This gives us inertial scrolling.
    float angleNow = currentAngle;
    currentAngle += (currentAngle - lastAngle) * 0.97f;
    lastAngle = angleNow;

    // push the new angle into the carousel
    [self setCarouselAngle:currentAngle];

    // if the last angle and the current one are now
    // really similar then cancel the animation timer
    if(fabsf(lastAngle - currentAngle) < 0.001)
    {
        [animationTimer invalidate];
        animationTimer = nil;
    }
}

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad 
{
    [super viewDidLoad];

    // create views that are an 80x80 rect, centred on (0, 0)
    CGRect frameForViews = CGRectMake(-40, -40, 80, 80);

    // create six views, each with a different colour. 
    carouselViews = [[NSMutableArray alloc] initWithCapacity:6];
    int c = 6;
    while(c--)
    {
        UIView *view = [[UIView alloc] initWithFrame:frameForViews];

        // We don't really care what the colours are as long as they're different,
        // so just do anything
        view.backgroundColor = [UIColor colorWithRed:(c&4) ? 1.0 : 0.0 green:(c&2) ? 1.0 : 0.0 blue:(c&1) ? 1.0 : 0.0 alpha:1.0];

        // make the view visible, also add it to our array of carousel views
        [carouselViews addObject:view];
        [self.view addSubview:view];
    }

    currentAngle = lastAngle = 0.0f;
    [self setCarouselAngle:currentAngle];

    /*
        Note: I've omitted viewDidUnload for brevity; remember to implement one and
        clean up after all the objects created here
    */
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // if we're not already tracking a touch then...
    if(!trackingTouch)
    {
        // ... track any of the new touches, we don't care which ...
        trackingTouch = [touches anyObject];

        // ... and cancel any animation that may be ongoing
        [animationTimer invalidate];
        animationTimer = nil;
        lastAngle = currentAngle;
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    // if our touch moved then...
    if([touches containsObject:trackingTouch])
    {
        // use the movement of the touch to decide
        // how much to rotate the carousel
        CGPoint locationNow = [trackingTouch locationInView:self.view];
        CGPoint locationThen = [trackingTouch previousLocationInView:self.view];

        lastAngle = currentAngle;
        currentAngle += (locationNow.x - locationThen.x) * 180.0f / self.view.bounds.size.width;
        // the 180.0f / self.view.bounds.size.width just says "let a full width of my view
        // be a 180 degree rotation"

        // and update the view positions
        [self setCarouselAngle:currentAngle];
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    // if our touch ended then...
    if([touches containsObject:trackingTouch])
    {
        // make sure we're no longer tracking it
        trackingTouch = nil;

        // and kick off the inertial animation
        animationTimer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:@selector(animateAngle) userInfo:nil repeats:YES];
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    // treat cancelled touches exactly like ones that end naturally
    [self touchesEnded:touches withEvent:event];
}

@end

所以相关的成员变量是一个可变数组carouselViews"、一个计时器animationTimer"、两个浮点数currentAngle"和lastAngle",以及一个UITouchtrackingTouch".显然,您可能想要使用不仅仅是彩色方块的视图,并且您可能想要调整我凭空提取的用于定位的数字.否则,它应该可以正常工作.

So relevant member variables are a mutable array, 'carouselViews', a timer, 'animationTimer', two floats, 'currentAngle' and 'lastAngle', and a UITouch, 'trackingTouch'. Obviously you'd probably want to use views that aren't just coloured squares and you might want to adjust the numbers I've pulled out of thin air for positioning. Otherwise, it should just work.

我应该说,我使用 Xcode 中的 iPhone基于视图的应用程序"模板编写并测试了这段代码.创建该模板,将我的东西转储到创建的视图控制器中,并添加必要的成员变量进行测试.但是,我意识到触摸跟踪假定 180 度是您的视图的全宽,但是 setCarouselAngle: 方法强制轮播始终为 280 点(即 xPosition 上的 100 乘数乘以 2,加上宽度看法).因此,如果您在 iPad 上运行手指跟踪,它会显得太慢.解决方案显然不是假设视图宽度为 180 度,而是将其留作练习!

I should say, I wrote and tested this code using the iPhone 'View-based application' template in Xcode. Create that template, dump my stuff into the created view controller and add the necessary member variables to test. However, I've realised that the touch tracking assumes 180 degrees is the full width of your view, but the setCarouselAngle: method forces the carousel always to be 280 points across (that's the 100 multiplier on xPosition times two, plus the width of a view). So the finger tracking will appear to be far too slow if you run it on an iPad. The solution is obviously not to assume the view width is 180 degrees but that's left as an exercise!

这篇关于iPad 上的 3D 轮播效果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆