最近学了一阵子Swift了, 最近写项目的时候有用到
RAMAnimatedTabBarController
这个框架, 这个框架目前已经有6K多的star了,还是挺受欢迎的. 其中被其中的BUG坑到了, 就研究了一下源码, 也注意到网上比较少关于这个框架的资料(可能这个框架比较简单, 哈哈), 所以分享分享.
一, 简单的使用:
整个框架就只有一个控制器RAMAnimatedTabBarControlle
r, 初始化RAMAnimatedTabBarController
必须配合RAMAnimatedTabBarItem
来使用, 所以我们自定义的TabBarController
的时候,我们得继承RAMAnimatedTabBarController
import RAMAnimatedTabBarController
class MainViewController: RAMAnimatedTabBarController {
overridefunc viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
///添加所有的子控制器
addAllChildsControllors()
}
}
然后创建配合使用的RAMAnimatedTabBarItem
//MARK: - UI
extension MainViewController {
///添加所有的子控制器
fileprivate func addAllChildsControllors() {
///首页
addOneChildVC(childVC: HomeViewController(), title: "首页", image: UIImage(imageLiteralResourceName: "btn_home_normal"), selecteImage: UIImage(named: "btn_home_selected"))
//直播
addOneChildVC(childVC: LiveViewController(), title: "直播", image: UIImage(imageLiteralResourceName: "btn_column_normal"), selecteImage: UIImage(named: "btn_column_selected"))
//关注
addOneChildVC(childVC: FollowViewController(), title: "关注", image: UIImage(imageLiteralResourceName: "btn_live_normal"), selecteImage: UIImage(named: "btn_live_selected"))
//我的
addOneChildVC(childVC: ProfileViewController(), title: "我的", image: UIImage(imageLiteralResourceName: "btn_user_normal"), selecteImage: UIImage(named: "btn_user_selected"))
}
///添加一个控制器
private func addOneChildVC(childVC: UIViewController, title: String?, image: UIImage?, selecteImage: UIImage?) {
//1.添加子控制器
let navVC = UINavigationController(rootViewController: childVC)
addChildViewController(navVC)
//2.添加标题
let item = RAMAnimatedTabBarItem(title: title, image: image, selectedImage: selecteImage)
let animation = RAMTransitionItemAnimations()
animation.iconSelectedColor = UIColor.orange
animation.transitionOptions = UIViewAnimationOptions.transitionCurlUp
item.animation = animation
navVC.tabBarItem = item
}
}
最终决定item
的动画类型和效果是有 RAMAnimatedTabBarItem
的实例化对象的animation
类型决定的. 根据框架提供给我们的动画类型有: RAMFumeAnimation, RAMBounceAnimation, RAMRotationAnimation, RAMFrameItemAnimation, RAMTransitionItemAnimations.
我们也可以遵守RAMItemAnimationProtocol
协议自定义动画
其中选中的效果图标animation.iconSelectedColo
r 属性决定, 文字由 textSelectedColor
决定, 所以 let item = RAMAnimatedTabBarItem(title: title, image: image, selectedImage: selecteImage)
中的selectedImage
是没有效果的
所以感觉用起来还是有点怪
二, 内部的实现
RAMAnimatedTabBarController
是一个给UITabBarItem
添加动画的框架, 总体可以分为两个方向: 控件的布局
和 动画的实现
控件的布局
根据源码我发现RAMAnimatedTabBarController
是继承于 UITabBarController
/// UITabBarController with item animations
openclass RAMAnimatedTabBarController: UITabBarController
根据这个方法作者是自定义了UITabBarItem
, 也就是上面我给出的控件布局图中得 viewContainer
, 这也就是我们界面所看到的UITabBarItem
.
fileprivate func createCustomIcons(_ containers : NSDictionary) {
guard let items = tabBar.items as? [RAMAnimatedTabBarItem] else {
fatalError("items must inherit RAMAnimatedTabBarItem")
}
var index = 0
for item in items {
guard let itemImage = item.image else {
fatalError("add image icon in UITabBarItem")
}
guard let container = containers["container\(items.count - 1 - index)"] as? UIView else {
fatalError()
}
container.tag = index
let renderMode = item.iconColor.cgColor.alpha == 0 ? UIImageRenderingMode.alwaysOriginal :
UIImageRenderingMode.alwaysTemplate
let icon = UIImageView(image: item.image?.withRenderingMode(renderMode))
icon.translatesAutoresizingMaskIntoConstraints = false
icon.tintColor = item.iconColor
// text
let textLabel = UILabel()
textLabel.text = item.title
textLabel.backgroundColor = UIColor.clear
textLabel.textColor = item.textColor
textLabel.font = item.textFont
textLabel.textAlignment = NSTextAlignment.center
textLabel.translatesAutoresizingMaskIntoConstraints = false
container.backgroundColor = (items as [RAMAnimatedTabBarItem])[index].bgDefaultColor
container.addSubview(icon)
createConstraints(icon, container: container, size: itemImage.size, yOffset: -5 - item.yOffSet)
container.addSubview(textLabel)
let textLabelWidth = tabBar.frame.size.width / CGFloat(items.count) - 5.0
createConstraints(textLabel, container: container, size: CGSize(width: textLabelWidth , height: 10), yOffset: 16 - item.yOffSet)
if item.isEnabled == false {
icon.alpha = 0.5
textLabel.alpha = 0.5
}
item.iconView = (icon:icon, textLabel:textLabel)
if 0 == index { // selected first elemet
item.selectedState()
container.backgroundColor = (items as [RAMAnimatedTabBarItem])[index].bgSelectedColor
}
item.image = nil
item.title = ""
index += 1
}
}
那么我们创建的 RAMAnimatedTabBarItem
呢, 其实我们创建的RAMAnimatedTabBarItem
,被保存到了TabBarConteoller
中得 Items
, 中了, 只是上面创建的自定义UItabBarItem(viewContainer)
添加到TabBarConteoller
,挡住了UITabBar
, 所以跟我们交互的是作者自定义的UItabBarItem(viewContainer)
//所有的viewContainer 就是 UITabBarItem 然后添加到控制器的View上
fileprivate func createViewContainers() -> NSDictionary {
guard let items = tabBar.items else {
fatalError("add items in tabBar")
}
var containersDict = [String: AnyObject]()
for index in 0.. UIView {
let viewContainer = UIView();
viewContainer.backgroundColor = UIColor.clear // for test
viewContainer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(viewContainer)
// add gesture
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(RAMAnimatedTabBarController.tapHandler(_:)))
tapGesture.numberOfTouchesRequired = 1
viewContainer.addGestureRecognizer(tapGesture)
// add constrains
let constY = NSLayoutConstraint(item: viewContainer,
attribute: NSLayoutAttribute.bottom,
relatedBy: NSLayoutRelation.equal,
toItem: view,
attribute: NSLayoutAttribute.bottom,
multiplier: 1,
constant: 0)
view.addConstraint(constY)
let constH = NSLayoutConstraint(item: viewContainer,
attribute: NSLayoutAttribute.height,
relatedBy: NSLayoutRelation.equal,
toItem: nil,
attribute: NSLayoutAttribute.notAnAttribute,
multiplier: 1,
constant: tabBar.frame.size.height)
viewContainer.addConstraint(constH)
return viewContainer
}
给自定义UItabBarItem(viewContainer)
添加手势, 点击抽发的事件
// MARK: actions 手势, 事件触发
open func tapHandler(_ gesture:UIGestureRecognizer) {
guard let items = tabBar.items as? [RAMAnimatedTabBarItem],
let gestureView = gesture.view else {
fatalError("items must inherit RAMAnimatedTabBarItem")
}
let currentIndex = gestureView.tag
if items[currentIndex].isEnabled == false { return }
let controller = self.childViewControllers[currentIndex]
if let shouldSelect = delegate?.tabBarController?(self, shouldSelect: controller)
, !shouldSelect {
return
}
if selectedIndex != currentIndex {
let animationItem : RAMAnimatedTabBarItem = items[currentIndex]
animationItem.playAnimation()
let deselectItem = items[selectedIndex]
let containerPrevious : UIView = deselectItem.iconView!.icon.superview!
containerPrevious.backgroundColor = items[currentIndex].bgDefaultColor
deselectItem.deselectAnimation()
let container : UIView = animationItem.iconView!.icon.superview!
container.backgroundColor = items[currentIndex].bgSelectedColor
selectedIndex = gestureView.tag
delegate?.tabBarController?(self, didSelect: controller)
} else if selectedIndex == currentIndex {
if let navVC = self.viewControllers![selectedIndex] as? UINavigationController {
navVC.popToRootViewController(animated: true)
}
}
我们再来看看 `RAMAnimatedTabBarItem
// UITabBarItem with animation
open class RAMAnimatedTabBarItem: UITabBarItem {
@IBInspectable open var yOffSet: CGFloat = 0
open override var isEnabled: Bool {
didSet {
iconView?.icon.alpha = isEnabled == true ? 1 : 0.5
iconView?.textLabel.alpha = isEnabled == true ? 1 : 0.5
}
}
/// animation for UITabBarItem. use RAMFumeAnimation, RAMBounceAnimation, RAMRotationAnimation, RAMFrameItemAnimation, RAMTransitionAnimation
/// or create custom anmation inherit RAMItemAnimation
/// 这个属性是使用 RAMFumeAnimation, RAMBounceAnimation, RAMRotationAnimation, RAMFrameItemAnimation, RAMTransitionAnimation 着五个类创建的动画, 或者自定义一个动画
@IBOutlet open var animation: RAMItemAnimation!
/// The font used to render the UITabBarItem text. (UITabBarItem上得字体大小)
open var textFont: UIFont = UIFont.systemFont(ofSize: 10)
/// The color of the UITabBarItem text. (UITabBarItem上得字体颜色)
@IBInspectable open var textColor: UIColor = UIColor.black
/// The tint color of the UITabBarItem icon. (UITabBarItem上得图标颜色)
@IBInspectable open var iconColor: UIColor = UIColor.clear // if alpha color is 0 color ignoring
var bgDefaultColor: UIColor = UIColor.clear // background color
var bgSelectedColor: UIColor = UIColor.clear
// The current badge value 角标
open var badge: RAMBadge? // use badgeValue to show badge
// Container for icon and text in UITableItem.
open var iconView: (icon: UIImageView, textLabel: UILabel)?
/**
Start selected animation
*/
open func playAnimation() {
assert(animation != nil, "add animation in UITabBarItem")
guard animation != nil && iconView != nil else {
return
}
animation.playAnimation(iconView!.icon, textLabel: iconView!.textLabel)
}
/**
Start unselected animation
*/
open func deselectAnimation() {
guard animation != nil && iconView != nil else {
return
}
animation.deselectAnimation(
iconView!.icon,
textLabel: iconView!.textLabel,
defaultTextColor: textColor,
defaultIconColor: iconColor)
}
/**
Set selected state without animation
*/
open func selectedState() {
guard animation != nil && iconView != nil else {
return
}
animation.selectedState(iconView!.icon, textLabel: iconView!.textLabel)
}
}
可以看到核心的方法就是 playAnimation(), deselectAnimation(), selectedState()
这三个方法.
动画的实现
接下来整个框架最核心的部分
可以看到所有的动画都是遵守了
RAMItemAnimationProtocol
协议实现的
RAMItemAnimationProtocol:
//动画的协议
public protocol RAMItemAnimationProtocol {
func playAnimation(_ icon : UIImageView, textLabel : UILabel)
func deselectAnimation(_ icon : UIImageView, textLabel : UILabel, defaultTextColor : UIColor, defaultIconColor : UIColor)
func selectedState(_ icon : UIImageView, textLabel : UILabel)
}
RAMItemAnimation
遵守了RAMItemAnimationProtocol
协议
/// Base class for UITabBarItems animation (UITabBarItems 动画的基础类)
open class RAMItemAnimation: NSObject, RAMItemAnimationProtocol {
// MARK: constants
struct Constants {
//动画的keys
struct AnimationKeys {
static let Scale = "transform.scale"
static let Rotation = "transform.rotation"
static let KeyFrame = "contents"
static let PositionY = "position.y"
static let Opacity = "opacity"
}
}
// MARK: properties
/// The duration of the animation (动画的时间, 默认为0.5s)
@IBInspectable open var duration : CGFloat = 0.5
/// The text color in selected state. (选中状态的文字颜色)
@IBInspectable open var textSelectedColor: UIColor = UIColor.init(red: 0, green: 0.478431, blue: 1, alpha: 1)
/// The icon color in selected state. (选中状态的图标颜色)
@IBInspectable open var iconSelectedColor: UIColor!
/**
Start animation, method call when UITabBarItem is selected
当您选中UITabBarItem的时候调用这个方法开始动画
- parameter icon: animating UITabBarItem icon (UITabBarItem 的动画图标)
- parameter textLabel: animating UITabBarItem textLabel (UITabBarItem 的动画标题)
*/
open func playAnimation(_ icon : UIImageView, textLabel : UILabel) {
fatalError("override method in subclass")
}
/**
Start animation, method call when UITabBarItem is unselected
当您 取消 选中UITabBarItem的时候调用这个方法开始动画
- parameter icon: animating UITabBarItem icon
- parameter textLabel: animating UITabBarItem textLabel
- parameter defaultTextColor: default UITabBarItem text color
- parameter defaultIconColor: default UITabBarItem icon color
*/
open func deselectAnimation(_ icon : UIImageView, textLabel : UILabel, defaultTextColor : UIColor, defaultIconColor : UIColor) {
fatalError("override method in subclass")
}
/**
Method call when TabBarController did load
选中的状态, 在加载 TabBarController 的时候就会调用
- parameter icon: animating UITabBarItem icon
- parameter textLabel: animating UITabBarItem textLabel
*/
open func selectedState(_ icon: UIImageView, textLabel : UILabel) {
fatalError("override method in subclass")
}
}
这个属性是使用RAMFumeAnimation, RAMBounceAnimation, RAMRotationAnimation, RAMFrameItemAnimation, RAMTransitionAnimation
都是继承了RAMItemAnimation
这个类去实现的
RAMFumeAnimation:
核心动画
RAMBounceAnimation:
核心动画
RAMRotationAnimation:
核心动画
RAMFrameItemAnimation:
帧动画, 这个类需要自己提供帧动画组
override open func awakeFromNib() {
guard let path = Bundle.main.path(forResource: imagesPath, ofType:"plist") else {
fatalError("don't found plist")
}
//获取所有图片的名字
guard case let animationImagesName as [String] = NSArray(contentsOfFile: path) else {
fatalError()
}
//变成所有的图片, 并存储在animationImages属性中
createImagesArray(animationImagesName)
// selected image 选中状态下得显示的图标, 这里是图片数组中最后一张
let selectedImageName = animationImagesName[animationImagesName.endIndex - 1]
selectedImage = UIImage(named: selectedImageName)
}
RAMTransitionAnimation:
核心代码如下 动画的策略 transitionFlipFromBottom, transitionFlipFromTop, transitionFlipFromRight, transitionFlipFromLeft, transitionCrossDissolve ,transitionCurlUp, transitionCurlDown
不同的动画策略显示的是不同的动画类型
/**
Start animation, method call when UITabBarItem is selected
- parameter icon: animating UITabBarItem icon
- parameter textLabel: animating UITabBarItem textLabel
*/
override open func playAnimation(_ icon : UIImageView, textLabel : UILabel) {
selectedColor(icon, textLabel: textLabel)
UIView.transition(with: icon, duration: TimeInterval(duration), options: transitionOptions, animations: {
}, completion: { _ in
})
}
自定义动画:
假如上面的动画类型你都不喜欢, 你还可以自定义动画, 自己去实现想要的动画效果. 继承 RAMItemAnimation
, 实现 RAMItemAnimationProtocol
中的 playAnimation(), deselectAnimation(), selectedState(),
这三个方法就好.
如果大家觉得这个框架不好用的话, 还可以自己写一个, 也挺简单的
TabBarItem添加动画的一种思路