再造 “手机QQ” 侧滑菜单(一)——实现侧滑效果

本系列文章中,我们将尝试再造手机QQ的侧滑菜单,力争最大限度接近手Q的实际效果,并使用 Auto Layout 仿造左侧菜单,实现和主视图的联动。

代码示例:https://github.com/johnlui/SwiftSideslipLikeQQ

最终效果:

Image


开发环境

本系列文章的开发环境为:

* OS X 10.10.3

* Xcode Version 6.3 (6D570)

基本数据采集

初步体验,手Q采用的应该是线性动画,即缩放比例等随着手指滑动的距离以一次方程的形式变化。动画达到最大幅度时截图如下(4.7 寸):

Image

提取基本数据:

  1. 右侧主视图左边界距离屏幕左边界的距离占屏幕宽度的比例为:78%
  2. 右侧主视图的高度占屏幕高度的比例为:77%

找出线性关系

1. 比例与手指移动距离的关系

字比较丑 o(╯□╰)o。注意:式(1)中的 x 表示“手指移动距离”这个变量,和上面图中表示屏幕宽度的 x 意义不同。

Image

2. 矩形中心向右移动距离和手指移动距离相等

实现侧滑

1. 新建项目,在 StoryBoard 中新增一个 View Controller,并新增一个名为 HomeViewController 的 UIViewController 类,并在 StoryBoard 中完成绑定。

2. 给 HomeViewController 设置背景颜色以示区分。也可以像我一样设一个大 Label 作为更明显的区分。

Image

3. 给 HomeViewController 拖放一个 UIPanGestureRecognizer 并绑定到代码。

从右下角拖一个 Pan Gesture Recognizer 到主窗体上,这一步会让它与 HomeViewController.view 自动绑定。下图为第二步,绑定到代码。

Image

3. 编写代码实现效果:

新建 Common.swift,存储屏幕宽度、高度:

import UIKit

struct Common {
    static let screenWidth = UIScreen.mainScreen().applicationFrame.maxX
    static let screenHeight = UIScreen.mainScreen().applicationFrame.maxY
}

修改 ViewController:

import UIKit

class ViewController: UIViewController {
    
    var homeViewController: HomeViewController!
    var distance: CGFloat = 0
    
    let FullDistance: CGFloat = 0.78
    let Proportion: CGFloat = 0.77

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 给主视图设置背景
        let imageView = UIImageView(image: UIImage(named: "back"))
        imageView.frame = UIScreen.mainScreen().bounds
        self.view.addSubview(imageView)
        
        // 通过 StoryBoard 取出 HomeViewController 的 view,放在背景视图上面
        homeViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
        self.view.addSubview(homeViewController.view)
        
        // 绑定 UIPanGestureRecognizer
        homeViewController.panGesture.addTarget(self, action: Selector("pan:"))
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // 响应 UIPanGestureRecognizer 事件
    func pan(recongnizer: UIPanGestureRecognizer) {
        let x = recongnizer.translationInView(self.view).x
        let trueDistance = distance + x // 实时距离
        
        // 如果 UIPanGestureRecognizer 结束,则激活自动停靠
        if recongnizer.state == UIGestureRecognizerState.Ended {

            if trueDistance > Common.screenWidth * (Proportion / 3) {
                showLeft()
            } else if trueDistance < Common.screenWidth * -(Proportion / 3) {
                showRight()
            } else {
                showHome()
            }
            
            return
        }

        // 计算缩放比例
        var proportion: CGFloat = recongnizer.view!.frame.origin.x >= 0 ? -1 : 1
        proportion *= trueDistance / Common.screenWidth
        proportion *= 1 - Proportion
        proportion /= 0.6
        proportion += 1
        if proportion <= Proportion { // 若比例已经达到最小,则不再继续动画
            return
        }
        // 执行平移和缩放动画
        recongnizer.view!.center = CGPointMake(self.view.center.x + trueDistance, self.view.center.y)
        recongnizer.view!.transform = CGAffineTransformScale(CGAffineTransformIdentity, proportion, proportion)
    }
    
    // 封装三个方法,便于后期调用
    
    // 展示左视图
    func showLeft() {
        distance = self.view.center.x * (FullDistance + Proportion / 2)
        doTheAnimate(self.Proportion)
    }
    // 展示主视图
    func showHome() {
        distance = 0
        doTheAnimate(1)
    }
    // 展示右视图
    func showRight() {
        distance = self.view.center.x * -(FullDistance + Proportion / 2)
        doTheAnimate(self.Proportion)
    }
    // 执行三种试图展示
    func doTheAnimate(proportion: CGFloat) {
        UIView.animateWithDuration(0.3, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
            self.homeViewController.view.center = CGPointMake(self.view.center.x + self.distance, self.view.center.y)
            self.homeViewController.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, proportion, proportion)
            }, completion: nil)
    }

}

4. 查看效果

Image


下一步:再造 “手机QQ” 侧滑菜单(二)——高仿左视图

WRITTEN BY

avatar
2015.4.11   /   热度:57247   /   分类: iOS & Swift

评论:

emlog
2017-06-25 22:45
希望博主有空可以回复我一下,关于5470。。。
emlog
2017-06-25 22:43
博主很喜欢用呃emlog嘛?
3123123
2017-06-25 12:22
支持
ggggg
2017-06-07 21:20
JS表示毫无压力
盘古科技
2017-04-23 21:04
PHP是最好的语言 偶也
daybyte
2017-03-13 10:03
看了下大神的侧滑。本人还是觉得在同一个控制器里写两个view+手势实现侧滑 耦合度太高了,毕竟两个view之间的业务没有太大关联。
所以本人写了一个完全解耦的侧滑菜单,一行代码调用。
GitHub地址是:https://github.com/maybeisyi/DYLeftSlipManager
过路人
2017-03-09 14:26
展示左右视图的 计算结果并不符合0.77的定义啊    distance = WIDTH * (FullDistance + Proportion / 2) - self.view.center.x
Jay
2016-09-02 11:17
大神,你这项目都是ios的啊,看来你还是喜欢ios多一点
好好地
2017-02-26 18:11
@Jay:同感
zero思念的力量
2016-08-02 11:23
强烈点赞还有
panGesture添加事件后油加了这句话才响应的事件
homeViewController.view.addGestureRecognizer(homeViewController.panGesture)
,效果是好好好 学到好多东西
赵春林
2016-04-25 17:03
能帮解答一下左侧Controller如果需要push到另外一个节目的思路吗?
逗霸帝
2016-03-07 23:04
setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key panGesture.' 出现这样的错误。。。怎么破?
wennnnnn
2015-12-28 16:49
请问。你的博客是自己写的吗。还是用开源的?
小班
2015-12-21 20:24
还有, 我 将 if trueDistance > Common.screenWidth * (Proportion / 3) { 这句的Common.screenWidth输出,为什么等于零啊!
chenhening
2016-01-11 18:38
@小班:你把3改成3.0就不一定是零了!
小班
2015-12-21 19:45
根据你这个我用OC写,但是发现最后的动画不流畅。情况是手指点击时滑动,没有缩放动画,只有手指松开时才缩小。
求分析。
空灵
2015-12-16 14:03
楼主  为什么我用OC按照你代码写滑动时HomeViewController没有缩放的效果 滑动后HomeViewController会是缩放的效果
aliios
2015-12-10 17:48
博主,我正在把你的项目转换成OC。转换成功之后,我是否可以将代码放到我的博客上面?
JohnLui
2015-12-10 17:52
@aliios:随意啦
aliios
2015-12-10 17:57
@JohnLui:非常感谢!
IOS潘
2015-12-02 00:30
我是新手,在看github上的这个qq项目,但是我一运行就报‘Index' does not have a member named ‘advanceBy‘错误。用的xcode7.0。
小肆
2015-10-31 11:36
岁寒大神,神功盖世,一统江湖,千秋万代.

看了以后了给我很大的启发.
zhao
2015-09-19 18:02
大神,有oc的吗?
Havyre
2015-08-31 15:32
找到一个好博

发表评论:

© 2011-2017 岁寒  |  Powered by Emlog