2011-03-09 2 views
13

iPad에 3D 회전식 캐 러셀을 구현하려고합니다. 이는 UI보기로 구성되며 here과 같은 효과가 나타납니다.iPad의 3D 회전식 효과

나는 비슷한 질문을 많이했지만, 어떤 staisfactory 대답이나 전혀 대답을 찾지 못했습니다.

나는 커버 플로우 애니메이션을 수정하여 효과를 얻으려고 노력하고 있지만, 그 매끄러운 효과는주지 못합니다.

누군가가 당신이 흐림을 전술 한 상관 없어 가정

답변

65

석영 또는 OpenGL을에 뛰어 필요가 없습니다 (석영 및 OpenGL 모두를 통해 제안 오픈)이? 구현했습니다. 링크 된 페이지에서 원근감이 잘못 표시됩니다 (배경의 이미지가 전경의 이미지보다 빠르게 이동하는 이유입니다). 따라서 수학은 연기와 거울이 될 수 있습니다.

하단에 전체 예제 코드가 있습니다. 내가 한 일은 사인과 코사인을 사용하여 일부 견해를 움직이는 것입니다. 그 뒤에있는 기본 이론은 원점에 위치한 반경 r의 원 밖에있는 각도 a의 점이 (a * sin (r), a * cos (r))에 있다는 것입니다. 이것은 데카르트 전환에 단순한 극지방이며 대부분의 국가에서 십대에 가르치는 삼각법에서 분명해야합니다. 길이가 a 인 사변형을 가진 직각 삼각형을 고려하십시오 - 다른 두 변의 길이는 얼마입니까?

그러면 할 수있는 것은 y 부분의 반경을 줄여 원을 타원으로 변환하는 것입니다. 그리고 타원은 당신이 비스듬히 바라보고있는 원처럼 보입니다. 그것은 원근법의 가능성을 무시하고 함께 간다.

그런 다음 y 좌표에 비례하여 크기를 조정하여 퍼스펙티브를 만듭니다. 그리고 내가 링크를 사용하는 사이트처럼 흐리게 처리하는 것처럼 알파를 조절하고 있습니다. 응용 프로그램에 충분한 희망이 있습니다.

내가 조작하고 싶은 UIView의 아핀 변환을 조정하여 위치와 스케일에 영향을줍니다. 나는 알파를 UIView에 직접 설정했다. 또한 뷰의 레이어에서 zPosition을 조정합니다 (QuartzCore를 가져 오는 이유입니다). z 위치는 CSS z 위치와 같습니다. 크기에 영향을 미치지 않고 도면 순서 만 영향을줍니다. 그래서 제가 계산 한 스케일과 동일하게 설정하면, 더 작은 것들의 꼭대기에 더 큰 것들을 그려서, 정확한 draw order를줍니다.

핑거 추적은 Bugan/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 배가되는 값과 전망). 그래서 손가락 추적은 iPad에서 실행하면 너무 느린 것처럼 보일 것입니다. 해결책은 뷰 너비가 180도라고 가정하지 않는 것이 분명하지만 운동으로 남아 있습니다!

원형 하나를 포함
+0

감사 토미! 나는 이것을 시도 할 것이다. 한편 당신을 위해 +1이 있습니다 : D – Vin

+1

정말 훌륭했습니다. 내가 너를 더 많이 투표 할 수 있으면 좋겠다 !! – Vin

+0

정말 좋은 예입니다. 공유해 주셔서 감사합니다. –