在SwiftUI中使用相对坐标Shape绘制

在SwiftUI中使用相对坐标Shape绘制

Swift UI 提供了一些内置的基本形状,如矩形,圆角矩形,圆和椭圆等。 实际开发过程中,这些形状远远不够,需要增加新的定制形状,比如,三角形,或者复杂一点的水滴形状等。

要增加新的自定义形状,只要实现SwiftUI 提供Shape协议即可。下面是自定义一个三角形的例子:

swift 复制代码
struct Triangle: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        let centerX = rect.width / 2
        let centerY = rect.height / 2
        path.move(to: CGPoint(x: centerX, y: 0))
        path.addLine(to: CGPoint(x: 0, y: centerY))
        path.addLine(to: CGPoint(x: centerX, y: rect.height))
        path.closeSubpath()
        return path
    }
}

在View中可以直接使用上述三角形:

swift 复制代码
#Preview
{
    HStack
    {
        Triangle()
            .stroke(Color.red)
            .frame(width: 100, height: 100)
            .background(Color.gray.opacity(0.  3))
  }   
}

显示效果如下:

在Shape协议中,所有的形状都在一个容器矩形(CGRect)中绘制。 绘制的关键是在如何确定形状中的一些关键点,并最终用容器矩形的四个参数minX,minY,maxX,maxY表达出这些关键点。 (矩形本身的长和宽也可以用上面四个参数表达)

上面绘制三角形的代码其实很简单,这主要是因为三角形很简单。但仔细观察 ,可以发现绘制代码并不好理解, 代码中充斥着各种CGPoint,这些CGPoint有些是关键点,有些是辅助点,它们的坐标需要根据 rect计算得出,都需要我们转几个弯才能理解。

想象一下,如果定制形状比较复杂,特别是一些有控制点的曲线形状,上述计算CGPoint的过程就非常容易出错,而且由于都是变量,对这些CGPoint在rect中的位置也没有直观认识。如果这些形状还需要一些外部参数控制的话,情况可能会变得更糟糕。

使用相对坐标简化绘制

使用相对坐标可以解决上述问题。我们首先引进相对坐标的概念,定义屏幕上任意点(X,Y)相对容器矩形rect的相对坐标为(ratioX,ratioY),其中,ratiXratioY 定义如下:

swift 复制代码
ratioX  = ( X - minX )/(maxX-minX)
ratioY  = ( Y - minY )/(maxY-minY)

相对坐标这个名称其实不尽合理,从数学上看,归一坐标或者标准坐标或正则坐标也许更合理些,不过这些名词不那么接地气。

相对坐标的主要思路是,将容器矩形rect,无论大小,都缩放到一个单位矩形,这在数学上可以对rect的坐标系进行了两次变换,一次平移变换,一次伸缩变换来实现。

容易看出,如果点(X,Y)位于矩形rect内,则其相对坐标分量均大于等于0且小于等于1 。实际上,点(X,Y)可以是屏幕上任何位置,可以在矩形内也可以在矩形外,因此其相对坐标不必都在0和1之间,大于1,小于0均是有可能的。实际上,由于形状的绘制均在容器矩形内,所以,其相对坐标的范围也就都在0和1之间了。

比较一下分别用相对坐标和绝对坐标表达的容器矩形中的几个关键点 :

位置 绝对坐标 相对坐标
矩形左下角 (minX,minY) (0,0)
矩形右上角 (maxX,maxY) (1,1)
矩形中心 (midX,midY) (0.5,0.5)

可以看到,相对于绝对坐标,相对坐标的表达更加简单,清晰直观。这正是我们简化Shape形状绘制的基础。

使用相对坐标进行绘制

如果已经知道相对坐标(ratioX,ratioY),则我们很容易用下面公式计算其绝对坐标:

swift 复制代码
X   =   minX + ratioX*(maxX-minX)
Y   =   minY + ratioY*(maxY-minY)

为了使用相对坐标进行绘制,我们必须按上述公式将相对坐标转换成绝对坐标,为此,首先对rect进行扩展,使之具有处理相对坐标的能力:

swift 复制代码
extension CGRect
{
    func xy(_ ratioX:CGFloat,_ ratioY:CGFloat )->CGPoint
    {
        return CGPoint (x: self.minX + ratioX * self.width ,y:self.minY + ratioY  * self.height )
    }
     
}     
  //备注容器矩形的长和宽
  //width   =  maxX - minX  
  //height  =  maxY - minY  

上述扩展为CGRect类增加一个xy()的方法,该函数接受相对坐标为参数,用我们的上述公式将该相对坐标转换为绝对坐标,用CGPoint对象表示,各种具体的绘制方法均以CGPoint 对象为基础进行。有了上述扩展后,我们就可以用相对坐标绘制了。

下面是使用相对坐标后的三角形绘制代码,

swift 复制代码
struct TriangleA: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: rect.xy(0.5, 0))
        path.addLine(to: rect.xy(0, 0.5))
        path.addLine(to: rect.xy(0.5, 1)) 
        path.closeSubpath()
        return path
    }
}

可以看到,新代码不仅更简洁,更清晰,也更容易理解了。

同时使用两种方式绘制三角形:

swift 复制代码
#Preview
{
    HStack
    {
        //按绝对坐标绘制的三角形
        Triangle()
            .stroke(Color.red)
            .frame(width: 100, height: 100)
            .background(Color.gray.opacity(0.3))
        //按相对坐标绘制的三角形
         TriangleA()
            .stroke(Color.blue)
            .frame(width: 100, height: 100)
            .background(Color.gray.opacity(0.3))
   }   
    
}

下面是显示效果,红色色使用绝对坐标绘制的三角形,蓝色是使用相对坐标绘制的三角形。可以看到,二者绘制效果完全一致:

相关推荐
_.Switch1 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光1 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   1 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   1 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web1 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常1 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇2 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr2 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho3 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常4 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js