You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SKCanvas高效绘制方案:仅绘制可视区域内的点和线段解决卡顿内存不足问题

问题根因

你当前代码存在两个核心性能问题:

  1. 每次绘制时都重新创建SKPaint对象,该对象属于重资源,频繁创建销毁会带来不必要的内存和CPU开销
  2. 全量遍历所有历史点位绘制线段,运行数小时后点位数量可达数万甚至数十万级别,每次绘制都全量执行绘制逻辑,GPU/CPU负载持续走高,最终导致内存不足、卡顿。
核心优化思路

要实现仅绘制可视区域内的轨迹,核心逻辑是先判断线段是否与当前视口相交,仅绘制相交的线段,同时配合前置优化减少不必要的资源消耗。

具体实现步骤
  • 第一步:将SKPaint提取为类成员变量,仅在参数变化时更新,避免每次绘制重复创建
  • 第二步:获取当前地图的可视区域边界(根据你自定义地图的平移、缩放参数计算,返回地图坐标体系下的视口矩形)
  • 第三步:遍历点位时,先判断前后两点组成的线段是否与视口相交,仅对相交的线段执行绘制操作
修改后的代码示例
// 类成员变量,仅初始化或线宽、颜色变化时更新
private SKPaint _linePaint = new SKPaint
{
    Style = SKPaintStyle.Stroke,
    Color = SKColors.White,
    StrokeJoin = SKStrokeJoin.Round,
    StrokeCap = SKStrokeCap.Round,
    IsAntialias = true
};

// 绘制方法内的逻辑
// 1. 先获取当前可视区域(你需要根据自己的地图缩放平移逻辑实现该方法,返回地图坐标下的视口矩形)
SKRect viewport = GetCurrentMapViewport();
_linePaint.StrokeWidth = lineWidth * 2f;

if (localPoints.Count < 2) return;

Point previousPoint = localPoints[0];
for (int i = 1; i < localPoints.Count; i++)
{
    Point currentPoint = localPoints[i];
    // 判断当前线段是否与可视区域相交
    if (SegmentIntersectsRect(previousPoint, currentPoint, viewport))
    {
        canvas.DrawLine(previousPoint.X, previousPoint.Y, currentPoint.X, currentPoint.Y, _linePaint);
    }
    previousPoint = currentPoint;
}

// 线段与矩形相交判断的辅助方法,也可以直接用SkiaSharp内置的SKRect.Intersects方法扩展实现
private bool SegmentIntersectsRect(Point p1, Point p2, SKRect rect)
{
    // 先判断两个端点是否有任意一个在矩形内
    if (rect.Contains(p1.X, p1.Y) || rect.Contains(p2.X, p2.Y))
    {
        return true;
    }
    // 再判断线段是否和矩形四条边相交
    if (LineIntersectsLine(p1, p2, new Point(rect.Left, rect.Top), new Point(rect.Right, rect.Top))) return true;
    if (LineIntersectsLine(p1, p2, new Point(rect.Right, rect.Top), new Point(rect.Right, rect.Bottom))) return true;
    if (LineIntersectsLine(p1, p2, new Point(rect.Right, rect.Bottom), new Point(rect.Left, rect.Bottom))) return true;
    if (LineIntersectsLine(p1, p2, new Point(rect.Left, rect.Bottom), new Point(rect.Left, rect.Top))) return true;
    return false;
}

// 两条线段相交判断的辅助方法
private bool LineIntersectsLine(Point a1, Point a2, Point b1, Point b2)
{
    float ccw(Point p1, Point p2, Point p3)
    {
        return (p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X);
    }
    return ccw(a1, a2, b1) * ccw(a1, a2, b2) < 0 && ccw(b1, b2, a1) * ccw(b1, b2, a2) < 0;
}
额外高阶优化(针对点位特别多的场景)
  • 点位预索引:将历史点位按网格或R树建立空间索引,绘制前直接筛选出视口覆盖范围内的点位集合,不用每次全量遍历所有历史点位
  • 轨迹抽稀:根据当前缩放等级,用道格拉斯-普克算法对轨迹进行抽稀,减少需要绘制的线段数量
  • 离屏缓存:将已经绘制过的、不随视口频繁变化的轨迹区域缓存为位图,绘制时直接贴位图,避免重复绘制大量线段

内容的提问来源于stack exchange,提问作者Dan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 18:36:03