带遮罩的 LinearGradient 不会填满整个区域

LinearGradient with mask does not fill the whole area

我正在创建折线图视图,灵感来自 this blog post

线条是用 Path 绘制的,渐变是用 LinearGradient 和遮罩绘制的,它们相互重叠。

我的主要问题是,正如您在屏幕截图中看到的那样,所有渐变的开始都有一个斜率,如果值为零,它们似乎关闭得太快 (而且结尾似乎剪得太早了,但这不是我的主要问题)。如果值 > 0(如橙色的),启动似乎也开始得太晚了。

我想要的是渐变完全填充红色箭头指向的部分(去除斜坡和突然切割)。

但我无法通过随机组合幻数找到解决方案。此外,我更愿意了解正在发生的事情,而不是四处闲逛。

我做错了什么?


我做了一个可重现的例子,粘贴这是一个 ContentView:

struct ContentView: View {
    
    @State var values: [Double] = [0, 0, 1, 2, 0, 6, 4, 0, 1, 1, 0, 0]
    @State var totalMaxnum: Double = 6
    
    var body: some View {
        GeometryReader { proxy in
            ScrollView {
                VStack {
                    // some other view
                    
                    VStack {
                        VStack {
                            RoundedRectangle(cornerRadius: 5)
                                .overlay(
                                    ZStack {
                                        Color.white.opacity(0.95)
                                        
                                        drawValuesLine(values: values, color: .blue)
                                    }
                                )
                                .overlay(
                                    ZStack {
                                        Color.clear
                                        
                                        drawValuesGradient(values: values, start: .orange, end: .red)
                                    }
                                )
                                .frame(width: proxy.size.width - 40, height: proxy.size.height / 4)
                            
                            // some other view
                        }
                        
                        // some other view
                    }
                }
            }
        }
    }
    
    func drawValuesLine(values: [Double], color: Color) -> some View {
        GeometryReader { geo in
            Path { p in
                let scale = (geo.size.height - 40) / CGFloat(totalMaxnum)
                var index: CGFloat = 0
                
                let y1: CGFloat = geo.size.height - 10 - (CGFloat(values[0]) * scale)
                let y = y1.isNaN ? 0 : y1
                p.move(to: CGPoint(x: 8, y: y))
                
                for _ in values {
                    if index != 0 {
                        let x1: CGFloat = 8 + ((geo.size.width - 16) / CGFloat(values.count)) * index
                        let x = x1.isNaN ? 0 : x1
                        let yy: CGFloat = geo.size.height - 10 - (CGFloat(values[Int(index)]) * scale)
                        let y = yy.isNaN ? 0 : yy
                        p.addLine(to: CGPoint(x: x, y: y))
                    }
                    index += 1
                }
                
                
            }
            .stroke(style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round, miterLimit: 80, dash: [], dashPhase: 0))
            .foregroundColor(color)
        }
    }
    
    func drawValuesGradient(values: [Double], start: Color, end: Color) -> some View {
        LinearGradient(gradient: Gradient(colors: [start, end]), startPoint: .top, endPoint: .bottom)
            .padding(.bottom, 1)
            .opacity(0.8)
            .mask(
                GeometryReader { geo in
                    Path { p in
                        let scale = (geo.size.height - 40) / CGFloat(totalMaxnum)
                        var index: CGFloat = 0
                        
                        // Move to the starting point
                        let y1: CGFloat = geo.size.height - (CGFloat(values[Int(index)]) * scale)
                        let y = y1.isNaN ? 0 : y1
                        p.move(to: CGPoint(x: 8, y: y))
                        
                        // Draw the lines
                        for _ in values {
                            if index != 0 {
                                let x1: CGFloat = 8 + ((geo.size.width - 16) / CGFloat(values.count)) * index
                                let x = x1.isNaN ? 0 : x1
                                let yy: CGFloat = geo.size.height - 10 - (CGFloat(values[Int(index)]) * scale)
                                let y = yy.isNaN ? 0 : yy
                                p.addLine(to: CGPoint(x: x, y: y))
                            }
                            index += 1
                        }
                        
                        // Close the subpath
                        let x1: CGFloat = 8 + ((geo.size.width - 16) / CGFloat(values.count)) * (index - 1)
                        let x = x1.isNaN ? 0 : x1
                        p.addLine(to: CGPoint(x: x, y: geo.size.height))
                        p.addLine(to: CGPoint(x: 8, y: geo.size.height))
                        p.closeSubpath()
                    }
                }
            )
    }
}

您可以在屏幕截图中看到橙色渐变如何无法正确填充线条下方的所有区域:

问题出在这一行:

let y1: CGFloat = geo.size.height - (CGFloat(values[Int(index)]) * scale)

y1成为整个图的高度。

您需要减去 10,因为这是您对蓝线和所有渐变的其他点所做的。

                              /// subtract 10
let y1: CGFloat = geo.size.height - 10 - (CGFloat(values[Int(index)]) * scale)