12-撰写按钮点击动画

撰写按钮点击动画

效果

compose_anim.gif
  • 点击加号背景磨砂效果
  • 各个按钮依次弹出
  • 点击空白处按钮依次消失,恢复弹出前的效果
  • 某个按钮点击有放大并且透明效果,其他缩小透明效果

实现思路

  • 磨砂效果可以用苹果提供的图片磨砂分类 UIImage+ImageEffects.h
  • 按钮弹出的动画可以使用 Facebook 开源的 Pop 框架
  • 放大透明

实现代码

  • 自定义弹出的整体控件 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()
}
  • 调整每一个按钮的位置,以便执行动画
// 设置位置与大小
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)
}
  • 在外界执行 show 的时候,执行动画
/// 将当前 View 显示出来
func show(){
    // 获取到屏幕上当前点击的最后一个 window
    let window = UIApplication.sharedApplication().windows.last!
    window.addSubview(self)

    for (index,value) in menuButtons.enumerate() {
        let anim = POPSpringAnimation(propertyNamed: kPOPViewCenter)
        // 执行动画
        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)
    }
}

运行测试

  • 在当前界面要移除的时候执行动画
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    for (index,value) in menuButtons.reverse().enumerate() {

        let anim = POPSpringAnimation(propertyNamed: kPOPViewCenter)
        // 执行动画
        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()
    }
}

运行测试

  • 重复代码抽取

    • 弹出动画与消失的动画代码重复
    • 抽取出一个方法,区分出是往上执行动画还是往下执行动画
  • 定义枚举,区分动画执行方向

/// 动画执行方向
///
/// - UP:   往上执行
/// - DOWN: 往下执行
enum ComposeMenuAnimType: Int {
    case UP = 0
    case DOWN = 1
}
  • 抽取执行动画的方法
/// 执行动画逻辑
///
/// - parameter button:    要执行动画的对象
/// - parameter beginTime: 开始执行动画的时间
/// - parameter type:      执行
private func anim(button: UIButton, beginTime: CFTimeInterval, type:ComposeMenuAnimType){

    let anim = POPSpringAnimation(propertyNamed: kPOPViewCenter)
    // 执行动画
    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)
  • 实现方法
@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,执行放大
                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)
    }
}
  • 在点击按钮的时候弹出
    • 发现问题,要弹出控制器需要用到另一个控制器,所以将 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()
            })
    })
}

运行测试:发现并没有看到从下往上弹出的效果,原因控制器是在 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)
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容