iPad上的3D旋转木马效果 [英] 3D Carousel effect on the iPad

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

问题描述

我正在尝试在iPad上实现3D Carousel,包括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 * 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.

我通过调整来影响位置和比例我想操纵的UIViews的仿射变换。我直接在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',以及一个UITouch,'trackingTouch'。显然你可能想要使用不仅仅是彩色方块的视图,你可能想要调整我用来定位的数字。否则,它应该工作。

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乘以2的100乘数加上a的宽度视图)。因此,如果您在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天全站免登陆