UITabBar 点击动画与切换 VC 动画实现
假如一个 APP 能带来良好的交互体验,那么它一定是”活的”。
效果图
show me the code.
tabBarController 切换 selectedViewController 的动画添加
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
NSUInteger shouldSelectIndex = [tabBarController.viewControllers indexOfObject:viewController];
if (tabBarController.selectedIndex == shouldSelectIndex) {
return YES;
}
CATransition *animation = [CATransition animation];
animation.duration = 0.3;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.type = kCATransitionPush;
if (tabBarController.selectedIndex > shouldSelectIndex) {
animation.subtype = kCATransitionFromLeft;
} else {
animation.subtype = kCATransitionFromRight;
}
[[[tabBarController valueForKey:@"_viewControllerTransitionView"] layer] addAnimation:animation forKey:@"animation"];
return YES;
}
点击 UITabBarItem添加动画效果
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
NSInteger index = [self.tabBar.items indexOfObject:item];
[self animationWithIndex:index];
}
#pragma mark - private methods
- (void)animationWithIndex:(NSInteger)index {
NSMutableArray *tabBarbuttonArray = [NSMutableArray array];
// 找到所有的 UITabBarButton
for (UIView *tabBarButton in self.tabBar.subviews) {
if ([tabBarButton isKindOfClass:NSClassFromString(@"UITabBarButton")]) {
[tabBarbuttonArray addObject:tabBarButton];
}
}
// tabBarButton 排序
[tabBarbuttonArray sortUsingComparator:^NSComparisonResult(UIView *_Nonnull obj1, UIView *_Nonnull obj2) {
return CGRectGetMinX(obj1.frame) > CGRectGetMinX(obj2.frame);
}];
UIView *tabbarBtn = tabBarbuttonArray[index];
// 找到UITabBarButton中的imageView,加动画
for (UIView *sub in tabbarBtn.subviews) {
if ([sub isKindOfClass:NSClassFromString(@"UITabBarSwappableImageView")]) {
[self preformAnimationForView:sub];
}
}
}
- (void)preformAnimationForView:(UIView *)view {
[view.layer addAnimation:[self scaleAnimation] forKey:nil];
}
/**
重力效果弹跳
*/
- (CAKeyframeAnimation *)gravityElasticAnimation {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.y"];
animation.values = @[@0.0, @-4.15, @-7.26, @-9.34, @-10.37, @-9.34, @-7.26, @-4.15, @0.0, @2.0, @-2.9, @-4.94, @-6.11, @-6.42, @-5.86, @-4.44, @-2.16, @0.0];
animation.duration = 0.8;
animation.beginTime = CACurrentMediaTime();
animation.removedOnCompletion = YES;
return animation;
}
/**
缩放动画
*/
- (CABasicAnimation *)scaleAnimation {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.duration = 0.2;
animation.repeatCount = 1;
animation.removedOnCompletion = YES;
animation.autoreverses = YES;
animation.fromValue = @1;
animation.toValue = @[@0.7, @1];
return animation;
}
Swift 5 版本
extension BaseTabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom _: UIViewController, to _: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return TabBarTransition(viewControllers: tabBarController.viewControllers)
}
private var scaleAnimation: CABasicAnimation {
let animation = CABasicAnimation(keyPath: "transform.scale")
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
animation.duration = 0.2
animation.repeatCount = 1
animation.isRemovedOnCompletion = true
animation.autoreverses = true
animation.fromValue = 1
animation.toValue = [0.7, 1]
return animation
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
let orderedTabBarItemViews: [UIView] = {
let interactionViews = tabBar.subviews.filter { $0 is UIControl }
return interactionViews.sorted(by: { $0.frame.minX < $1.frame.minX })
}()
guard
let index = self.tabBar.items?.firstIndex(of: item),
let imageView = orderedTabBarItemViews[index].subviews.first as? UIImageView
else {
return
}
// 找到UITabBarButton中的imageView,加动画
imageView.layer.add(scaleAnimation, forKey: nil)
}
}
TabBarTransition.swift:
//
// TabBarTransition.swift
// ViPay
//
// Created by VanJay on 2019/6/30.
// Copyright © 2019 VanJay. All rights reserved.
//
import UIKit
class TabBarTransition: NSObject, UIViewControllerAnimatedTransitioning {
let viewControllers: [UIViewController]?
let transitionDuration: TimeInterval = 0.3
init(viewControllers: [UIViewController]?) {
self.viewControllers = viewControllers
}
func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval {
return transitionDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let fromView = fromVC.view,
let fromIndex = getIndex(forViewController: fromVC),
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to),
let toView = toVC.view,
let toIndex = getIndex(forViewController: toVC)
else {
transitionContext.completeTransition(false)
return
}
let frame = transitionContext.initialFrame(for: fromVC)
var fromFrameEnd = frame
var toFrameStart = frame
fromFrameEnd.origin.x = toIndex > fromIndex ? frame.origin.x - frame.width : frame.origin.x + frame.width
toFrameStart.origin.x = toIndex > fromIndex ? frame.origin.x + frame.width : frame.origin.x - frame.width
toView.frame = toFrameStart
DispatchQueue.main.async {
transitionContext.containerView.addSubview(toView)
UIView.animate(withDuration: self.transitionDuration, animations: {
fromView.frame = fromFrameEnd
toView.frame = frame
}, completion: { success in
fromView.removeFromSuperview()
transitionContext.completeTransition(success)
})
}
}
private func getIndex(forViewController vc: UIViewController) -> Int? {
guard let vcs = self.viewControllers else { return nil }
for (index, thisVC) in vcs.enumerated() {
if thisVC == vc { return index }
}
return nil
}
}