手把手教你写“旋转的圆盘”

网上烂大街的有写网易彩票的圆盘动画,今天我也记录一下,防止遗忘

没事正常旋转,点击开始选号码,快速旋转

基本思路
1.搭建基本的wheel
2.让第二层(大黄色的圆盘)的旋转起来
3.往第二层(大黄色)中添加12个按钮,
3.1(谈谈awakeFromNib,和init?(coder aDecoder: NSCoder)的顺序和区别)
3.2 锚点的设置,和point的实际用法
3.3 选择按钮经典3部曲
4.将大图剪切成小图,讲讲CGImageCreateWithImageInRect这个方法的使用,还有像素比,以及image.size到底是啥,还有为毛线[UIScreen mainScreen].scale
5.重写btn方法,更改内部image的尺寸
6.给btn一个选中的image的照片(还是按照4那样切图)
7.重写setHightLighit方法 (但是没写明白)
8.UIControlEvents.TouchDownUIControlEvents.TouchUpInside的区别
9.为装盘添加2个方法,开始和结束
10.CADisplayLinkNSTimer的区别和使用情景
10.避免多个定时器同时工作,会出什么问题?
11.细节,(大黄圆盘)交互yes,no

1.搭建基本的wheel

直接封装一个view,叫做RoundWheel,xib脱线布局,比较方便快捷

view上变放了两个imageView,和view一样的尺寸
2.让第二层(大黄色的圆盘)的旋转起来

对外提供一个开始方法,直接选择

   func startRotating()
   {
      let anim = CABasicAnimation()
       anim.keyPath = "transform.rotation"
       anim.toValue = M_PI*2
       anim.duration = 3
       anim.removedOnCompletion = false
       anim.repeatCount = MAXFLOAT
       anim.fillMode = kCAFillModeForwards
//我现在让最低部的view旋转了,所以“开始按钮”才跟着旋转
    layer.addAnimation(anim, forKey: "rotationWheel")
本图片有点问题,本图片是让最底下的view旋转了,所以“开始按钮”也跟这转,如果你是是黄色的view.layer执行动画的,就不会这样了开始按钮也在上边,然后就跟着转了
3.往第二层(大黄色)中添加12个按钮

先做出这样的效果

平面图
立体图
 override func awakeFromNib() {
        //创建12个按钮
        createSubviews()
        centerView.userInteractionEnabled = true
    }
    private func createSubviews(){
        for index in 0 ..< 12
        {
     let btn = RWButton(        
     btn.addTarget(self, action: "btnBeSelected:", forControlEvents: UIControlEvents.TouchUpInside)
     //2.设置背景颜色
     let randomR = Float(arc4random_uniform(255))/255.0
     let randomG = Float(arc4random_uniform(255))/255.0
     let randomB = Float(arc4random_uniform(255))/255.0
     let randomColor = UIColor.init(colorLiteralRed: randomR, green: randomG, blue: randomB, alpha: 1)
         btn.backgroundColor = randomColor
        }
    }

布局的代码

    override func layoutSubviews() {
        super.layoutSubviews()
        let bW:CGFloat = 68
        let bH:CGFloat = 143
        let bY:CGFloat = 0
        let bX = (self.frame.width - bW) * 0.5
        for index in 0 ..< 12
        {
            
            //1.设置基本的frame
            let btn = centerView.subviews[index] as! RWButton
            btn.frame = CGRectMake(bX, bY, bW, bH)
        }
    }

这里讲解一下awakeFromNibinit?(coder aDecoder: NSCoder)的顺序和区别

  • 1.如果A控制器(或者View)想通过xib加载B(view),那么一定会调用B的init?(coder aDecoder: NSCoder
  • 2.如果C(view)是通过xib加载出来的,那么一定会调用awakeFromNib方法
  • 3.B(View)的创建,可能是纯代码写的,一定会调用initWithFrame:方法,如果是通过xib创建的,一定会调用init?(coder aDecoder: NSCoder方法
  • 4.init?(coder aDecoder: NSCoder县调用,awakeFromNib后调用(执行到这里,xib拉出来的连线的view,才不为空,在init(coder aDecoder)的时候,连线出来的view是空的
要做出这样的效果

重新布局

    override func layoutSubviews() {
        super.layoutSubviews()
        let bW:CGFloat = 68
        let bH:CGFloat = 143
        let bY:CGFloat = 0
        let bX = (self.frame.width - bW) * 0.5
        for index in 0 ..< 12
        {
            
            //1.设置基本的frame
            let btn = centerView.subviews[index] as! RWButton
            btn.frame = CGRectMake(bX, bY, bW, bH)
            
            //2.设置transfrom的属性
            let angle = (Double(index) * 2) * M_PI / 12.0
            btn.layer.anchorPoint = CGPointMake(0.5, 1)
            btn.layer.position = CGPointMake(self.frame.width*0.5, self.frame.height*0.5)
            btn.transform = CGAffineTransformMakeRotation(CGFloat(angle))
        }
    }

注意
1.一个view锚点默认值是(0.5,0.5)
2.btn.layer.position是锚点的位置
3.所有的旋转,或者是平移,以及拉伸都是沿着锚地做的
4.设置每个btn锚点的位置在(0.5,1),锚点的取值范围是0到1
5.一共12份,所以360°/12 = 30°

做成可以点击每一个按钮,的样子,忽略上班的文字,我要的是selected的选中的效果

1.设置每个按钮的选中状态的背景照片

btn .setBackgroundImage(UIImage(named:"LuckyRototeSelected"), forState: UIControlState.Selected)
背景照片

2.经典的选中btn三部曲
2.0 准备工作. 首先要有一个全局weak属性selectedButton.还有给12个按钮点击事件btn.addTarget(self, action: "btnBeSelected:", forControlEvents: UIControlEvents.TouchUpInside)

  • 2.1 先让selectedButton选中状态为false
  • 2.2 让刚刚点击的btn的selected等于true
  • 2.3 最后让selectedButton指向bin
    func btnBeSelected(btn:UIButton){
        //1.先让之前选中的按钮取消选中
      selectedBtn?.selected = false
      //2.让刚刚点的按钮设置成选中状态
        btn.selected = true
        //3.使用全局变量保存刚刚点中的btn
        selectedBtn = btn
    }

简单快捷有效,我看过很多程序员,都写的特别麻烦~

4.将大图剪切成小图,设置成btn.image
UI为了减小包的大小,只给了一张图片,让我们自己剪切,那就切割12份啊,貌似很简单,其实很多坑
    private func createSubviews(){
        for index in 0 ..< 12
        {
            let btn = UIButton()
            btn .setBackgroundImage(UIImage(named:"LuckyRototeSelected"), forState: UIControlState.Selected)
            btn.addTarget(self, action: "btnBeSelected:", forControlEvents: UIControlEvents.TouchUpInside)
            
            //3.设置背景照片
            let smallImage = UIImage(named: "LuckyAstrology")
            let iW = (smallImage?.size.width)! 
            let iH = (smallImage?.size.height)! 

            //4.设置normal情况下的image
            let rect = CGRectMake(CGFloat(index) * iW , 0, iW, iH)
            let norImageRef = CGImageCreateWithImageInRect(smallImage?.CGImage,rect)
            btn .setImage(UIImage(CGImage:norImageRef!), forState: UIControlState.Normal)
            
            //5.设置select样式的image
            let selectedSmallImage = UIImage(named: "LuckyAstrologyPressed")
            let seletedImageRef = CGImageCreateWithImageInRect(selectedSmallImage?.CGImage, rect)
            btn.setImage(UIImage(CGImage: seletedImageRef!), forState: UIControlState.Selected)
            centerView.addSubview(btn)
        }
    }
乱七八糟,其实细看,只有4分之一的图片

问题很复杂,就是几倍图的问题。
1.在ios项目中,我们使用的是点坐标(dx)
2.在c语言函数中,我们使用的是像素(px)单位
CGImageCreateWithImageInRect是c语言函数
3.ios中有1倍图,2倍图,3倍图
4.我们将图片使用在项目中,看他的大小,是1倍图的尺寸
5.但是retain屏幕,是2倍图,plus是3倍图,不同情况,系统加载的图片是不一样的,所以获取的照片的尺寸一定是不同的!
6.获取当前测试机是几倍图的终结者UIScreen.mainScreen().scale,判断屏幕尺寸的方法有的时候不准,但是这个属性一定准, 4s,5,6,6s都是 2倍图,plus是三倍图,我打印了~~
7.切割图片的时候,通过smallImage?.size.width获取的是1倍图的尺寸
8.CGImageCreateWithImageInRect(smallImage?.CGImage,rect)第一个参数是要切割什么图(加载相应的几倍图),第二个参数是用什么rect切割
9.做了一份打印 ,结果如下
//尺寸 Optional((480.0, 46.0)) 当前的比例 2.0
//尺寸 Optional((480.0, 46.0)) 当前的比例 3.0

10.这个解释了7,8
11.切割图片的时候,rect的宽高要乘以 屏幕比例

一倍图的尺寸

代理更改如下

           //3.设置背景照片
            let smallImage = UIImage(named: "LuckyAstrology")
            let iW = (smallImage?.size.width)! / 12.0 * UIScreen.mainScreen().scale
            let iH = (smallImage?.size.height)! * UIScreen.mainScreen().scale
            
            print("\(smallImage)  尺寸 \(smallImage?.size) 当前的比例 \(UIScreen.mainScreen().scale)")
            //4.设置normal情况下的image
            let rect = CGRectMake(CGFloat(index) * iW , 0, iW, iH)
            let norImageRef = CGImageCreateWithImageInRect(smallImage?.CGImage,rect)
            btn .setImage(UIImage(CGImage:norImageRef!), forState: UIControlState.Normal)
            //尺寸 Optional((480.0, 46.0)) 当前的比例 2.0
            //尺寸 Optional((480.0, 46.0)) 当前的比例 3.0
            //5.设置select样式的image
            let selectedSmallImage = UIImage(named: "LuckyAstrologyPressed")
            let seletedImageRef = CGImageCreateWithImageInRect(selectedSmallImage?.CGImage, rect)
            btn.setImage(UIImage(CGImage: seletedImageRef!), forState: UIControlState.Selected)
            centerView.addSubview(btn)

对大图进行了乘以比例的切割后的样子
5.重写btn方法,更改内部image的尺寸

调整image位置

    override func imageRectForContentRect(contentRect: CGRect) -> CGRect {
        let iY:CGFloat = 20
        let iW:CGFloat = 40
        let iH:CGFloat = 47
        let iX:CGFloat = (contentRect.size.width - iH)*0.5
        return CGRectMake(iX, iY, iW, iH)
    }
调整image位置后的样子
6.给btn一个选中的image的照片(还是按照4那样切图)

            let selectedSmallImage = UIImage(named: "LuckyAstrologyPressed")
            let seletedImageRef = CGImageCreateWithImageInRect(selectedSmallImage?.CGImage, rect)
            btn.setImage(UIImage(CGImage: seletedImageRef!), forState: UIControlState.Selected)
7.重写setHightLighit方法 (但是没写明白)

为什么要重写setHightLighit这个方法?因为当你按下去的时候,出去高亮状态,如果你重写了这个方法,就不会有高亮的状态,现在的项目中你按住某个btn,是黑色的,有bug

oc中自定义一个button,内部这样写,就不会有高亮了
- (void)setHighlighted:(BOOL)highlighted{

}

swift中我不会好尴尬,就是截获set方法,然后不让父类实现这个方法 ,我去监听了set,但是感觉内部还是执行了super.setHightlighted方法~~有木有知道这个的同学,给我讲讲吧,谢谢哈

   //swift中截获set方法,但是我怀疑这里面,已经调用了父类的set方法,
    override var highlighted: Bool{
        didSet{
          
        }
    }
8 UIControlEvents.TouchDownUIControlEvents.TouchUpInside的区别

这个想必大家都知道,强者是一按下去,就执行,后者是,按下去,让后抬起来,在执行。现在想执行的效果是--按下去就被选中,就执行

   func btnBeSelected(btn:UIButton){
        //1.先让之前选中的按钮取消选中
      selectedBtn?.selected = false
      //2.让刚刚点的按钮设置成选中状态
        btn.selected = true
        //3.使用全局变量保存刚刚点中的btn
        selectedBtn = btn
    }

代码是这样写的,但是效果不是这样的~不知道咋回事,有时间再看看

9.为装盘添加2个方法,开始和结束

很简单,就是开始和结束

    /**
     开始旋转
     */
    func startRotating()
    {
        
        if (self.link != nil)
        {
            return
        }
        
        //1.生成定时器
        let link = CADisplayLink.init(target: self, selector: "update")
        link.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
        self.link = link
    }
    
    func update()
    {
       centerView.transform = CGAffineTransformRotate(centerView.transform, CGFloat(M_PI/300.0))
    }
    func endRotationing()
    {
        link?.invalidate()
        link = nil
    }
10.CADisplayLink和NSTimer的区别和使用情景

为甚使用的是CADisplayLink
这个方法一秒钟调用60次,可以非常快速,而NSTimer调用的是1秒的,不能像前者调用频率那么快
这两个的使用场景是什么?
特别快的使用CADisplayLink,一秒及以后的调用NSTimer

11.避免多个定时器同时工作,会出什么问题?

开始的方法中必须调用这个

        if (self.link != nil)
        {
            return
        }

防止多个定时器同事工作,一调用开始,先判断是不是有值,如果有,退出,否则多个在一起,越来越来转速,叠加的~

11.细节,(大黄圆盘)交互yes,no

体现有没有工作经验,可以看看有没有这个大黄圆盘的交互,就是当我们点击中间开始按钮的时候,圆盘要快速旋转,但是那时候是不饿能够点击圆盘的任何按钮的,所以,交互式no,其他事yes

12.点击开始按钮

这段代码有点意思,就是先知道,layer层的动画都是假象,点击某个按钮都是不准的,但是UIView的动画是真实的,可以点击到具体的那个Btn的,但是这里我们就是让他快速的旋转,所以给个layer层就好了

这里使用到的是timingFunction,进入缓慢,出来缓慢,我们设置3次,所以我们旋转圈数和时间都乘以3,然后第一圈第三圈都是缓慢的~

    @IBAction func centerBtnClick(sender: AnyObject) {
               endRotationing()
                let anim = CABasicAnimation()
                anim.keyPath = "transform.rotation"
                anim.toValue = M_PI*2*3
                anim.duration = 1.5*3
                anim.removedOnCompletion = false
//                anim.repeatCount = 3
                anim.fillMode = kCAFillModeForwards
        anim.timingFunction = CAMediaTimingFunction.init(name: kCAMediaTimingFunctionEaseInEaseOut)
        anim.delegate = self
                centerView.layer.addAnimation(anim, forKey: "rotationWheel")
    }

监听了结束的代理方法,也执行了,但是2秒之后还是不转,没搞懂,欢迎指教~

    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
        let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(2 * Double(NSEC_PER_SEC)))
        dispatch_after(delayTime, dispatch_get_main_queue()) { () -> Void in
//            print("切换到首页")
            self.startRotating()
        }
    }
底部的btn都是等宽度的,点击可能有误

点击底部的时候,因为btn都是一样大的,重叠了,如何解决?
实际上我们可以让他的底部不能点击,只能点击上边,那么我们就要重写but的方法,设置那些区域可以点击,那些不行

右侧图红色的地方不可以点击,所以返回nil
    /**
     寻找合适的view(可以判断点view的那个位置是不可以点击的,那个是可以点击的)
     
     :param: point 当前点所在位置
     :param: event 点击事件
     
     :returns: 返回合适的view
     */
    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        let x:CGFloat = 0
        let y:CGFloat = 0
        let w:CGFloat = frame.width
        let h:CGFloat = frame.height*0.5
        let rect = CGRectMake(x, y, w, h)
        if(CGRectContainsPoint(rect, point)){
            return super.hitTest(point, withEvent: event)
        }else{
            return nil
        }
    }

demo地址

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

推荐阅读更多精彩内容