寫這個demo緣于一次公司內部技術的分享,一直以來在閑余時間都會去研究各個精選app的一些內部交互方式,順便也會去猜測每個產品設計種種很炫的交互的初衷,今天就拿印象筆記的的轉場和彈簧效果就此剖析一下,期間我也在網上找了一些該方面的資料和類似的demo,經過一天的整合,寫了如下和大伙共同交流的文檔,有好的意見和建議隨時@me,謝謝!
當collection view的布局改變時,我們自定義的布局必須被丟棄,但這滾動并不會影響到布局。幸運的是,collection view將它的新bounds傳給shouldInvalidateLayoutForBoundsChange: method。這樣我們便能比較視圖當前的bounds和新的bounds:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
這個動畫只在collectionview滑動到頂部和底部會觸發,重寫layoutAttributesForElementsInRect這個方法根據collectionview的contentoffset計算出cell的frame,這樣就ok了。
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let offsetY = self.collectionView!.contentOffset.y
let attrsArray = super.layoutAttributesForElementsInRect(rect)
let collectionViewFrameHeight = self.collectionView!.frame.size.height;
let collectionViewContentHeight = self.collectionView!.contentSize.height;
let ScrollViewContentInsetBottom = self.collectionView!.contentInset.bottom;
let bottomOffset = offsetY + collectionViewFrameHeight - collectionViewContentHeight - ScrollViewContentInsetBottom
let numOfItems = self.collectionView!.numberOfSections()
for attr:UICollectionViewLayoutAttributes in attrsArray! {
if (attr.representedElementCategory == UICollectionElementCategory.Cell) {
var cellRect = attr.frame;
if offsetY <= 0 {
let distance = fabs(offsetY) / SpringFactor;
cellRect.origin.y += offsetY + distance * CGFloat(attr.indexPath.section + 1);
}else if bottomOffset > 0 {
let distance = bottomOffset / SpringFactor;
cellRect.origin.y += bottomOffset - distance *
CGFloat(numOfItems - attr.indexPath.section)
}
attr.frame = cellRect;
}
}
return attrsArray;
}
轉場效果:
自定義類EvernoteTransition遵守UIViewControllerAnimated
Transitioning和UIViewControllerTransitioningDelegate,
該類的對象作為transitioningDelegate。實現
UIViewControllerAnimatedTransitioning中
的transitionDuration和animateTransition方法前者返回動畫的時間,后者用來實現轉場時的具體動畫。
在UIViewControllerTransitioningDelegate的present和dismiss代理方法中返回EvernoteTransition對象,這樣就ok了
#pragma mark - UIViewControllerAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController *nextVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[transitionContext containerView].backgroundColor = kBGColor;
_selectClell.frame = _isPresent ? _originFrame : _finalFrame;
UIView *addView = nextVC.view;
addView.hidden = _isPresent ? YES : NO;
[[transitionContext containerView] addSubview:addView];
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
for (CollectionViewCell *visibleCell in self.visibleCells) {
if (visibleCell != self.selectClell) {
CGRect frame = visibleCell.frame;
if (visibleCell.tag < self.selectClell.tag) {
CGFloat yDistance = self.originFrame.origin.y - self.finalFrame.origin.y + 30;
CGFloat yUpdate = self.isPresent ? yDistance : -yDistance;
frame.origin.y -= yUpdate;
} else if (visibleCell.tag > self.selectClell.tag){
CGFloat yDistance = CGRectGetMaxY(self.finalFrame) - CGRectGetMaxY(self.originFrame) + 30;
CGFloat yUpdate = self.isPresent ? yDistance : -yDistance;
frame.origin.y += yUpdate;
}
visibleCell.frame = frame;
visibleCell.transform = self.isPresent ? CGAffineTransformMakeScale(0.8, 1.0) : CGAffineTransformIdentity;
}
}
self.selectClell.backButton.alpha = self.isPresent ? 1.0 : 0.0;
self.selectClell.titleLine.alpha = self.isPresent ? 1.0 : 0.0;
self.selectClell.textView.alpha = self.isPresent ? 1.0 : 0.0;
self.selectClell.frame = self.isPresent ? self.finalFrame : self.originFrame;
[self.selectClell layoutIfNeeded];
} completion:^(BOOL finished) {
addView.hidden = YES;
[transitionContext completeTransition:YES];
}];
}
#pragma mark - UIViewControllerAnimatedTransitionDelegate
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
self.isPresent = YES;
self.selectClell.textView.scrollEnabled = NO;
return self;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
self.isPresent = NO;
return self;
}
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator {
return self.interactionController;
}