Commit 165dadf6 authored by Mark Cogan's avatar Mark Cogan Committed by Commit Bot

[iOS] Animated transition from tab to grid.

This CL implements an imperfect but serviceable-for-now transition animator
used when entering the tab grid.

Most of the logic for building and running the animation is in the animator
class. However, to set the animation up, the tab grid needs to vend some
information. This is done through the TabGridTransitionStateProvider protocol,
which the TabGridViewController implements.

Most of this information is handled by the -transitionGridLayout method, which
returns an array of dictionaries with predefined keys. Some of the values are
collection cells, which GridCell provides (a grid cell can now provide a "proxy"
version of itself for this purpose). These are then used to build the animation.

Because the grid animation really needs to happen *behind* the grid toolbars
to look right, the state provider protocol includes two somewhat janky methods
to expose views to make this possible -- a view to add the transition cells to,
and a view in that view to put them above. The implementations of these methods
return the TabGridViewController's view, and it's scroll view, respectively.

This means the animator is adding and removing stuff from the view hierarchy
of a view controller, which isn't ideal. The alternative is to also proxy the
tab grid toolbars in the transition animation, which will require a bit more
plumbing.

There are a number of fit-and-finish cleanups that need to happen; a separate
task bug has been created for them.

Bug: 804538
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: Ifde8bb27b70ce350e40eeb1e1b14675579f84d7d
Reviewed-on: https://chromium-review.googlesource.com/957039
Commit-Queue: Mark Cogan <marq@chromium.org>
Reviewed-by: default avataredchin <edchin@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#542757}
parent 376776f1
......@@ -58,7 +58,6 @@ source_set("tab_grid_ui") {
"tab_grid_top_toolbar.mm",
"tab_grid_transition_handler.h",
"tab_grid_transition_handler.mm",
"tab_grid_transition_state_provider.h",
"tab_grid_view_controller.h",
"tab_grid_view_controller.mm",
"top_aligned_image_view.h",
......
......@@ -30,6 +30,11 @@
@property(nonatomic, weak) UIImage* icon;
@property(nonatomic, weak) UIImage* snapshot;
@property(nonatomic, copy) NSString* title;
// Returns a cell with the same theme, icon, snapshot, and title as the reciever
// (but no delegate or identifier) for use in animated transitions.
- (GridCell*)proxyForTransitions;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_GRID_GRID_CELL_H_
......@@ -176,6 +176,16 @@ const CGFloat kBorderWidth = 6.0f;
_title = title;
}
- (GridCell*)proxyForTransitions {
GridCell* proxy = [[[self class] alloc] initWithFrame:self.bounds];
proxy.selected = NO;
proxy.theme = self.theme;
proxy.icon = self.icon;
proxy.snapshot = self.snapshot;
proxy.title = self.title;
return proxy;
}
#pragma mark - Private
// Sets up the top bar with icon, title, and close button.
......
......@@ -11,6 +11,7 @@
#import "ios/chrome/browser/ui/tab_grid/grid_theme.h"
@protocol GridImageDataSource;
@class GridTransitionLayout;
@class GridViewController;
// Protocol used to relay relevant user interactions from a grid UI.
......@@ -45,6 +46,13 @@
@property(nonatomic, weak) id<GridViewControllerDelegate> delegate;
// Data source for images.
@property(nonatomic, weak) id<GridImageDataSource> imageDataSource;
// YES if the selected cell is visible in the grid.
@property(nonatomic, readonly, getter=isSelectedCellVisible)
BOOL selectedCellVisible;
// Returns the layout of the grid for use in an animated transition.
- (GridTransitionLayout*)transitionLayout;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_GRID_GRID_VIEW_CONTROLLER_H_
......@@ -13,6 +13,7 @@
#import "ios/chrome/browser/ui/tab_grid/grid_image_data_source.h"
#import "ios/chrome/browser/ui/tab_grid/grid_item.h"
#import "ios/chrome/browser/ui/tab_grid/grid_layout.h"
#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_layout.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
......@@ -106,6 +107,39 @@ NSIndexPath* CreateIndexPath(NSInteger index) {
return self.items.count == 0;
}
- (BOOL)isSelectedCellVisible {
if (self.collectionView.indexPathsForSelectedItems.count == 0)
return NO;
return [self.collectionView.indexPathsForVisibleItems
containsObject:self.collectionView.indexPathsForSelectedItems
.firstObject];
}
- (GridTransitionLayout*)transitionLayout {
[self.collectionView layoutIfNeeded];
NSMutableArray<GridTransitionLayoutItem*>* items =
[[NSMutableArray alloc] init];
GridTransitionLayoutItem* selectedItem;
for (NSIndexPath* path in self.collectionView.indexPathsForVisibleItems) {
GridCell* cell = base::mac::ObjCCastStrict<GridCell>(
[self.collectionView cellForItemAtIndexPath:path]);
UICollectionViewLayoutAttributes* attributes =
[self.collectionView layoutAttributesForItemAtIndexPath:path];
// Normalize frame to window coordinates. The attributes class applies this
// change to the other properties such as center, bounds, etc.
attributes.frame =
[self.collectionView convertRect:attributes.frame toView:nil];
GridTransitionLayoutItem* item =
[GridTransitionLayoutItem itemWithCell:[cell proxyForTransitions]
attributes:attributes];
[items addObject:item];
if (cell.selected) {
selectedItem = item;
}
}
return [GridTransitionLayout layoutWithItems:items selectedItem:selectedItem];
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView*)collectionView
......
......@@ -85,6 +85,7 @@
[[TabGridViewController alloc] init];
self.transitionHandler = [[TabGridTransitionHandler alloc] init];
self.transitionHandler.provider = mainViewController;
mainViewController.modalPresentationStyle = UIModalPresentationCustom;
mainViewController.transitioningDelegate = self.transitionHandler;
mainViewController.tabPresentationDelegate = self;
_mainViewController = mainViewController;
......
......@@ -7,12 +7,12 @@
#import <UIKit/UIKit.h>
@protocol TabGridTransitionStateProvider;
@protocol GridTransitionStateProviding;
@interface TabGridTransitionHandler
: NSObject<UIViewControllerTransitioningDelegate>
@property(nonatomic, weak) id<TabGridTransitionStateProvider> provider;
@property(nonatomic, weak) id<GridTransitionStateProviding> provider;
@end
......
......@@ -4,9 +4,9 @@
#import "ios/chrome/browser/ui/tab_grid/tab_grid_transition_handler.h"
#import "ios/chrome/browser/ui/tab_grid/tab_grid_transition_state_provider.h"
#import "ios/chrome/browser/ui/tab_grid/transitions/grid_to_hidden_tab_animator.h"
#import "ios/chrome/browser/ui/tab_grid/transitions/grid_to_visible_tab_animator.h"
#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_state_providing.h"
#import "ios/chrome/browser/ui/tab_grid/transitions/tab_to_grid_animator.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
......@@ -24,7 +24,7 @@ animationControllerForPresentedController:(UIViewController*)presented
presentingController:(UIViewController*)presenting
sourceController:(UIViewController*)source {
id<UIViewControllerAnimatedTransitioning> animator;
if (self.provider.selectedTabVisible) {
if (self.provider.selectedCellVisible) {
// This will be a GridToVisibleTabAnimator eventually.
animator = [[GridToHiddenTabAnimator alloc] init];
} else {
......@@ -35,8 +35,7 @@ animationControllerForPresentedController:(UIViewController*)presented
- (id<UIViewControllerAnimatedTransitioning>)
animationControllerForDismissedController:(UIViewController*)dismissed {
// This will be a TabToGridAnimator eventually.
return nil;
return [[TabToGridAnimator alloc] initWithStateProvider:self.provider];
}
@end
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_TRANSITION_STATE_PROVIDER_H_
#define IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_TRANSITION_STATE_PROVIDER_H_
#import <Foundation/Foundation.h>
// Objects conforming to this protocol can provide state information to
// transition delegates and animators for the tab grid.
@protocol TabGridTransitionStateProvider
// YES if the currently selected tab is visible in the tab grid.
@property(nonatomic, readonly) BOOL selectedTabVisible;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_TRANSITION_STATE_PROVIDER_H_
......@@ -8,7 +8,7 @@
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/tab_grid/tab_grid_paging.h"
#import "ios/chrome/browser/ui/tab_grid/tab_grid_transition_state_provider.h"
#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_state_providing.h"
@protocol GridConsumer;
@protocol GridCommands;
......@@ -25,10 +25,10 @@ extern NSString* const kTabGridDoneButtonAccessibilityID;
- (void)showActiveTab;
@end
// View controller representing a tab switcher. The tab switcher has an
// View controller representing a tab switcher. The tab switcher has an
// incognito tab grid, regular tab grid, and remote tabs.
@interface TabGridViewController
: UIViewController<TabGridPaging, TabGridTransitionStateProvider>
: UIViewController<TabGridPaging, GridTransitionStateProviding>
// Delegate for this view controller to handle presenting tab UI.
@property(nonatomic, weak) id<TabPresentationDelegate> tabPresentationDelegate;
......
......@@ -142,10 +142,39 @@ NSString* const kTabGridDoneButtonAccessibilityID =
}
}
#pragma mark - TabGridTransitionStateProvider properties
#pragma mark - GridTransitionStateProviding properties
- (BOOL)selectedTabVisible {
return NO;
- (BOOL)isSelectedCellVisible {
switch (self.currentPage) {
case TabGridPageIncognitoTabs:
return self.incognitoTabsViewController.selectedCellVisible;
case TabGridPageRegularTabs:
return self.regularTabsViewController.selectedCellVisible;
case TabGridPageRemoteTabs:
return NO;
}
}
- (GridTransitionLayout*)layoutForTransitionContext:
(id<UIViewControllerContextTransitioning>)context {
switch (self.currentPage) {
case TabGridPageIncognitoTabs:
return [self.incognitoTabsViewController transitionLayout];
case TabGridPageRegularTabs:
return [self.regularTabsViewController transitionLayout];
case TabGridPageRemoteTabs:
return nil;
}
}
- (UIView*)proxyContainerForTransitionContext:
(id<UIViewControllerContextTransitioning>)context {
return self.view;
}
- (UIView*)proxyPositionForTransitionContext:
(id<UIViewControllerContextTransitioning>)context {
return self.scrollView;
}
#pragma mark - Public
......@@ -458,29 +487,38 @@ NSString* const kTabGridDoneButtonAccessibilityID =
// interact with the layout system at all.
- (void)animateToolbarsForAppearance {
DCHECK(self.transitionCoordinator);
// TODO(crbug.com/820410): Tune the timing of these animations.
// Capture the current toolbar transforms.
CGAffineTransform topToolbarBaseTransform = self.topToolbar.transform;
CGAffineTransform bottomToolbarBaseTransform = self.bottomToolbar.transform;
// Translate the top toolbar up offscreen by shifting it up by its height.
self.topToolbar.transform =
CGAffineTransformTranslate(self.topToolbar.transform, /*tx=*/0,
/*ty=*/-self.topToolbar.bounds.size.height);
self.topToolbar.transform = CGAffineTransformTranslate(
self.topToolbar.transform, /*tx=*/0,
/*ty=*/-(self.topToolbar.bounds.size.height * 0.5));
// Translate the bottom toolbar down offscreen by shifting it down by its
// height.
self.bottomToolbar.transform =
CGAffineTransformTranslate(self.bottomToolbar.transform, /*tx=*/0,
/*ty=*/self.topToolbar.bounds.size.height);
self.bottomToolbar.transform = CGAffineTransformTranslate(
self.bottomToolbar.transform, /*tx=*/0,
/*ty=*/(self.topToolbar.bounds.size.height * 0.5));
// Block that restores the toolbar transforms, suitable for using with the
// transition coordinator.
void (^animation)(id<UIViewControllerTransitionCoordinatorContext>) =
^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.topToolbar.transform = topToolbarBaseTransform;
self.bottomToolbar.transform = bottomToolbarBaseTransform;
};
// Animate the toolbars into place alongside the current transition by
// restoring their transforms.
auto animation = ^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.topToolbar.transform = topToolbarBaseTransform;
self.bottomToolbar.transform = bottomToolbarBaseTransform;
};
// Also hide the scroll view (and thus the tab grids) until the transition
// completes.
self.scrollView.hidden = YES;
auto cleanup = ^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.scrollView.hidden = NO;
};
// Animate the toolbars into place alongside the current transition.
[self.transitionCoordinator animateAlongsideTransition:animation
completion:nil];
completion:cleanup];
}
// Update |enabled| property of the done and close all buttons.
......
......@@ -10,6 +10,9 @@ source_set("transitions") {
"grid_to_hidden_tab_animator.mm",
"grid_to_visible_tab_animator.h",
"grid_to_visible_tab_animator.mm",
"grid_transition_layout.h",
"grid_transition_layout.mm",
"grid_transition_state_providing.h",
"tab_to_grid_animator.h",
"tab_to_grid_animator.mm",
]
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_GRID_TRANSITION_LAYOUT_H_
#define IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_GRID_TRANSITION_LAYOUT_H_
#import <UIKit/UIKit.h>
@class GridTransitionLayoutItem;
// An encapsulation of information for the layout of a grid of cells that will
// be used in an animated transition. The layout object is composed of layout
// items (see below).
@interface GridTransitionLayout : NSObject
// All of the items in the layout.
@property(nonatomic, copy, readonly) NSArray<GridTransitionLayoutItem*>* items;
// The item in the layout (if any) that's selected.
// Note that |selectedItem.cell.selected| doesn't need to be YES; the transition
// animation may set or unset that selection state as part of the animation.
@property(nonatomic, strong, readonly) GridTransitionLayoutItem* selectedItem;
// Creates a new layout object with |items|, and |selectedItem| selected.
// |items| should be non-nil, but it may be empty.
// |selectedItem| must either be nil, or one of the members of |items|.
+ (instancetype)layoutWithItems:(NSArray<GridTransitionLayoutItem*>*)items
selectedItem:(GridTransitionLayoutItem*)selectedItem;
@end
// An encapsulation of information for the layout of a single grid cell, in the
// form of UICollectionView classes.
@interface GridTransitionLayoutItem : NSObject
// A cell object with the desired appearance for the animation. This should
// correspond to an actual cell in the collection view involved in the trans-
// ition, but the value of thie property should not be in any view hierarchy
// when the layout item is created.
@property(nonatomic, strong, readonly) UICollectionViewCell* cell;
// The layout attributes for the cell in the collection view, normalized to
// UIWindow coordinates. It's the responsibility of the setter to do this
// normalization.
@property(nonatomic, strong, readonly)
UICollectionViewLayoutAttributes* attributes;
// Creates a new layout item instance will |cell| and |attributes|, neither of
// which can be nil.
// It's an error if |cell| has a superview.
// The properties (size, etc) of |attributes| don't need to match the corres-
// ponding properties of |cell| when the item is created.
+ (instancetype)itemWithCell:(UICollectionViewCell*)cell
attributes:(UICollectionViewLayoutAttributes*)attributes;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_GRID_TRANSITION_LAYOUT_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_layout.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#include "base/logging.h"
@interface GridTransitionLayout ()
@property(nonatomic, readwrite) NSArray<GridTransitionLayoutItem*>* items;
@property(nonatomic, readwrite) GridTransitionLayoutItem* selectedItem;
@end
@implementation GridTransitionLayout
@synthesize selectedItem = _selectedItem;
@synthesize items = _items;
+ (instancetype)layoutWithItems:(NSArray<GridTransitionLayoutItem*>*)items
selectedItem:(GridTransitionLayoutItem*)selectedItem {
DCHECK(items);
GridTransitionLayout* layout = [[GridTransitionLayout alloc] init];
layout.items = items;
layout.selectedItem = selectedItem;
return layout;
}
- (void)setSelectedItem:(GridTransitionLayoutItem*)selectedItem {
DCHECK([self.items containsObject:selectedItem]);
_selectedItem = selectedItem;
}
@end
@interface GridTransitionLayoutItem ()
@property(nonatomic, readwrite) UICollectionViewCell* cell;
@property(nonatomic, readwrite) UICollectionViewLayoutAttributes* attributes;
@end
@implementation GridTransitionLayoutItem
@synthesize cell = _cell;
@synthesize attributes = _attributes;
+ (instancetype)itemWithCell:(UICollectionViewCell*)cell
attributes:(UICollectionViewLayoutAttributes*)attributes {
DCHECK(cell);
DCHECK(attributes);
DCHECK(!cell.superview);
GridTransitionLayoutItem* item = [[GridTransitionLayoutItem alloc] init];
item.cell = cell;
item.attributes = attributes;
return item;
}
@end
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_GRID_TRANSITION_STATE_PROVIDING_H_
#define IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_GRID_TRANSITION_STATE_PROVIDING_H_
#import <Foundation/Foundation.h>
@class GridTransitionLayout;
// Objects conforming to this protocol can provide state information to
// transition delegates and animators for a grid.
@protocol GridTransitionStateProviding
// YES if the currently selected cell is visible in the grid.
@property(nonatomic, readonly, getter=isSelectedCellVisible)
BOOL selectedCellVisible;
// Asks the provider for an aray of layout items that provide objects for use
// in building an animated transition.
- (GridTransitionLayout*)layoutForTransitionContext:
(id<UIViewControllerContextTransitioning>)context;
// Asks the provider for the view to add proxy views to when building an
// animated transition.
- (UIView*)proxyContainerForTransitionContext:
(id<UIViewControllerContextTransitioning>)context;
// Asks the provider for the view (if any) that proxy views should be added
// in front of when building an animated transition. It's an error if this
// view is not nil and isn't an immediate subview of the view returned by
// |-proxyContainerForTransitionContext:|
- (UIView*)proxyPositionForTransitionContext:
(id<UIViewControllerContextTransitioning>)context;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_TAB_GRID_TRANSITION_STATE_PROVIDING_H_
......@@ -7,8 +7,17 @@
#import <UIKit/UIKit.h>
@protocol GridTransitionStateProviding;
// Animator object for transitioning from a fullscreen view controller (the
// "tab") into a collection view of square-ish items (the "grid").
@interface TabToGridAnimator : NSObject<UIViewControllerAnimatedTransitioning>
// Initialize an animator object with |stateProvider| to provide state
// information for the transition.
- (instancetype)initWithStateProvider:
(id<GridTransitionStateProviding>)stateProvider;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_TAB_TO_GRID_ANIMATOR_H_
......@@ -4,19 +4,191 @@
#import "ios/chrome/browser/ui/tab_grid/transitions/tab_to_grid_animator.h"
#import "base/logging.h"
#import "base/mac/foundation_util.h"
#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_layout.h"
#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_state_providing.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface TabToGridAnimator ()
// State provider for this transition.
@property(nonatomic, weak) id<GridTransitionStateProviding> stateProvider;
@end
@implementation TabToGridAnimator
@synthesize stateProvider = _stateProvider;
- (instancetype)initWithStateProvider:
(id<GridTransitionStateProviding>)stateProvider {
if ((self = [super init])) {
_stateProvider = stateProvider;
}
return self;
}
- (NSTimeInterval)transitionDuration:
(id<UIViewControllerContextTransitioning>)transitionContext {
return 0.25;
return 0.4;
}
- (void)animateTransition:
(id<UIViewControllerContextTransitioning>)transitionContext {
// Get views and view controllers for this transition.
UIView* containerView = [transitionContext containerView];
UIViewController* gridViewController = [transitionContext
viewControllerForKey:UITransitionContextToViewControllerKey];
UIView* gridView =
[transitionContext viewForKey:UITransitionContextToViewKey];
UIView* dismissingView =
[transitionContext viewForKey:UITransitionContextFromViewKey];
// Extract some useful metrics from the tab view.
CGSize proxySize = dismissingView.bounds.size;
CGPoint proxyCenter = dismissingView.center;
// Add the grid view to the container. This isn't just for the transition;
// this is how the grid view controller's view is added to the view
// hierarchy.
[containerView insertSubview:gridView belowSubview:dismissingView];
gridView.frame =
[transitionContext finalFrameForViewController:gridViewController];
// Get the layout of the grid for the transition.
GridTransitionLayout* layout =
[self.stateProvider layoutForTransitionContext:transitionContext];
// Compute the scale of the transition grid (which is at the propotional size
// of the actual tab view.
CGFloat xScale = proxySize.width / layout.selectedItem.attributes.size.width;
CGFloat yScale =
proxySize.height / layout.selectedItem.attributes.size.height;
// Ask the state provider for the views to use when inserting the tab grid.
UIView* proxyContainer =
[self.stateProvider proxyContainerForTransitionContext:transitionContext];
UIView* viewBehindProxies =
[self.stateProvider proxyPositionForTransitionContext:transitionContext];
// Lay out the transition grid and add it to the view hierarchy.
CGFloat finalSelectedCellCornerRadius = 0.0;
for (GridTransitionLayoutItem* item in layout.items) {
// The state provider vends attributes in UIWindow coordinates.
// Find where this item is located in |proxyContainer|'s coordinate.
CGPoint gridCenter =
[proxyContainer convertPoint:item.attributes.center fromView:nil];
// Map that to the scale and position of the transition grid.
CGPoint center = CGPointMake(
proxyCenter.x +
((gridCenter.x - layout.selectedItem.attributes.center.x) * xScale),
proxyCenter.y +
((gridCenter.y - layout.selectedItem.attributes.center.y) *
yScale));
UICollectionViewCell* cell = item.cell;
cell.bounds = item.attributes.bounds;
// Add a scale transform to the cell so it matches the x-scale of the
// open tab.
cell.transform = CGAffineTransformScale(cell.transform, xScale, xScale);
cell.center = center;
if (item == layout.selectedItem) {
finalSelectedCellCornerRadius = cell.contentView.layer.cornerRadius;
cell.contentView.layer.cornerRadius = 0.0;
}
// Add the cell into the container for the transition.
[proxyContainer insertSubview:cell aboveSubview:viewBehindProxies];
}
// The transition is structured as four separate animations. Three of them
// are timed based on |staggeredDuration|, which is a configurable fraction
// of the overall animation duration.
// (1) Fading out the view being dismissed. This happens during the first 20%
// of the overall animation.
// (2) Zooming the selected cell into position. This starts immediately and
// has a duration of |staggeredDuration|.
// (3) Fading in the selected cell highlight indicator. This starts after a
// delay of |staggeredDuration| and runs to the end of the transition.
// This means it starts as soon as (2) ends.
// (4) Zooming all other cells into position. This ends at the end of the
// transition and has a duration of |staggeredDuration|.
//
// Animation (4) always runs the whole duration of the transition, so it's
// where the completion block that does overall cleanup is run.
// TODO(crbug.com/804539): Factor all of these animations into a single
// Orchestrator object that the present and dismiss animation can both use.
// TODO(crbug.com/820410): Tune the timing, relative pacing, and curves of
// these animations.
NSTimeInterval duration = [self transitionDuration:transitionContext];
CGFloat staggeredDuration = duration * 0.7;
// (1) Fade out active tab view.
[UIView animateWithDuration:duration / 5
animations:^{
dismissingView.alpha = 0;
}
completion:nil];
// (2) Zoom selected cell into place. Also round its corners.
UICollectionViewCell* selectedCell = layout.selectedItem.cell;
[UIView animateWithDuration:staggeredDuration
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
selectedCell.center = [containerView
convertPoint:layout.selectedItem.attributes.center
fromView:nil];
selectedCell.bounds =
layout.selectedItem.attributes.bounds;
selectedCell.transform = CGAffineTransformIdentity;
selectedCell.contentView.layer.cornerRadius =
finalSelectedCellCornerRadius;
}
completion:nil];
// (3) Show highlight state on selected cell.
[UIView animateWithDuration:duration - staggeredDuration
delay:staggeredDuration
options:UIViewAnimationOptionCurveEaseIn
animations:^{
selectedCell.selected = YES;
}
completion:nil];
// (4) Zoom other cells into place.
[UIView animateWithDuration:staggeredDuration
delay:duration - staggeredDuration
options:UIViewAnimationOptionCurveEaseOut
animations:^{
for (GridTransitionLayoutItem* item in layout.items) {
if (item == layout.selectedItem)
continue;
UIView* cell = item.cell;
cell.center =
[containerView convertPoint:item.attributes.center fromView:nil];
cell.transform = CGAffineTransformIdentity;
}
}
completion:^(BOOL finished) {
// Clean up all of the proxy cells.
for (GridTransitionLayoutItem* item in layout.items) {
[item.cell removeFromSuperview];
}
// If the transition was cancelled, restore the dismissing view and
// remove the grid view.
// If not, remove the dismissing view.
if (transitionContext.transitionWasCancelled) {
dismissingView.alpha = 1.0;
[gridView removeFromSuperview];
} else {
[dismissingView removeFromSuperview];
}
// Mark the transition as completed.
[transitionContext completeTransition:YES];
}];
}
@end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment