• iOS LED跑马灯效果实现


    iOS中实现LED跑马灯效果

    实现原理是使用scrollView, 将需要滚动的label添加两次到 scrollView的subView下面, 然后通过滚动scrollView来实现跑马灯效果。

    具体实现代码如下:

    //
    //  KMScrollLabel.swift
    //  StopSmokingPrograms
    //
    //  Created by Fran on 15/11/2.
    //  Copyright © 2015年 kimree. All rights reserved.
    //
    
    import UIKit
    
    // 文字滚动方向
    enum KMScrollDirection: Int{
        case Left = 0
        case Right
        case Up
        case Down
    }
    
    class KMScrollLabel: UIScrollView {
        
        // label 是只读的
        // 在外部只修改label的text即可
        private var label = UILabel()
        var textLabel: UILabel{
            get{
                return label
            }
        }
        
        // label是否在滑动
        private var keepScroll = false
        
        // 动画时间
        var animationDuringTime:Double = 5
        
        deinit{
            KMLog("KMScrollLabel deinit")
        }
        
        convenience init(){
            self.init(frame: CGRectZero)
        }
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            KMLog("init frame")
            
            self.scrollEnabled = false
            self.showsHorizontalScrollIndicator = false
            self.showsVerticalScrollIndicator = false
            self.addSubview(label)
        }
        
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            KMLog("init aDecoder")
            
            self.scrollEnabled = false
            self.showsHorizontalScrollIndicator = false
            self.showsVerticalScrollIndicator = false
            self.addSubview(label)
        }
        
        // MARK: - 开始滑动
        func startScroll(direction: KMScrollDirection = .Left){
            fitFrame(direction)
            
            // 当label不处于滑动状态时, 才可以开始滑动, 避免动画效果叠加
            if !keepScroll{
                keepScroll = true
                self.contentOffset = CGPointZero
                beginScroll(direction)
            }
        }
        
        // removeAllAnimations 之后会立刻执行 animation block 的 completion 部分, 
        // 目前无法确定 completion 部分的参数总会是 false, 为了避免巧合, 需要配合 keepScroll 参数一起使用
        func stopScroll(){
            keepScroll = false
            self.layer.removeAllAnimations()
        }
        
        // MARK: - 具体怎样滑动
        func beginScroll(direction: KMScrollDirection = .Left){
            let w = label.frame.size.width
            let h = label.frame.size.height
            
            switch direction{
            case .Left:
                self.contentOffset.x = 0
                UIView.animateWithDuration(animationDuringTime, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: { [weak self]() -> Void in
                    self?.contentOffset.x = w
                    }, completion: { [weak self](finished: Bool) -> Void in
                        if finished && (self != nil && self!.keepScroll){
                            self!.contentOffset.x = 0
                            self!.beginScroll(direction)
                        }
                    })
                
            case .Right:
                self.contentOffset.x = w
                UIView.animateWithDuration(animationDuringTime, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: { [weak self]() -> Void in
                    self?.contentOffset.x = 0
                    }, completion: { [weak self](finished: Bool) -> Void in
                        if finished && (self != nil && self!.keepScroll){
                            self!.contentOffset.x = w
                            self!.beginScroll(direction)
                        }
                    })
                
            case .Up:
                self.contentOffset.y = 0
                UIView.animateWithDuration(animationDuringTime, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: { [weak self]() -> Void in
                    self?.contentOffset.y = h
                    }, completion: { [weak self](finished: Bool) -> Void in
                        if finished && (self != nil && self!.keepScroll){
                            self!.contentOffset.y = 0
                            self!.beginScroll(direction)
                        }
                    })
                
            case .Down:
                self.contentOffset.y = h
                UIView.animateWithDuration(animationDuringTime, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: { [weak self]() -> Void in
                    self?.contentOffset.y = 0
                    }, completion: { [weak self](finished: Bool) -> Void in
                        if finished && (self != nil && self!.keepScroll){
                            self!.contentOffset.y = h
                            self!.beginScroll(direction)
                        }
                    })
            }
            
        }
        
        // MARK: - 适配frame
        private func fitFrame(direction: KMScrollDirection){
            // 按照水平方向移动的要求修改frame
            func fitFrameAtHorizontalDirection(){
                
                let width = self.frame.size.width
                var height = self.frame.size.height
                
                label.numberOfLines = 1
                label.lineBreakMode = .ByWordWrapping
                var size = label.sizeThatFits(CGSizeMake(CGFloat.max, height))
                
                // scroll 的 height 不能小于 label 的 height
                if height < size.height{
                    self.frame.size.height = size.height
                    height = size.height
                }
                
                // label 的 width 要加上 scroll width 的一半作为留白, 并且 label 的 width 不能小于 scroll 的 width
                size.width += width / 2.0
                if size.width < width{
                    size.width = width
                }
                
                label.frame = CGRectMake(0, (height - size.height) / 2.0, size.width, size.height)
                
                let tempLabel = NSKeyedUnarchiver.unarchiveObjectWithData(NSKeyedArchiver.archivedDataWithRootObject(label)) as! UILabel
                tempLabel.frame.origin.x += size.width
                self.addSubview(tempLabel)
                
                self.contentSize = CGSizeMake(2 * size.width, height)
            }
            
            // 按照竖直方向移动的要求修改frame
            func fitFrameAtVerticalDirection(){
                
                var width = self.frame.size.width
                let height = self.frame.size.height
                
                label.numberOfLines = 0
                label.lineBreakMode = .ByWordWrapping
                let originalText = label.text
                
                // 获取单个文字的宽度
                label.text = "田"
                let singleSize = label.sizeThatFits(CGSizeZero)
                
                label.text = originalText
                var size = label.sizeThatFits(CGSizeMake(singleSize.width, CGFloat.max))
                
                if width < size.width{
                    self.frame.size.width = size.width
                    width = size.width
                }
                
                size.height += height / 2.0
                if size.height < height{
                    size.height = height
                }
                
                label.frame = CGRectMake((width - size.width) / 2.0, 0, size.width, size.height)
                
                let tempLabel = NSKeyedUnarchiver.unarchiveObjectWithData(NSKeyedArchiver.archivedDataWithRootObject(label)) as! UILabel
                tempLabel.frame.origin.y += size.height
                self.addSubview(tempLabel)
                
                self.contentSize = CGSizeMake(width, 2 * size.height)
            }
            
            // 首先遍历 scroll 的所有 subView, 将占位的 label 移除
            for view in self.subviews{
                if view != label{
                    view.removeFromSuperview()
                }
            }
            
            switch direction{
            case .Left, .Right:
                fitFrameAtHorizontalDirection()
            case .Up, .Down:
                fitFrameAtVerticalDirection()
            }
            
        }
        
        /*
        // Only override drawRect: if you perform custom drawing.
        // An empty implementation adversely affects performance during animation.
        override func drawRect(rect: CGRect) {
        // Drawing code
        }
        */
        
    }
    

      注意: 如果在有navigationController的界面使用时,由于跑马灯中使用到了UIScrollView,此时跑马灯的Label显示不正常,解决方法:

            self.automaticallyAdjustsScrollViewInsets = false
            
            // 不想采用上诉方式时, 可以调整scrollView的contentInset来达到目的
            // 需要在 kmScrollView.startScroll() 之后调用
    //        kmScrollView.contentInset.top = -64
    

      

  • 相关阅读:
    设计模式复习【1】- 设计原则
    Java8的学习笔记
    Gson关于抽象类的序列化与反序列化
    关于一个Java web与JFrame的深度结合
    《重构》笔记
    JAVA8 Stream API总结的好的文章 —— 持续更
    Spring Boot引入Thymeleaf前端框架的诸多问题
    敏捷开发:原则,模式与实践——第8章 单一职责原则SRP
    maven 你应该懂得那些事
    redis之单机和主从环境搭建
  • 原文地址:https://www.cnblogs.com/FranZhou/p/5128006.html
Copyright © 2020-2023  润新知