2011-03-09 109 views
13

我正试图在iPad上实现3D旋转木马,包含UIViews,效果类似于hereiPad上的3D旋转木马效果

我已经经历了很多类似的问题,但没有找到任何满意的答案或根本没有答案。

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

有没有人来实现这一点?(开放的建议,通过石英和OpenGL两)

回答

65

无需潜入石英或OpenGL,假设你不介意前述模糊。您链接到的页面导致视角错误(这就是为什么背景中的图像比前景中的图像移动得更快),所以数学可能会有点烟雾和镜像。

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

然后你可以做的是减少y部分的半径以将圆转换成椭圆。椭圆看起来有点像你从一个角度看的一个圆。这忽视了透视的可能性,但随之而来。

然后我通过使尺寸与y坐标成比例来假装透视。而且我正在以某种方式调整alpha,就像您链接的网站模糊不清,希望对您的应用程序足够好。

我通过调整我想操纵的UIViews的仿射变换来影响位置和比例。我直接在UIView上设置alpha。我也调整视图层上的zPosition(这就是导入QuartzCore的原因)。 zPosition就像CSS z的位置;它不影响比例尺,只能绘制顺序。所以通过设置它等于我计算的比例,它只是说“在较小的东西上画更大的东西”,给我们正确的绘画顺序。

通过在touchesBegan/touchesMoved/touchesEnded周期中每次跟随一个UITouch完成手指跟踪。如果没有手指正在被追踪并且一些触摸开始,其中一个手指变成被追踪的手指。如果它移动,则传送带旋转。

为了产生惯性,我有一个附加到定时器的小方法来跟踪当前角度与之前一个角度的角度。这种差异就像速度一样使用,同时向下缩放产生惯性。

计时器开始用手指向上,因为那时候旋转木马应该开始旋转自己的意志。如果传送带静止或放下新手指,它会停止。

离开你填补空白,我的代码是:

#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'。显然你可能想要使用不仅仅是彩色方块的视图,而且你可能想调整我为了定位而抽出的数字。否则,它应该只是工作。

编辑:我应该说,我在Xcode中使用iPhone'基于视图的应用程序'模板编写并测试了这段代码。创建该模板,将我的东西转储到创建的视图控制器中并添加必要的成员变量以进行测试。不过,我已经意识到,触摸跟踪假设180度是视图的整个宽度,但setCarouselAngle:方法强制轮换始终为280点(这是xPosition上的100乘数乘以2,再加上a的宽度视图)。所以如果你在iPad上运行它,手指跟踪将显得太慢。解决方案显然不是假设视图宽度是180度,但这只是一个练习!

+0

感谢汤米!我会试试这个。同时有一个给你的+1:D – Vin 2011-03-11 05:37:43

+1

这真的很棒。希望我能投票给你更多! – Vin 2011-03-11 05:54:30

+0

真的很好的例子。感谢分享。 – 2011-03-12 14:22:15