12-撰寫按鈕點擊動畫

撰寫按鈕點擊動畫

效果

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()
}

背景磨砂效果

  1. 先把屏幕的內容截圖
  2. 利用 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 控制器傳入
// 定義屬性
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)
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容