Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(643)

Side by Side Diff: ios/chrome/browser/ui/stack_view/page_animation_util.mm

Issue 2587023002: Upstream Chrome on iOS source code [8/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
6
7 #import <QuartzCore/QuartzCore.h>
8 #import <UIKit/UIKit.h>
9
10 #import "base/mac/scoped_nsobject.h"
11 #import "ios/chrome/browser/ui/animation_util.h"
12 #include "ios/chrome/browser/ui/rtl_geometry.h"
13 #import "ios/chrome/browser/ui/stack_view/card_view.h"
14 #import "ios/chrome/common/material_timing.h"
15
16 using ios::material::TimingFunction;
17
18 namespace {
19
20 const NSTimeInterval kAnimationDuration = 0.25;
21 const NSTimeInterval kAnimationHesitation = 0.2;
22
23 // Constants used for rotating/translating in in transition-in animations and
24 // rotating/translating out in transition-out animations.
25 const CGFloat kDefaultRotation = 0.2094; // 12 degrees.
26 // The amount by which the card should be translated along the axis on which
27 // its short side is oriented (horizontal in portrait, vertical in landscape).
28 const CGFloat kDefaultShortSideAxisTranslation = 240;
29 // The amount by which the card should be translated along the axis on which
30 // its long side is oriented (vertical in portrait, horizontal in landscape).
31 const CGFloat kDefaultLongSideAxisTranslation = 10;
32
33 // Transitioning in on landscape has a special-case animation.
34 const CGFloat kLandscapeAnimateInRotation = 0.9423; // 54 degrees.
35 const CGFloat kLandscapeAnimateInShortSideAxisTranslation = -180;
36 const CGFloat kLandscapeAnimateInLongSideAxisTranslation = 140;
37
38 NSString* const kViewAnimateInKey = @"ViewAnimateIn";
39 NSString* const kPaperAnimateInKey = @"PaperAnimateIn";
40
41 // When animating out, a card shrinks slightly.
42 const CGFloat kAnimateOutScale = 0.7;
43 const CGFloat kAnimateOutAnchorX = 0.9;
44 const CGFloat kAnimateOutAnchorY = 0;
45 }
46
47 @interface PaperView : UIView
48
49 @end
50
51 @implementation PaperView
52
53 - (id)initWithFrame:(CGRect)frame {
54 self = [super initWithFrame:frame];
55 if (self) {
56 const UIEdgeInsets kShadowStretchInsets = {28.0, 28.0, 28.0, 28.0};
57 const UIEdgeInsets kShadowLayoutOutset = {-10.0, -11.0, -12.0, -11.0};
58 CGRect shadowFrame = UIEdgeInsetsInsetRect(frame, kShadowLayoutOutset);
59 base::scoped_nsobject<UIImageView> frameShadowImageView(
60 [[UIImageView alloc] initWithFrame:shadowFrame]);
61 [frameShadowImageView
62 setAutoresizingMask:(UIViewAutoresizingFlexibleWidth |
63 UIViewAutoresizingFlexibleHeight)];
64 [self addSubview:frameShadowImageView];
65
66 UIImage* image = [UIImage imageNamed:@"popup_background"];
67 image = [image resizableImageWithCapInsets:kShadowStretchInsets];
68 [frameShadowImageView setImage:image];
69 }
70 return self;
71 }
72
73 @end
74
75 namespace ios_internal {
76
77 namespace page_animation_util {
78
79 const CGFloat kCardMargin = 14.0;
80
81 void SetNewTabAnimationStartPositionForView(UIView* view, BOOL isPortrait) {
82 CGAffineTransform transform = CGAffineTransformMakeTranslation(
83 (isPortrait ? kDefaultShortSideAxisTranslation
84 : kLandscapeAnimateInLongSideAxisTranslation),
85 (isPortrait ? kDefaultLongSideAxisTranslation
86 : kLandscapeAnimateInShortSideAxisTranslation));
87 transform = CGAffineTransformRotate(
88 transform, (isPortrait ? kDefaultRotation : kLandscapeAnimateInRotation));
89 view.transform = transform;
90 }
91
92 void AnimateInPaperWithAnimationAndCompletion(UIView* view,
93 CGFloat paperOffset,
94 CGFloat contentOffset,
95 CGPoint origin,
96 BOOL isOffTheRecord,
97 void (^extraAnimation)(void),
98 void (^completion)(void)) {
99 CGRect endFrame = view.frame;
100 UIView* parent = [view superview];
101 NSInteger index = [[parent subviews] indexOfObject:view];
102
103 // Create paper background.
104 CGRect paperFrame = CGRectOffset(endFrame, 0, paperOffset);
105 paperFrame.size.height -= paperOffset;
106 base::scoped_nsobject<PaperView> paper(
107 [[PaperView alloc] initWithFrame:paperFrame]);
108 [parent insertSubview:paper belowSubview:view];
109 [paper addSubview:view];
110 [paper setBackgroundColor:isOffTheRecord
111 ? [UIColor colorWithWhite:34 / 255 alpha:1]
112 : [UIColor whiteColor]];
113
114 [CATransaction begin];
115 [CATransaction setCompletionBlock:^{
116
117 // Put view back where it belongs, with its original frame.
118 [parent insertSubview:view atIndex:index];
119 [paper removeFromSuperview];
120 [[view layer] removeAnimationForKey:kViewAnimateInKey];
121 view.frame = endFrame;
122 if (completion)
123 completion();
124 }];
125 [CATransaction setAnimationDuration:ios::material::kDuration5];
126 CAMediaTimingFunction* transformCurve2 = ios::material::TransformCurve2();
127
128 // // Animate paper to full size.
129 CABasicAnimation* scaleAnimation =
130 [CABasicAnimation animationWithKeyPath:@"transform"];
131 scaleAnimation.fromValue =
132 [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.03, 0.03, 1)];
133 scaleAnimation.timingFunction = transformCurve2;
134 scaleAnimation.duration = ios::material::kDuration1;
135
136 CABasicAnimation* positionAnimation =
137 [CABasicAnimation animationWithKeyPath:@"position"];
138 positionAnimation.fromValue = [NSValue valueWithCGPoint:origin];
139 positionAnimation.timingFunction = transformCurve2;
140 positionAnimation.duration = ios::material::kDuration1;
141
142 CAAnimation* fadeAnimation = OpacityAnimationMake(0, 1);
143 fadeAnimation.timingFunction = transformCurve2;
144 fadeAnimation.duration = ios::material::kDuration1;
145 [[paper layer]
146 addAnimation:AnimationGroupMake(
147 @[ scaleAnimation, positionAnimation, fadeAnimation ])
148 forKey:kPaperAnimateInKey];
149
150 // Animate content from -10px to full size, as a child of the paper parent.
151 // At the half-way point, the child will be offset / 2 vertically higher than
152 // the paper parent, but be sure to account for paperOriginYOffset, which
153 // allows for pages to draw above |parent| (as the new tab page does).
154 CGFloat offset = -10;
155 CGFloat width = endFrame.size.width;
156 CGFloat height = endFrame.size.height - contentOffset;
157 CGRect startFrame = CGRectMake(0, offset, width, height);
158 CGRect middleFrame =
159 CGRectMake(0, offset / 2 - paperOffset + contentOffset, width, height);
160 CGRect childEndFrame =
161 CGRectMake(0, -paperOffset + contentOffset, width, height);
162
163 CAAnimation* frameAnimation =
164 FrameAnimationMake([view layer], startFrame, middleFrame);
165 frameAnimation.timingFunction = transformCurve2;
166 frameAnimation.duration = ios::material::kDuration1;
167 frameAnimation.fillMode = kCAFillModeBackwards;
168
169 CAMediaTimingFunction* fadeInCurve =
170 TimingFunction(ios::material::CurveEaseOut);
171 CAAnimation* frameAnimation2 =
172 FrameAnimationMake([view layer], middleFrame, childEndFrame);
173 frameAnimation2.timingFunction = fadeInCurve;
174 frameAnimation2.duration = ios::material::kDuration1;
175 frameAnimation2.beginTime = ios::material::kDuration1;
176 frameAnimation2.fillMode = kCAFillModeForwards;
177
178 fadeAnimation = OpacityAnimationMake(0, 1);
179 fadeAnimation.timingFunction = fadeInCurve;
180 fadeAnimation.duration = ios::material::kDuration5;
181 [[view layer]
182 addAnimation:AnimationGroupMake(
183 @[ frameAnimation, frameAnimation2, fadeAnimation ])
184 forKey:kViewAnimateInKey];
185
186 [CATransaction commit];
187 }
188
189 void AnimateInCardWithAnimationAndCompletion(UIView* view,
190 void (^extraAnimation)(void),
191 void (^completion)(void)) {
192 SetNewTabAnimationStartPositionForView(view, true);
193 [UIView animateWithDuration:kAnimationDuration
194 delay:0
195 options:UIViewAnimationCurveEaseOut
196 animations:^{
197 view.transform = CGAffineTransformIdentity;
198 if (extraAnimation)
199 extraAnimation();
200 }
201 completion:^(BOOL finished) {
202 if (completion)
203 completion();
204 }];
205 }
206
207 void AnimateNewBackgroundPageWithCompletion(CardView* currentPageCard,
208 CGRect displayFrame,
209 BOOL isPortrait,
210 void (^completion)(void)) {
211 // Create paper background.
212 base::scoped_nsobject<PaperView> paper(
213 [[PaperView alloc] initWithFrame:CGRectZero]);
214 UIView* parent = [currentPageCard superview];
215 [parent insertSubview:paper aboveSubview:currentPageCard];
216 CGRect pageBounds = currentPageCard.bounds;
217 [paper setCenter:CGPointMake(CGRectGetMidX(pageBounds),
218 CGRectGetMidY(pageBounds))];
219 [paper setBackgroundColor:[UIColor whiteColor]];
220 [paper setAlpha:0.0];
221
222 CGSize pageSize = currentPageCard.bounds.size;
223 CGRect paperFrame =
224 CGRectMake((displayFrame.size.width - pageSize.width) / 2,
225 CGRectGetMidY(pageBounds), pageSize.width, pageSize.height);
226
227 // The animation of the current page during the new background card animation
228 // has three parts:
229 // 1. It shrinks the current tab image into an inset card view.
230 // 2. It hesitates for a fraction of a second.
231 // 3. It expands back out, transforming again into the current tab.
232 // |currentPageCard| gives the card at the correct size for step 2, as it
233 // appears in the slight hesitation. Here, the code creates the transform
234 // that will start by displaying the card at full size; the animation then
235 // moves the card into its original state, and back out to full screen size.
236 CGSize displaySize = displayFrame.size;
237 CGFloat fullScreenScale =
238 (displaySize.width + kCardImageInsets.left + kCardImageInsets.right +
239 kCardFrameImageSnapshotOverlap) /
240 currentPageCard.frame.size.width;
241 // Align the bottom of |currentPageCard|'s snapshot with the bottom of the
242 // screen, so that snapshots of any height are correctly aligned with the
243 // tab's content.
244 currentPageCard.center = CGPointMake(
245 displaySize.width / 2.0, displaySize.height -
246 (currentPageCard.image.size.height / 2.0) -
247 kCardImageInsets.top / 2);
248 CGAffineTransform fullScreenTransform =
249 CGAffineTransformMakeScale(fullScreenScale, fullScreenScale);
250 currentPageCard.transform = fullScreenTransform;
251 [currentPageCard setTabOpacity:0.0];
252
253 [CATransaction begin];
254 [CATransaction
255 setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseIn)];
256 [UIView animateWithDuration:kAnimationDuration
257 delay:0
258 options:UIViewAnimationCurveLinear
259 animations:^{
260 [currentPageCard setTabOpacity:1.0];
261 currentPageCard.transform = CGAffineTransformIdentity;
262 [paper setFrame:paperFrame];
263 [paper setAlpha:1.0];
264 }
265 completion:^(BOOL finished) {
266 // Zoom out the top tab, slide away the paper view.
267 [UIView animateWithDuration:kAnimationDuration
268 delay:kAnimationHesitation
269 options:UIViewAnimationCurveLinear
270 animations:^{
271 [currentPageCard setTabOpacity:0.0];
272 currentPageCard.transform = fullScreenTransform;
273 [paper setFrame:CGRectOffset(paperFrame, 0,
274 CGRectGetMaxY(paperFrame))];
275 [paper setAlpha:0.0];
276
277 }
278 completion:^(BOOL finished) {
279 [paper removeFromSuperview];
280 if (completion)
281 completion();
282 }];
283 }];
284 [CATransaction commit];
285 }
286
287 void AnimateNewBackgroundTabWithCompletion(CardView* currentPageCard,
288 CardView* newCard,
289 CGRect displayFrame,
290 BOOL isPortrait,
291 void (^completion)(void)) {
292 // The animation of the current page during the new background card animation
293 // has three parts:
294 // 1. It shrinks the current tab image into an inset card view.
295 // 2. It hesitates for a fraction of a second.
296 // 3. It expands back out, transforming again into the current tab.
297 // |currentPageCard| gives the card at the correct size for step 2, as it
298 // appears in the slight hesitation. Here, the code creates the transform
299 // that will start by displaying the card at full size; the animation then
300 // moves the card into its original state, and back out to full screen size.
301 CGSize displaySize = displayFrame.size;
302 CGFloat fullScreenScale =
303 (displaySize.width + kCardImageInsets.left + kCardImageInsets.right) /
304 currentPageCard.frame.size.width;
305 // Align the bottom of |currentPageCard|'s snapshot with the bottom of the
306 // screen, so that snapshots of any height are correctly aligned with the
307 // tab's content.
308 currentPageCard.center = CGPointMake(
309 displaySize.width / 2.0,
310 displaySize.height - (currentPageCard.image.size.height / 2.0));
311 CGAffineTransform fullScreenTransform =
312 CGAffineTransformMakeScale(fullScreenScale, fullScreenScale);
313 currentPageCard.transform = fullScreenTransform;
314 [currentPageCard setTabOpacity:0.0];
315
316 // The new background card animation has three parts:
317 // 1. It moves from offscreen onto the screen (in a "rotating" motion).
318 // 2. It hesitates for a fraction of a second, halfway on the screen.
319 // 3. It moves from the screen offscreen (in a "sliding" motion).
320 // In the setup code below, we position the card on the screen as it will
321 // appear in step 2; in the animation code, we then move it to and from this
322 // original onscreen position.
323 //
324 // In portrait mode, the card in step 2 appears to be halfway off the bottom
325 // edge of the screen; in landscape mode, it appears to be halfway off the
326 // right edge. The x and y offsets below set up this screen position.
327 CGFloat yOffset = -displayFrame.origin.y + kCardMargin +
328 (isPortrait ? displaySize.height / 2.0 : 0);
329 CGFloat xOffset = isPortrait ? kCardMargin : displaySize.width / 2.0;
330 CGRect newCardFrame = newCard.frame;
331 newCardFrame.origin.x += xOffset;
332 newCardFrame.origin.y += yOffset;
333 newCard.frame = newCardFrame;
334
335 // For step 1, we apply a transform to the card that moves it offscreen and
336 // rotates it away in preparation for the "rotate in" animation that starts
337 // any new tab appearance.
338 SetNewTabAnimationStartPositionForView(newCard, isPortrait);
339
340 // For step 3, we create a transform which will slide the card offscreen along
341 // its longer axis to end the animation.
342 CGAffineTransform slideAwayTransform =
343 isPortrait
344 ? CGAffineTransformMakeTranslation(0, newCard.frame.size.height)
345 : CGAffineTransformMakeTranslation(newCard.frame.size.width, 0);
346
347 [UIView animateWithDuration:kAnimationDuration
348 delay:0
349 options:UIViewAnimationCurveEaseOut
350 animations:^{
351 [currentPageCard setTabOpacity:1.0];
352 currentPageCard.transform = CGAffineTransformIdentity;
353 newCard.transform = CGAffineTransformIdentity;
354 }
355 completion:^(BOOL finished) {
356 // Zoom out the top tab, slide away the new card.
357 [UIView animateWithDuration:kAnimationDuration
358 delay:kAnimationHesitation
359 options:UIViewAnimationCurveEaseOut
360 animations:^{
361 [currentPageCard setTabOpacity:0.0];
362 currentPageCard.transform = fullScreenTransform;
363 newCard.transform = slideAwayTransform;
364 }
365 completion:^(BOOL finished) {
366 if (completion)
367 completion();
368 }];
369 }];
370 }
371
372 void UpdateLayorAnchorWithTransform(CALayer* layer,
373 CGPoint newAnchor,
374 CGAffineTransform transform) {
375 CGSize size = layer.bounds.size;
376 CGPoint oldAnchor = layer.anchorPoint;
377 CGPoint newCenter =
378 CGPointMake(size.width * newAnchor.x, size.height * newAnchor.y);
379 CGPoint oldCenter =
380 CGPointMake(size.width * oldAnchor.x, size.height * oldAnchor.y);
381
382 newCenter = CGPointApplyAffineTransform(newCenter, transform);
383 oldCenter = CGPointApplyAffineTransform(oldCenter, transform);
384
385 CGPoint position = layer.position;
386 position.x = position.x - oldCenter.x + newCenter.x;
387 position.y = position.y - oldCenter.y + newCenter.y;
388 layer.position = position;
389
390 layer.anchorPoint = newAnchor;
391 }
392
393 void AnimateOutWithCompletion(UIView* view,
394 NSTimeInterval delay,
395 BOOL clockwise,
396 BOOL isPortrait,
397 void (^completion)(void)) {
398 // The close animation spec calls for the anchor point to be the upper right.
399 CGPoint newAnchorPoint = CGPointMake(kAnimateOutAnchorX, kAnimateOutAnchorY);
400 CALayer* layer = [view layer];
401 UpdateLayorAnchorWithTransform(layer, newAnchorPoint, view.transform);
402
403 [CATransaction begin];
404 if (completion)
405 [CATransaction setCompletionBlock:completion];
406
407 [CATransaction setAnimationDuration:ios::material::kDuration6];
408 CAMediaTimingFunction* timing = TimingFunction(ios::material::CurveEaseIn);
409 [CATransaction setAnimationTimingFunction:timing];
410
411 CABasicAnimation* scaleAnimation =
412 [CABasicAnimation animationWithKeyPath:@"transform"];
413 CATransform3D transform = CATransform3DScale(
414 layer.transform, kAnimateOutScale, kAnimateOutScale, 1);
415 [scaleAnimation setToValue:[NSValue valueWithCATransform3D:transform]];
416
417 CABasicAnimation* fadeAnimation =
418 [CABasicAnimation animationWithKeyPath:@"opacity"];
419 [fadeAnimation setFromValue:[NSNumber numberWithFloat:[layer opacity]]];
420 [fadeAnimation setToValue:@0];
421
422 [layer addAnimation:AnimationGroupMake(@[ scaleAnimation, fadeAnimation ])
423 forKey:@"animateOut"];
424 [CATransaction commit];
425 }
426
427 CGAffineTransform AnimateOutTransform(CGFloat fraction,
428 BOOL clockwise,
429 BOOL isPortrait) {
430 CGFloat horizontalTranslation = isPortrait ? kDefaultShortSideAxisTranslation
431 : kDefaultLongSideAxisTranslation;
432 CGFloat verticalTranslation = isPortrait ? kDefaultLongSideAxisTranslation
433 : kDefaultShortSideAxisTranslation;
434 CGFloat rotationAmount = kDefaultRotation;
435
436 if (!isPortrait && UseRTLLayout()) {
437 rotationAmount *= -1;
438 horizontalTranslation *= -1;
439 }
440
441 horizontalTranslation *= fraction;
442 verticalTranslation *= fraction;
443 rotationAmount *= fraction;
444 if (!clockwise)
445 rotationAmount *= -1;
446
447 // In portrait, rotating counterclockwise pushes the animation to the left.
448 if (isPortrait && !clockwise) {
449 horizontalTranslation *= -1;
450 }
451
452 // In landscape, rotating clockwise pushes the animation up.
453 if (!isPortrait && clockwise) {
454 verticalTranslation *= -1;
455 }
456
457 // Scale the card between full-scale and the final desired scale based on
458 // |fraction|.
459 CGFloat differenceInScale = 1.0 - kAnimateOutScale;
460 CGFloat scaleAmount = 1.0 - (differenceInScale * fraction);
461 CGAffineTransform transform = CGAffineTransformMakeTranslation(
462 horizontalTranslation, verticalTranslation);
463 transform = CGAffineTransformRotate(transform, rotationAmount);
464 transform = CGAffineTransformScale(transform, scaleAmount, scaleAmount);
465 return transform;
466 }
467
468 CGFloat AnimateOutTransformBreadth() {
469 return kDefaultShortSideAxisTranslation;
470 }
471
472 } // namespace page_animation_util
473
474 } // namespace ios_internal
OLDNEW
« no previous file with comments | « ios/chrome/browser/ui/stack_view/page_animation_util.h ('k') | ios/chrome/browser/ui/stack_view/stack_card.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698