2011-03-09 108 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