撰寫按鈕點擊動畫
效果

compose_anim.gif
- 點擊加號背景磨砂效果
- 各個按鈕依次彈出
- 點擊空白處按鈕依次消失,恢復彈出前的效果
- 某個按鈕點擊有放大并且透明效果,其他縮小透明效果
實現(xiàn)思路
- 磨砂效果可以用蘋果提供的圖片磨砂分類
UIImage+ImageEffects.h - 按鈕彈出的動畫可以使用
Facebook開源的Pop框架 - 放大透明
實現(xiàn)代碼
- 自定義彈出的整體控件
HMComposeView
class HMComposeView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
// 由于當前 View 的特殊性,所以自己指定寬高
self.size = CGSizeMake(SCREENW, SCREENH)
// 設置背景顏色 (為了能展示出來效果)
backgroundColor = UIColor(white: 0.95, alpha: 0.5)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
- 點擊加號按鈕的時候展示在屏幕的最上層
// 在 HMMainViewController 的 viewDidLoad 方法里
let tab = HMTabBar()
//設置撰寫按鈕點擊的事件響應
tab.composeButtonClickBlock = {
print("撰寫按鈕點擊")
let composeView = HMComposeView()
// 獲取到屏幕上當前點擊的最后一個 window
let window = UIApplication.sharedApplication().windows.last!
window.addSubview(composeView)
}
- 在觸摸
HMComposeView的時候消失
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
removeFromSuperview()
}
背景磨砂效果
- 先把屏幕的內容截圖
- 利用
UIImage+ImageEffects分類把圖片處理成磨砂效果
- 屏幕截圖
/// 獲取屏幕截圖
///
/// - returns: 屏幕當前內容
private func getScreenShot() -> UIImage {
// 獲取到主window
let window = UIApplication.sharedApplication().keyWindow
// 開啟上下文 size: 大小 opaque:是否透明 scale:縮放系數(0:分辨率大小 1:點坐標大?。? UIGraphicsBeginImageContextWithOptions(window!.size, false, 0)
// 把主window的內容畫上去
window?.drawViewHierarchyInRect(window!.bounds, afterScreenUpdates: false)
// 獲取到當前上下文的圖片
let image = UIGraphicsGetImageFromCurrentImageContext()
// 約束上下文
UIGraphicsEndImageContext()
return image
}
- 拖入分類設置磨砂效果并添加到當前 View 上
// 磨砂背景
private lazy var bgImageView: UIImageView = {
let imageView = UIImageView(image: self.getScreenShot().applyLightEffect())
return imageView
}()
...
private func setupUI(){
// 添加子控件
addSubview(bgImageView)
// 添加約束
bgImageView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(self.snp_edges)
}
}
- 添加 slogan 控件
// 懶加載控件
private lazy var sloganImage: UIImageView = UIImageView(image: UIImage(named: "compose_slogan"))
// 添加子控件
addSubview(sloganImage)
// 添加約束
sloganImage.snp_makeConstraints { (make) -> Void in
make.centerX.equalTo(self.snp_centerX)
make.top.equalTo(self.snp_top).offset(100)
}
彈性動畫
- 自定義菜單按鈕(圖片在上面,文字在下面)
class HMComposeMenuButton: UIButton {
// 重寫 highlighted 屬性,去掉高亮效果
override var highlighted: Bool {
set{}
get{
return false
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupUI()
}
private func setupUI(){
// 文字大小
titleLabel?.font = UIFont.systemFontOfSize(14)
// 文字顏色
setTitleColor(UIColor.grayColor(), forState: UIControlState.Normal)
// 設置文字居中
titleLabel?.textAlignment = .Center
// 設置圖片顯示模型
imageView?.contentMode = .Center
}
/// 重寫 layoutSubviews 調整文字與圖片的位置
override func layoutSubviews() {
super.layoutSubviews()
// 設置圖片位置
imageView?.size = CGSizeMake(self.width, self.width)
imageView?.x = 0
imageView?.y = 0
// 設置文字位置
titleLabel?.x = 0
titleLabel?.y = self.width
titleLabel?.size = CGSizeMake(self.width, self.height - self.width)
}
}
- 遍歷添加6個按鈕
/// 添加子按鈕
private func addChildButton(){
// 最大列數
let maxRow: CGFloat = 3
// 按鈕的寬與高
let childButtonW: CGFloat = 80
let childButtonH: CGFloat = 110
// 按鈕水平方向間隔
let margin = (SCREENW - (childButtonW * maxRow)) / (maxRow + 1)
for i in 0..<6 {
// 初始化按鈕
let button = HMComposeMenuButton()
// 設置數據
button.setTitle("文字", forState: UIControlState.Normal)
button.setImage(UIImage(named: "tabbar_compose_friend"), forState: UIControlState.Normal)
// 計算當前 button 在第幾行,第幾列
let col = i % Int(maxRow)
let row = i / Int(maxRow)
// 設置位置與大小
button.x = CGFloat(col + 1) * margin + CGFloat(col) * childButtonW
button.y = CGFloat(row) * (margin + childButtonW)
button.size = CGSizeMake(childButtonW, childButtonH);
addSubview(button)
}
}
- 利用 plist 文件設置每一個按鈕的顯示信息
/// 添加子按鈕
private func addChildButton(){
...
// 每個 button 要顯示的數據
let composetButtonInfos = NSArray(contentsOfFile: NSBundle.mainBundle().pathForResource("compose.plist", ofType: nil)!)!
for i in 0..<composetButtonInfos.count {
// 初始化按鈕
let button = HMComposeMenuButton()
// 讀取 title 與 icon 數據
let title = composetButtonInfos[i]["title"] as! String
let icon = composetButtonInfos[i]["icon"] as! String
// 設置數據
button.setTitle(title, forState: UIControlState.Normal)
button.setImage(UIImage(named: icon), forState: UIControlState.Normal)
...
}
}
運行測試
- 仿照其他框架設計思想,提供
show的方法顯示出來
/// 將當前 View 顯示出來
func show(){
// 獲取到屏幕上當前點擊的最后一個 window
let window = UIApplication.sharedApplication().windows.last!
window.addSubview(self)
}
...
// MainViewController 中加號按鈕點擊事件
let tab = HMTabBar()
//設置撰寫按鈕點擊的事件響應
tab.composeButtonClickBlock = {
print("撰寫按鈕點擊")
let composeView = HMComposeView()
composeView.show()
}
- 調整每一個按鈕的位置,以便執(zhí)行動畫
// 設置位置與大小
button.y = CGFloat(row) * (margin + childButtonW) + SCREENH
- 添加
pop框架到框架,在 Podfile 中添加以下代碼
pod 'pop'
- 記錄添加的每一個子菜單按鈕
/// 菜單按鈕的集合
private lazy var menuButtons: [UIButton] = [UIButton]()
/// 添加子按鈕的時候一并添加到上面定義的集合里面去
for i in 0..<composetButtonInfos.count {
...
addSubview(button)
menuButtons.append(button)
}
- 在外界執(zhí)行
show的時候,執(zhí)行動畫
/// 將當前 View 顯示出來
func show(){
// 獲取到屏幕上當前點擊的最后一個 window
let window = UIApplication.sharedApplication().windows.last!
window.addSubview(self)
for (index,value) in menuButtons.enumerate() {
let anim = POPSpringAnimation(propertyNamed: kPOPViewCenter)
// 執(zhí)行動畫
anim.toValue = NSValue(CGPoint: CGPointMake(value.centerX, value.centerY - 350))
// 彈性度
anim.springBounciness = 8
// 彈動速度
anim.springSpeed = 10
// 開始時間
anim.beginTime = CACurrentMediaTime() + 0.025 * Double(index)
// 添加動畫
value.pop_addAnimation(anim, forKey: nil)
}
}
運行測試
- 在當前界面要移除的時候執(zhí)行動畫
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for (index,value) in menuButtons.reverse().enumerate() {
let anim = POPSpringAnimation(propertyNamed: kPOPViewCenter)
// 執(zhí)行動畫
anim.toValue = NSValue(CGPoint: CGPointMake(value.centerX, value.centerY + 350))
// 彈性度
anim.springBounciness = 8
// 彈動速度
anim.springSpeed = 10
// 開始時間
anim.beginTime = CACurrentMediaTime() + 0.025 * Double(index)
// 添加動畫
value.pop_addAnimation(anim, forKey: nil)
}
// 0.3秒之后移除
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.3 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
self.removeFromSuperview()
}
}
運行測試
-
重復代碼抽取
- 彈出動畫與消失的動畫代碼重復
- 抽取出一個方法,區(qū)分出是往上執(zhí)行動畫還是往下執(zhí)行動畫
定義枚舉,區(qū)分動畫執(zhí)行方向
/// 動畫執(zhí)行方向
///
/// - UP: 往上執(zhí)行
/// - DOWN: 往下執(zhí)行
enum ComposeMenuAnimType: Int {
case UP = 0
case DOWN = 1
}
- 抽取執(zhí)行動畫的方法
/// 執(zhí)行動畫邏輯
///
/// - parameter button: 要執(zhí)行動畫的對象
/// - parameter beginTime: 開始執(zhí)行動畫的時間
/// - parameter type: 執(zhí)行
private func anim(button: UIButton, beginTime: CFTimeInterval, type:ComposeMenuAnimType){
let anim = POPSpringAnimation(propertyNamed: kPOPViewCenter)
// 執(zhí)行動畫
if type == .UP {
anim.toValue = NSValue(CGPoint: CGPointMake(button.centerX, button.centerY - 350))
}else{
anim.toValue = NSValue(CGPoint: CGPointMake(button.centerX, button.centerY + 350))
}
// 彈性度
anim.springBounciness = 8
// 彈動速度
anim.springSpeed = 10
// 開始時間
anim.beginTime = beginTime
// 添加動畫
button.pop_addAnimation(anim, forKey: nil)
}
- 調用
/// 將當前 View 顯示出來
func show(){
// 獲取到屏幕上當前點擊的最后一個 window
let window = UIApplication.sharedApplication().windows.last!
window.addSubview(self)
for (index,value) in menuButtons.enumerate() {
anim(value, beginTime: CACurrentMediaTime() + 0.025 * Double(index), type: .UP)
}
}
運行測試
點擊動畫處理
- 添加按鈕點擊事件
// 初始化按鈕
let button = HMComposeMenuButton()
button.addTarget(self, action: "composeButtonClick:", forControlEvents: .TouchUpInside)
- 實現(xiàn)方法
@objc private func composeButtonClick(button: UIButton) {
UIView.animateWithDuration(0.25, animations: { () -> Void in
for value in self.menuButtons {
value.alpha = 0.0
if value == button {
// 如果是當前點擊的button,執(zhí)行放大
value.transform = CGAffineTransformMakeScale(2, 2)
}else{
// 否則縮小
value.transform = CGAffineTransformMakeScale(0.3, 0.3)
}
}}, completion: { (finish) -> Void in
for value in self.menuButtons {
UIView.animateWithDuration(0.25, animations: { () -> Void in
value.transform = CGAffineTransformIdentity
value.alpha = 1
})
}
})
}
- 新建控制器
HMComposeViewController
class HMComposeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI(){
view.backgroundColor = UIColor.whiteColor()
navigationItem.leftBarButtonItem = UIBarButtonItem.item(title: "返回", target: self, action: "back")
}
@objc private func back(){
dismissViewControllerAnimated(true, completion: nil)
}
}
- 在點擊按鈕的時候彈出
- 發(fā)現(xiàn)問題,要彈出控制器需要用到另一個控制器,所以將
HMMainViewController控制器傳入
- 發(fā)現(xiàn)問題,要彈出控制器需要用到另一個控制器,所以將
// 定義屬性
var target: UIViewController?
// 更改 show 方法
func show(target: UIViewController){
self.target = target
...
}
...
// 外界通過 show 這個方法傳入
let composeView = HMComposeView()
composeView.show(self)
- 點擊彈出控制器
@objc private func composeButtonClick(button: UIButton) {
UIView.animateWithDuration(0.25, animations: { () -> Void in
...
}}, completion: { (finish) -> Void in
let controller = HMNavigationController(rootViewController: HMComposeViewController())
self.target?.presentViewController(controller, animated: true, completion: { () -> Void in
self.removeFromSuperview()
})
})
}
運行測試:發(fā)現(xiàn)并沒有看到從下往上彈出的效果,原因控制器是在
composeView的下面彈出的,所以分析之后,需要把composeView添加到HMMainViewController的 view 上
- 更改添加的代碼
/// 將當前 View 顯示出來
func show(target: UIViewController){
self.target = target
// 添加到傳入的控制器身上
target.view.addSubview(self)
for (index,value) in menuButtons.enumerate() {
anim(value, beginTime: CACurrentMediaTime() + 0.025 * Double(index), type: .UP)
}
}