在iOS股票分时图的开发中,上方的价格曲线通常只占整个视图的一部分,下方还需要配合一个成交量副图区域。成交量以一根根细密的柱状图呈现,红色代表当前分钟上涨,绿色代表下跌。仔细观察专业的行情软件会发现一个细节:这些成交量柱的顶部两个角是圆角,而底部紧贴横坐标轴的位置是直角。如果简单地使用UIBezierPath(roundedRect:)统一设置圆角,底部也会出现弧度,视觉上与坐标轴之间产生缝隙。本文将详细讲解如何用Core Graphics精确控制圆角的位置,只对顶部做圆角处理。

为什么统一圆角方案不可行
最直观的思路是使用UIKit提供的便捷方法,对每根柱子创建一个带圆角的矩形路径然后填充:
let path = UIBezierPath(roundedRect: barRect,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: 2, height: 2))
UIColor.red.setFill()
path.fill()</code>这个方法其实已经支持byRoundingCorners参数,可以指定只圆化顶部两角,UIKit内部会调用Core Graphics完成绘制。它的优点是代码简洁、可读性好,对于几十根柱子的场景完全够用。
但它的局限在于灵活性不足。分时图中每根柱子的宽度往往只有2到4个点,圆角半径一旦接近柱宽的一半,路径的形状会开始失真;另外某些设计稿要求顶部的红柱和绿柱圆角半径不同,或者需要在柱体内部再做渐变填充,此时便捷方法就难以满足需求。更底层的做法是直接操作Core Graphics的路径API,手动构建这个“顶部圆角、底部平角”的形状,这也是本文的重点。
用Core Graphics手动构建顶部圆角路径
Core Graphics中构建自定义路径的核心是CGMutablePath。绘制一个顶部圆角、底部直角的矩形,本质上是从左下角出发,沿逆时针或顺时针方向依次连接四条边,其中顶部的两个角用圆弧代替直角转折。圆弧使用addArc方法添加,需要明确圆心坐标、半径、起始角度和结束角度。
这里以顺时针方向为例,从左下角开始绘制。左下角是直角,直接move到该点;向上画到左侧圆弧的起点,也就是左上角圆角圆心向下偏移一个半径的位置;接着用一段四分之一圆弧过渡到顶部;沿顶部直线到右侧圆弧起点;再用一段圆弧过渡到右侧边;最后向下画回底部,closeSubpath闭合路径。具体代码如下:
func roundedTopBarPath(rect: CGRect, radius: CGFloat) -> CGPath {
let path = CGMutablePath()
// 半径不能超过柱宽和柱高各自的一半,否则形状会异常
let r = min(radius, rect.width / 2, rect.height / 2)
// 从左下角起点,柱子底部保持直角
path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
// 左侧直边向上,到圆弧起点
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + r))
// 左上角圆弧:圆心为 (minX + r, minY + r),从180度转到270度
path.addArc(center: CGPoint(x: rect.minX + r, y: rect.minY + r),
radius: r,
startAngle: .pi,
endAngle: .pi * 1.5,
clockwise: false)
// 顶部直线
path.addLine(to: CGPoint(x: rect.maxX - r, y: rect.minY))
// 右上角圆弧:从270度转到360度(即0度)
path.addArc(center: CGPoint(x: rect.maxX - r, y: rect.minY + r),
radius: r,
startAngle: .pi * 1.5,
endAngle: .pi * 2,
clockwise: false)
// 右侧直边向下,回到右下角(直角)
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
// 闭合路径,底部两个角均为直角
path.closeSubpath()
return path
}需要特别注意的是Core Graphics的坐标系问题。UIView的draw(_ rect: CGRect)方法中,系统已经将坐标系翻转为我们熟悉的原点在左上角的形式,角度的约定是:0度指向正右方,.pi指向左方,.pi * 1.5指向上方。上面代码中左上角圆弧从.pi(正左)逆时针参数设置为clockwise: false,转到.pi * 1.5(正上),正好是四分之一圆。如果角度算错,绘制出来的圆弧会凸向柱子内部,形成“凹陷”的怪异形状,这是初学者最常踩的坑。
另一个容易忽略的细节是对半径的钳制处理。分时图中成交量柱的高度差异很大,开盘初期某一分钟的成交量可能极小,柱高甚至只有一两个点。如果传入的圆角半径大于柱高的一半,圆弧的圆心会落到柱体外部,路径自相交后填充结果不可预期。所以代码第一行用min函数对半径做了保护,这是保证绘制稳定性的关键一步。
结合涨跌颜色绘制完整的成交量副图
解决了单根柱子的路径问题,接下来把它放入完整的绘制流程。分时图的数据源通常是一个包含时间、价格、成交量的数组,红绿颜色的判定规则是:当前分钟价格大于上一分钟则为红,小于则为绿,相等时沿用前一根的颜色(不同券商的规则略有差异,需与产品确认)。
在draw(_ rect: CGRect)中遍历数据源,为每根柱子计算矩形区域并调用上面的路径函数。计算X坐标时,用总宽度除以柱子数量得到间距,柱子实际宽度取间距的一定比例(如60%),这样柱子之间能留出呼吸感:
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext(), volumes.count > 0 else { return }
let count = max(volumes.count, 240)
let step = rect.width / CGFloat(count)
let barWidth = step * 0.6
let maxVolume = volumes.map { $0 }.max() ?? 1
for (index, volume) in volumes.enumerated() {
// 根据涨跌确定颜色
let isUp = prices[index] >= prices[max(0, index - 1)]
let color = isUp ? UIColor(red: 0.85, green: 0.2, blue: 0.24, alpha: 1)
: UIColor(red: 0.13, green: 0.63, blue: 0.33, alpha: 1)
let barHeight = CGFloat(volume) / CGFloat(maxVolume) * rect.height
let x = CGFloat(index) * step + (step - barWidth) / 2
let barRect = CGRect(x: x, y: rect.height - barHeight,
width: barWidth, height: barHeight)
context.addPath(roundedTopBarPath(rect: barRect, radius: barWidth / 2))
context.setFillColor(color.cgColor)
context.fillPath()
}
}注意柱子的Y坐标计算:分时图坐标原点在左上角,柱子要从底部向上生长,所以y值是rect.height - barHeight。成交量归一化时使用当前已显示数据中的最大值作为基准,这样开盘初期数据量少时,柱子不会被过度放大;等数据积累到全天240根后,整体比例会自动趋于稳定。
性能优化与高频刷新的处理
分时图的特点是需要定时刷新,盘中通常每几秒钟追加或更新一根柱子,如果每次都完整重绘240根柱子,在低端设备上可能带来不必要的开销。优化的方向有两个层面。
第一个层面是避免不必要的setNeedsDisplay调用。可以将成交量柱分层:已经确定的历史柱子绘制到一张CGLayer或离屏位图中缓存起来,每次刷新时只把缓存位图贴回视图,再增量绘制最新的几根柱子。当新的一分钟开始时才重建整张缓存,这样大部分刷新周期内的绘制量从240根降到1根左右。
第二个层面是路径合并。Core Graphics每次fillPath都有上下文状态切换的成本,可以把相同颜色的柱子路径全部addPath到同一个CGMutablePath里,最后分别用红色和绿色各执行一次fillPath,绘制调用次数从240次降到2次。实测这种合并策略在中端机型上可以将单帧绘制耗时降低约一半,对于滚动流畅度的提升相当明显。
最后提一点兼容性建议:如果项目最低支持iOS 13以上,也可以考虑用CAShapeLayer承载柱状图,把路径生成逻辑复用过去,配合CADisplayLink做柱子生长动画会更容易。但对于纯静态的高密度柱状图,直接在draw(_ rect: CGRect)中用Core Graphics绘制仍然是最轻量、最可控的方案,这也是主流行情软件普遍采用的方式。
Core Graphics分时图圆角绘制修改时间:2026-08-31 16:17:14