草庐IT

iOS:将圆形切片动画化为更宽的切片

coder 2023-09-22 原文

Core-Animation 处理角度如下图所示:
(图片来自 http://btk.tillnagel.com/tutorials/rotation-translation-matrix.html)

编辑:添加动画 gif 以更好地解释我需要的内容:



我需要为切片设置动画以使其变宽,从 300:315 度开始,到 300:060 结束。

要创建每个切片,我正在使用此函数:

extension CGFloat {
    func toRadians() -> CGFloat {
        return self * CGFloat(Double.pi) / 180.0
    }
}
func createSlice(angle1:CGFloat, angle2:CGFloat) -> UIBezierPath! {
    let path: UIBezierPath = UIBezierPath()
    let width: CGFloat = self.frame.size.width/2
    let height: CGFloat = self.frame.size.height/2
    let centerToOrigin: CGFloat = sqrt((height)*(height)+(width)*(width));
    let ctr: CGPoint = CGPoint(x: width, y: height)
    path.move(to: ctr)
    path.addArc( withCenter: ctr,
                 radius: centerToOrigin,
                 startAngle: CGFloat(angle1).toRadians(),
                 endAngle: CGFloat(angle2).toRadians(),
                 clockwise: true
    )
    path.close()
    return path
}

我现在可以创建两个切片和一个带有较小切片的子层,但我无法找到如何从这一点开始:
func doStuff() {
    path1 = self.createSlice(angle1: 300,angle2: 315)
    path2 = self.createSlice(angle1: 300,angle2: 60)

    let shapeLayer = CAShapeLayer()
    shapeLayer.path = path1.cgPath
    shapeLayer.fillColor = UIColor.cyan.cgColor
    self.layer.addSublayer(shapeLayer)

我非常感谢这里的任何帮助!

最佳答案

只有一种颜色

如果您想像问题中的那样为纯色填充饼段的角度设置动画,那么您可以通过为 strokeEnd 设置动画来实现。的 CAShapeLayer .

这里的“技巧”是画一条很宽的线。更具体地说,您可以在预期半径的一半处创建一个仅是弧形的路径(下面动画中的虚线),然后为其指定完整半径作为其线宽。当你动画抚摸那条线时,它看起来像下面的橙色部分:



根据您的用例,您可以:

  • 创建从一个角度到另一个角度的路径,并将笔划末端从 0 设置为 1
  • 为一个完整的圆创建一条路径,将笔画开始和笔画结束设置为圆的某个部分,并将笔画结束从开始部分设置为结束部分。

  • 如果您的绘图只是这样的单一颜色,那么这将是您问题的最小解决方案。

    但是,如果您的绘图更复杂(例如,还要抚摸饼图段),那么此解决方案根本不起作用,您将不得不做一些更复杂的事情。

    自定义绘图/自定义动画

    如果您绘制的饼段更加复杂,那么您很快就会发现自己必须创建一个具有自定义动画属性的图层子类。这样做需要更多的代码——其中一些可能看起来有点不寻常1——但并不像听起来那么可怕。
  • 这可能是在 Objective-C 中更方便的事情之一。

  • 动态属性

    首先,使用您将需要的属性创建一个图层子类。在 Objective-C 的说法中,这些属性应该是 @dynamic ,即未合成。这与 dynamic 不同在 swift 。相反,我们必须使用 @NSManaged .
    class PieSegmentLayer : CALayer {
        @NSManaged var startAngle, endAngle, strokeWidth: CGFloat
        @NSManaged var fillColor, strokeColor: UIColor?
    
        // More to come here ...
    }
    

    这允许 Core Animation 动态处理这些属性,从而允许它跟踪更改并将它们集成到动画系统中。

    备注 :一个好的经验法则是这些属性应该全部 与图层的绘图/视觉呈现相关。如果它们不是,那么它们很可能不属于该层。相反,它们可以添加到 View 中,而 View 又使用图层进行绘图。

    复制图层

    在自定义动画期间,Core Animation 将要为不同的帧创建和渲染不同的层配置。与 Apple 的大多数其他框架不同,这是使用复制构造函数 init(layer:) 发生的。 .对于要一起复制的上述五个属性,我们需要覆盖 init(layer:)并复制它们的值。

    在 Swift 中,我们还必须覆盖普通的 init()init?(coder) .
    override init(layer: Any) {
        super.init(layer: layer)
        guard let other = layer as? PieSegmentLayer else { return }
    
        fillColor   = other.fillColor
        strokeColor = other.strokeColor
        startAngle  = other.startAngle
        endAngle    = other.endAngle
        strokeWidth = other.strokeWidth
    }
    
    override init() {
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        return nil
    }
    

    对变化使用react

    核心动画在很多方面都是为性能而构建的。实现这一目标的方法之一是避免不必要的工作。默认情况下,当属性更改时,图层不会重绘自身。但是这些属性用于绘制,我们希望图层在其中任何一个发生变化时重新绘制。为此,我们需要覆盖 needsDisplay(forKey:)并返回 true如果键是这些属性之一。
    override class func needsDisplay(forKey key: String) -> Bool {
        switch key {
        case #keyPath(startAngle), #keyPath(endAngle),
             #keyPath(strokeWidth),
             #keyPath(fillColor), #keyPath(strokeColor):
            return true
    
        default:
            return super.needsDisplay(forKey: key)
        }
    }
    

    此外,如果我们希望这些属性的图层默认隐式动画,我们需要覆盖 action(forKey:)返回一个部分配置的动画对象。如果我们只希望某些属性(例如角度)隐式动画,那么我们只需要为这些属性返回动画。除非我们需要非常自定义的东西,否则最好只返回带有 fromValue 的基本动画。设置为当前呈现值:
    override func action(forKey key: String) -> CAAction? {
        switch key {
        case #keyPath(startAngle), #keyPath(endAngle):
            let anim = CABasicAnimation(keyPath: key)
            anim.fromValue = presentation()?.value(forKeyPath: key)
            return anim
    
        default:
            return super.action(forKey: key)
        }
    }
    

    画画

    自定义动画的最后一部分是自定义绘图。这是通过覆盖 draw(in:) 来完成的并使用提供的上下文绘制图层:
    override func draw(in ctx: CGContext) {
        let center = CGPoint(x: bounds.midX, y: bounds.midY)
        // subtract half the stroke width to avoid clipping the stroke
        let radius = min(center.x, center.y) - strokeWidth / 2
    
        // The two angle properties are in degrees but CG wants them in radians.
        let start = startAngle * .pi / 180
        let end   = endAngle   * .pi / 180
    
        ctx.beginPath()
        ctx.move(to: center)
    
        ctx.addLine(to: CGPoint(x: center.x + radius * cos(start),
                                y: center.y + radius * sin(start)))
    
        ctx.addArc(center: center, radius: radius,
                   startAngle: start, endAngle: end,
                   clockwise: start > end)
    
        ctx.closePath()
    
        // Configure the graphics context
        if let fillCGColor = fillColor?.cgColor {
            ctx.setFillColor(fillCGColor)
        }
        if let strokeCGColor = strokeColor?.cgColor {
            ctx.setStrokeColor(strokeCGColor)
        }
        ctx.setLineWidth(strokeWidth)
    
        ctx.setLineCap(.round)
        ctx.setLineJoin(.round)
    
        // Draw
        ctx.drawPath(using: .fillStroke)
    }
    

    在这里,我填充并描画了一个从图层中心延伸到最近边缘的饼图段。您应该将其替换为您的自定义绘图。

    Action 中的自定义动画

    有了所有这些代码,我们现在有一个自定义图层子类,其属性可以隐式(仅通过更改它们)和显式(通过为其键添加 CAAnimation)进行动画处理。结果如下所示:



    最后的话

    这些动画的帧速率可能并不明显,但在这两种解决方案中利用 Core Animation(以不同方式)的一个强大好处是,它将单个状态的绘制与动画的时间分离。

    这意味着该层不知道也不必知道持续时间、延迟、时序曲线等。这些都可以在外部进行配置和控制。

    关于iOS:将圆形切片动画化为更宽的切片,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44886406/

    有关iOS:将圆形切片动画化为更宽的切片的更多相关文章

    1. ruby - 如何验证 IO.copy_stream 是否成功 - 2

      这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

    2. ruby-on-rails - 将 Ruby 中的日期/时间格式化为 YYYY-MM-DD HH :MM:SS - 2

      这个问题在这里已经有了答案:Railsformattingdate(4个答案)关闭4年前。我想格式化Time.Now函数以显示YYYY-MM-DDHH:MM:SS而不是:“2018-03-0909:47:19+0000”该函数需要放在时间中.现在功能。require‘roo’require‘roo-xls’require‘byebug’file_name=ARGV.first||“Template.xlsx”excel_file=Roo::Spreadsheet.open(“./#{file_name}“,extension::xlsx)xml=Nokogiri::XML::Build

    3. Ruby 文件 IO 定界符? - 2

      我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

    4. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

      Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

    5. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

      1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

    6. ruby - 在 Ruby 中将整数格式化为固定长度的字符串 - 2

      有没有一种简单的方法可以将给定的整数格式化为具有固定长度和前导零的字符串?#convertnumberstostringsoffixedlength3[1,12,123,1234].map{|e|???}=>["001","012","123","234"]我找到了解决方案,但也许还有更聪明的方法。format('%03d',e)[-3..-1] 最佳答案 如何使用%1000而不是进行字符串操作来获取最后三位数字?[1,12,123,1234].map{|e|format('%03d',e%1000)}更新:根据theTinMan的

    7. ruby - 为什么不能使用类IO的实例方法noecho? - 2

      print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

    8. ruby-on-rails - Ruby on Rails 将列表拆分或切片为列 - 2

      @locations=Location.all#currentlistingall@locations=Location.slice(5)orLocation.split(5)使用Ruby,我试图将我的列表分成4列,每列限制为5个;然而,切片或拆分似乎都不起作用。知道我可能做错了什么吗?任何帮助是极大的赞赏。 最佳答案 您可能想使用in_groups_of:http://railscasts.com/episodes/28-in-groups-of这是RyanBates在railscast中的示例用法:

    9. ruby-on-rails - 如何以递归方式将 YAML 文件扁平化为 JSON 对象,其中键是点分隔的字符串? - 2

      例如,如果我有YAML文件en:questions:new:'NewQuestion'other:recent:'Recent'old:'Old'这最终会变成一个json对象,例如{'questions.new':'NewQuestion','questions.other.recent':'Recent','questions.other.old':'Old'} 最佳答案 由于问题是关于在Rails应用程序上使用YAML文件进行i18n,因此值得注意i18ngem提供了一个辅助模块I18n::Backend::Flatten完全像

    10. LVGL V8动画 - 2

      动画/*INITIALIZEANANIMATION 初始化一个动画*-----------------------*/lv_anim_ta;lv_anim_init(&a);/*MANDATORYSETTINGS 必选设置*------------------*//*Setthe"animator"function 设置“动画”功能*/lv_anim_set_exec_cb(&a,(lv_anim_exec_xcb_t)lv_obj_set_x);/*Setthe"animator"function*/lv_anim_set_var(&a,obj);/*Lengthoftheanim

    随机推荐