美文网首页iOS进阶指南
手把手教你写“旋转的圆盘”

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

作者: mkb2 | 来源:发表于2016-09-02 20:59 被阅读740次

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

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

基本思路
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地址

相关文章

网友评论

  • C_HPY:不错哦,学习了。
    mkb2:@C己__ 一起进步

本文标题:手把手教你写“旋转的圆盘”

本文链接:https://www.haomeiwen.com/subject/jpjhettx.html