ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Android Path 绘制全解析:moveTo、lineTo、quadTo、cubicTo、arcTo 详解

Android Path 绘制全解析:moveTo、lineTo、quadTo、cubicTo、arcTo 详解

1. 为什么值得把 Path 的这几个方法吃透

做 Android 自定义 View 的朋友大概都有过这种体验:拿到设计稿,看着一条曲线挺简单,真到onDraw里下手,moveTo、lineTo、quadTo、cubicTo、arcTo五个方法摆在那儿,一时不知道从哪个开始写。更尴尬的是,写出来了,形状也差不多,但拐角发虚、弧线接不上、动画跑起来一顿一顿的。这类问题十有八九不是画笔的问题,而是 Path 的构建逻辑没理顺。

这篇内容就围绕这五个方法展开,讲清楚它们各自负责什么、参数怎么算、什么场景该用哪个、组合起来怎么画出一条顺滑的路径。适合已经能写基础自定义 View、但一遇到曲线和圆弧就发怵的开发者;也适合刚接触Path不久、想系统把这块补齐的新手。文中所有示例都是模拟项目里的通用写法,不绑定任何具体业务,你可以直接拿去改。

先把结论摆前面:moveTo是抬笔定位,lineTo是画直线,quadTo是画一段二次贝塞尔曲线(一个控制点),cubicTo是画一段三次贝塞尔曲线(两个控制点),arcTo是往路径里追加一段椭圆弧。五个方法本质上都在做同一件事——往Path对象里追加几何片段,区别只在于片段的形状和参数表达方式。理解了这一点,后面所有的组合技巧都是水到渠成。

我在实际项目里踩过最多的坑,是把arcTo的两个重载混着用,结果路径莫名其妙多出一条连线;其次是quadTo的控制点凭感觉给,曲线形状完全不受控。这些问题在后面章节会逐个拆开讲,先把每个方法的脾气摸清楚。

2. 五个方法的核心定位与参数拆解

2.1 moveTo 与 lineTo:路径的起点与直线骨架

moveTo(float x, float y)的作用是设置下一个片段的起始点,它本身不产生任何可见图形,相当于把画笔“抬起来”挪到新位置。lineTo(float x, float y)则是从当前点画一条直线到目标点,画完之后当前点自动更新为目标点。

这两个方法看着简单,但有一个细节很多人没注意:Path 的第一个片段如果是lineTo,它会默认从(0,0)开始画。也就是说,如果你不先moveTo,第一条线会从画布左上角拉过来,屏幕上就多出一条莫名其妙的斜线。这个现象在调试时特别容易让人抓狂,因为代码逻辑看起来完全没问题。

Path path = new Path(); // 错误示范:没有 moveTo,第一条线从 (0,0) 拉过来 path.lineTo(200, 200); // 正确写法:先定位起点 path.moveTo(100, 100); path.lineTo(200, 200);

moveTo还有一个容易被忽略的用途:在同一条 Path 里画多个不连续的图形。比如你要画两个分开的矩形,不需要建两个 Path,中间moveTo一下就行。这在做图标、做多段折线图的时候很实用,能减少对象创建。

Path path = new Path(); // 第一个矩形 path.moveTo(50, 50); path.lineTo(150, 50); path.lineTo(150, 150); path.lineTo(50, 150); path.close(); // 抬笔,画第二个矩形 path.moveTo(200, 50); path.lineTo(300, 50); path.lineTo(300, 150); path.lineTo(200, 150); path.close();

这里顺带提一下close()。它会把当前点连回该子路径的起点,形成一个闭合图形。画填充色的时候,闭合与否直接影响填充结果——不闭合的路径填充时,系统会隐式补一条线回来,但描边时那条线是不画的。所以画封闭图形,close()该加就加,别省。

2.2 quadTo:一个控制点撑起的二次曲线

quadTo(float x1, float y1, float x2, float y2)画的是二次贝塞尔曲线。参数里(x1, y1)是控制点,(x2, y2)是终点,起点就是当前点。曲线从起点出发,被控制点“拽”向它所在的方向,但不会真正经过控制点,最后落到终点。

理解二次贝塞尔的关键在于那个控制点。你可以把它想象成一根橡皮筋:起点和终点是钉在墙上的两颗钉子,控制点是你的手指勾住橡皮筋中间往外拉的位置。手指拉得越远,曲线鼓得越厉害;手指位置偏左,曲线就往左弯。

数学上,二次贝塞尔的点坐标是这么算的(t 从 0 到 1):

B(t) = (1-t)² · P0 + 2(1-t)t · P1 + t² · P2

其中 P0 是起点,P1 是控制点,P2 是终点。这个公式不用背,但知道它存在有好处:当你想精确控制曲线在某个 t 值处的弯曲程度时,可以反推控制点位置。实际开发中更常用的是经验法——控制点大致放在起点和终点连线的中垂线方向上,距离取弦长的 0.5 到 1 倍之间,形状比较自然。

Path path = new Path(); path.moveTo(100, 300); // 起点 path.quadTo(250, 100, 400, 300); // 控制点(250,100),终点(400,300)

这段代码画出来是一条开口向下的拱形曲线,顶点大概在(250, 200)附近。注意顶点不在控制点上,控制点在(250, 100),曲线最高只到y=200左右。这个“控制点不等于曲线经过点”的认知差,是新手画曲线时最常见的困惑来源。

2.3 cubicTo:两个控制点带来的自由度

cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)是三次贝塞尔曲线,两个控制点(x1,y1)和(x2,y2),终点(x3,y3)。相比二次曲线,它多了一个控制点,意味着曲线两端可以各自拥有独立的切线方向。

这个差别在实际画图时很关键。二次贝塞尔曲线在起点和终点处的切线方向,分别由“起点到控制点”和“控制点到终点”决定,两端是耦合的——你调控制点位置,两端的弯曲会同时变化。三次贝塞尔则解耦了:第一个控制点管起点方向的切线,第二个控制点管终点方向的切线,互不干扰。

Path path = new Path(); path.moveTo(100, 400); // 控制点1(150,100) 控制点2(350,700) 终点(400,400) path.cubicTo(150, 100, 350, 700, 400, 400);

这段代码画出来是一条 S 形曲线:从(100,400)出发先向上翘,中途拐下来,最后从下方进入(400,400)。两个控制点一上一下,曲线就呈现出这种波浪感。如果换成quadTo,无论控制点放哪儿,都画不出这种两端切线方向相反的 S 形。

三次贝塞尔的公式是:

B(t) = (1-t)³ · P0 + 3(1-t)²t · P1 + 3(1-t)t² · P2 + t³ · P3

同样不用背,但要知道它的一个实用性质:当两个控制点分别位于起点和终点附近、且方向与期望的切线一致时,曲线最平滑。做路径动画、画平滑折线图的时候,这个性质就是控制点取值的依据。

2.4 arcTo:椭圆弧的两种接入方式

arcTo有两个重载,这是最容易出问题的地方,必须分开说。

第一个重载:

arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)

它在一个由left, top, right, bottom确定的椭圆(内切于这个矩形)上,从startAngle开始,扫过sweepAngle度,把这段弧追加到路径里。角度单位是度,0 度指向三点钟方向,顺时针为正。

第二个重载:

arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)

参数含义一样,只是椭圆用RectF表达。

关键就在最后那个forceMoveTo布尔值。它的行为差异如下表:

forceMoveTo行为
true弧的起点作为新的当前点,弧之前不连线,相当于先moveTo到弧起点再画弧
false如果路径已有当前点,会先从当前点画一条直线到弧起点,再画弧

这个差异在实际效果上非常明显。假设你先moveTo(100,100),然后arcTo一个从别处开始的弧:

  • forceMoveTo = true:路径里只有那段弧,(100,100)到弧起点之间没有线。
  • forceMoveTo = false:路径里多出一条从(100,100)到弧起点的直线,然后才是弧。

很多“弧线旁边莫名多一条斜线”的 bug,根源就是这里传了false却没意识到会连线。我的习惯是:除非明确需要那条连接线(比如画圆角矩形的连续轮廓),否则一律传true。

Path path = new Path(); RectF oval = new RectF(100, 100, 300, 300); // 从 0 度开始,扫 90 度,不连线 path.arcTo(oval, 0, 90, true);

这段代码画的是椭圆右下四分之一段弧(0 度在右侧中点,顺时针扫 90 度到下方中点)。

2.5 五个方法的能力对照

把五个方法放在一张表里对比,选型时一目了然:

方法片段类型控制点数量是否经过控制点典型场景
moveTo无(定位)0不适用设置起点、分隔子路径
lineTo直线0不适用折线、多边形、矩形边框
quadTo二次曲线1否单峰拱形、简单波浪、聊天气泡尾巴
cubicTo三次曲线2否S 形曲线、平滑折线、路径动画
arcTo椭圆弧0(用角度描述)不适用圆角、扇形、环形进度

选型的基本逻辑是:能用直线就别用曲线,能用二次就别用三次,能用弧就别用贝塞尔拟合。原因很实在——直线计算最省,二次曲线比三次少一个控制点的调参成本,而arcTo画标准圆弧比用贝塞尔逼近更精确,系统内部对弧的渲染也有优化。

3. 组合使用的实操流程与关键细节

3.1 从零构建一条复合路径的完整步骤

光看单个方法不够,真实场景里几乎都是组合使用。我拿一个模拟项目里的“带尾巴的圆角气泡”举例,把完整构建流程走一遍。这个图形包含圆角矩形主体加一个三角尾巴,正好把moveTo、lineTo、arcTo都用上。

第一步,确定整体尺寸和圆角半径。假设气泡宽 300、高 120,圆角半径 20,尾巴在底部中间偏右,宽 40、高 30。

第二步,规划路径走向。从左上角圆角的起点开始,顺时针绕一圈:上边直线、右上圆角、右边直线、右下圆角、底边直线(到尾巴起点)、尾巴两条斜线、底边直线(尾巴之后)、左下圆角、左边直线、左上圆角闭合。

第三步,把每个片段翻译成对应方法。圆角部分用arcTo,直线部分用lineTo,起点用moveTo。

float left = 50, top = 50, right = 350, bottom = 170; float r = 20; float tailStartX = 220, tailEndX = 260, tailTipY = 200; Path path = new Path(); // 左上圆角起点:从左边 r 处开始 path.moveTo(left, top + r); // 左上圆角:从 180 度扫到 270 度 path.arcTo(new RectF(left, top, left + 2*r, top + 2*r), 180, 90, false); // 上边直线 path.lineTo(right - r, top); // 右上圆角:270 到 360 path.arcTo(new RectF(right - 2*r, top, right, top + 2*r), 270, 90, false); // 右边直线 path.lineTo(right, bottom - r); // 右下圆角:0 到 90 path.arcTo(new RectF(right - 2*r, bottom - 2*r, right, bottom), 0, 90, false); // 底边到尾巴起点 path.lineTo(tailEndX, bottom); // 尾巴 path.lineTo((tailStartX + tailEndX) / 2f, tailTipY); path.lineTo(tailStartX, bottom); // 底边继续到左下圆角 path.lineTo(left + r, bottom); // 左下圆角:90 到 180 path.arcTo(new RectF(left, bottom - 2*r, left + 2*r, bottom), 90, 90, false); // 左边直线回到起点 path.lineTo(left, top + r); path.close();

这段代码里arcTo全部传false,因为我们要的是连续轮廓,圆角之间本来就有直线连接,false让弧起点和当前点自然衔接。如果这里传true,每个圆角都会断开,轮廓就碎了。这就是前面说的“什么时候该传 false”的典型场景——画连续闭合轮廓时传 false,画独立弧段时传 true。

3.2 圆角矩形的角度计算过程

上面代码里每个arcTo的起始角度和扫描角度不是随便写的,有明确的推导逻辑。以左上圆角为例:这个圆角的圆心在(left + r, top + r),我们要画的弧是从圆的左侧中点(180 度方向)到上方中点(270 度方向),顺时针扫 90 度。所以startAngle = 180,sweepAngle = 90。

四个圆角的角度对应关系整理如下:

圆角位置圆心起始角度扫描角度
左上(left+r, top+r)18090
右上(right-r, top+r)27090
右下(right-r, bottom-r)090
左下(left+r, bottom-r)9090

这里有个容易算错的地方:arcTo的椭圆矩形参数是外接矩形,不是圆心加半径。左上圆角的外接矩形是(left, top, left+2r, top+2r),宽高都是2r,内切出来的圆半径才是r。我见过有人直接把(left, top, left+r, top+r)传进去,结果圆角只有预期的一半大,排查半天才发现是矩形尺寸给错了。

提示:arcTo的矩形参数是椭圆的外接矩形,宽高是直径而非半径。圆角半径 r 对应的外接矩形宽高都是 2r。

3.3 用 quadTo 和 cubicTo 画平滑折线图

折线图是另一个高频场景。原始数据点直接lineTo连起来是硬邦邦的折线,要做成平滑曲线,就得在相邻点之间插入贝塞尔片段。这里有个经典做法:用相邻两点的中点作为曲线端点,原始数据点作为控制点。

float[] xs = {50, 120, 200, 280, 350}; float[] ys = {300, 150, 250, 100, 200}; Path path = new Path(); path.moveTo(xs[0], ys[0]); for (int i = 1; i < xs.length - 1; i++) { float midX = (xs[i] + xs[i + 1]) / 2f; float midY = (ys[i] + ys[i + 1]) / 2f; path.quadTo(xs[i], ys[i], midX, midY); } // 最后一段连到终点 path.lineTo(xs[xs.length - 1], ys[xs.length - 1]);

这个写法的原理是:每段二次曲线以数据点为控制点、以中点为终点,曲线在数据点附近被“拽”过去,但真正落点在中点,于是相邻两段在中点处自然衔接,整体看起来就是一条穿过数据点附近的平滑曲线。它不保证曲线精确经过每个数据点,但视觉上足够顺,计算量也小。

如果要求曲线必须精确经过每个数据点,那就得用三次贝塞尔配合切线计算,控制点位置由前后点的斜率推导。这套算法在图表库里很常见,核心是根据每个点的前后邻居算出切线方向,然后沿切线方向取 1/3 弦长作为控制点偏移。实现起来代码量翻倍,但曲线质量更高。我的建议是:普通展示型图表用中点法足够,需要精确贴合数据语义的场景再上三次贝塞尔。

3.4 路径方向与填充规则的影响

组合路径时还有一个隐性因素:子路径的绘制方向。Path默认使用FillType.WINDING填充规则,它根据路径环绕方向判断某个区域是否在内部。如果两个子路径方向相同,重叠区域算内部;方向相反,重叠区域可能被挖空。

这个特性在做环形、镂空图形时是利器。比如画一个圆环,外圆顺时针、内圆逆时针,中间就自动镂空,不需要用两个 Path 做裁剪。

Path ring = new Path(); ring.addCircle(200, 200, 100, Path.Direction.CW); // 外圆顺时针 ring.addCircle(200, 200, 60, Path.Direction.CCW); // 内圆逆时针 // 直接填充就是一个圆环

arcTo的扫描角度正负也影响方向:正角度顺时针,负角度逆时针。做镂空效果时,把内圈的sweepAngle取负值,方向就反过来了。这个技巧比用Path.Op做布尔运算轻量,渲染开销更小。

4. 常见问题排查与避坑实录

4.1 弧线旁边多出一条直线

这是arcTo最高频的问题,前面提过,这里给完整的排查路径。现象是:明明只想画一段弧,结果弧的起点和路径之前的某个点之间多了一条直线。

排查顺序:

  1. 检查arcTo最后一个参数是不是false。如果是,且路径已有当前点,系统必然连线。
  2. 如果确实需要false(连续轮廓场景),检查当前点是否就是弧起点。不是的话,先moveTo到弧起点,或者调整前一个片段让当前点落在弧起点上。
  3. 检查弧的起始角度算得对不对。角度错了,弧起点位置就错,连线看起来就像“从奇怪的地方拉过来”。

速查表:

现象可能原因解决
弧前多一条斜线forceMoveTo传了 false 且当前点不在弧起点改传 true,或先 moveTo 到弧起点
弧的形状不对、位置偏移外接矩形参数给成了半径尺寸矩形宽高改为 2r
弧的方向反了sweepAngle正负号错顺时针为正,逆时针为负
弧画不出来起始角度和扫描角度组合超出预期打印角度值核对,0 度在三点钟方向

4.2 贝塞尔曲线形状不受控

quadTo和cubicTo的控制点凭感觉给,是另一个常见痛点。画出来的曲线要么太鼓,要么太扁,反复调参调半天。

我的经验是建立一个参照系:控制点到弦(起点终点连线)的垂直距离,决定了曲线的最大偏移量。二次贝塞尔曲线在 t=0.5 处的偏移量,正好是控制点到弦垂直距离的一半。也就是说,如果你希望曲线在中点处偏离弦 30 像素,控制点就要放在偏离弦 60 像素的位置。

三次贝塞尔稍微复杂,t=0.5 处的偏移量约等于两个控制点偏移量的平均值乘以 0.75。实际调参时,先按“期望偏移量 ÷ 0.75”估算控制点偏移,再微调,比盲试快得多。

还有一个实用技巧:画对称曲线时,两个控制点关于弦的中垂线对称布置。这样曲线在 t=0.5 处达到极值,形状最规整。画不对称曲线(比如 S 形)时,两个控制点分居弦两侧,各自控制一端的弯曲方向。

4.3 路径动画卡顿与重置问题

用Path做动画(比如路径绘制动画、沿路径运动),常见问题是卡顿和状态残留。

卡顿多半是因为在onDraw里反复new Path()或者反复构建路径。Path对象的构建涉及大量浮点运算和内存分配,每帧重建会触发 GC,帧率就掉下来了。正确做法是在构造阶段建好 Path,动画过程中只改PathMeasure的截取比例,或者用reset()复用对象。

// 错误:每帧重建 @Override protected void onDraw(Canvas canvas) { Path p = new Path(); p.moveTo(...); // ... } // 正确:复用成员变量 private final Path path = new Path(); @Override protected void onDraw(Canvas canvas) { // 直接使用已构建好的 path }

状态残留则是reset()和rewind()的区别没搞清楚。reset()清空所有片段和填充类型设置,rewind()只清空片段但保留填充类型等配置。做路径动画循环播放时,用rewind()更合适,省去重新设置FillType的开销。

注意:Path不是线程安全的,别在子线程里构建路径然后丢给主线程绘制,容易出诡异问题。构建和绘制放在同一线程。

4.4 浮点精度导致的接缝

组合大量弧和曲线时,偶尔会看到接缝处有 1 像素左右的缝隙或重叠。这通常是浮点精度累积误差导致的——每个片段的终点坐标经过多次三角函数计算,和下一个片段的起点对不齐。

解决办法有两个:一是尽量用整数或简单小数作为关键坐标,减少计算链长度;二是在接缝处用lineTo显式补一小段,把当前点“拉”到精确位置。后者虽然多一个片段,但能保证视觉上无缝。

// 弧画完后,当前点可能有微小误差 path.arcTo(oval, 0, 90, false); // 显式对齐到精确坐标 path.lineTo(exactX, exactY);

这个技巧在画复杂图标时特别管用,尤其是多个圆角、弧线拼接的图形。多写一行lineTo,省下半小时找缝隙。

4.5 硬件加速下的路径限制

开启硬件加速后,Path的某些操作行为会变化。比如Path包含大量片段时,硬件加速可能把它拆分成多个纹理,导致渲染结果和软件渲染不一致。另外,Path的setFillType在某些旧版本上对复杂路径的支持不完整。

排查这类问题的思路是:先在AndroidManifest里对目标 View 关闭硬件加速,看问题是否消失。如果消失了,说明是硬件加速的兼容性问题,再针对性处理——要么简化路径复杂度,要么对该 View 单独关闭硬件加速。

<View android:layerType="software" ... />

这个属性只对特定 View 生效,不影响全局,是排查路径渲染问题的常用手段。

5. 参数计算与选型的经验沉淀

5.1 控制点位置的快速估算方法

前面讲了控制点偏移量的估算,这里给一个更完整的操作流程,方便直接套用。

画二次曲线时,假设起点 P0、终点 P2,期望曲线在中间偏离弦的最大距离为 d:

  1. 算出弦的中点 M。
  2. 算出弦的单位法向量 n(垂直于 P0P2 方向)。
  3. 控制点 P1 = M + n × (2d)。

这个公式来自二次贝塞尔 t=0.5 处偏移量为控制点偏移量一半的性质。代入后曲线中点正好偏离弦 d,符合预期。

画三次曲线时,假设期望中间偏移 d,两个控制点对称布置:

  1. 弦中点 M,单位法向量 n。
  2. 控制点 P1 = P0 + (P2-P0)/3 + n × (d/0.75)。
  3. 控制点 P2 = P0 + 2(P2-P0)/3 + n × (d/0.75)。

这套估算在实际调参时能省掉大量试错。我一般先用它算出初始值,再根据视觉效果微调 10% 到 20%,基本两三次就能定稿。

5.2 弧与贝塞尔的选型边界

什么时候用arcTo,什么时候用贝塞尔拟合弧?这个问题值得单独说。

arcTo画的是真正的椭圆弧,数学上精确,渲染时系统有专门优化,适合标准圆弧、圆角、扇形、环形进度。缺点是参数用角度描述,做非标准形状时不够灵活。

贝塞尔拟合弧的优势是灵活——你可以用任意控制点画出近似弧形的曲线,方便和其他曲线片段平滑衔接。缺点是拟合有误差,一段 90 度弧用三次贝塞尔拟合,最大误差约 0.027% 半径,视觉上完全看不出来,但数学上不精确。

选型原则:标准圆弧用arcTo,需要和贝塞尔曲线平滑衔接的弧用贝塞尔拟合。比如画一个圆角矩形,四个角是标准圆弧,用arcTo;但如果这个圆角要和一条波浪线平滑连接,连接处的过渡弧用贝塞尔拟合更自然,因为切线方向可以精确控制。

5.3 路径复杂度与性能的平衡

Path的片段数量直接影响渲染性能。一条包含几百个lineTo的路径,在低端设备上绘制时可能掉帧。优化思路有几个层次:

第一层,减少不必要的片段。能用addRect、addRoundRect这类批量方法就别手动lineTo拼,系统内部有优化。

第二层,合并共线片段。连续多个lineTo如果方向一致,合并成一个,减少片段数。

第三层,用Path.Op做布尔运算替代手动拼接。两个图形求并集、交集,用op()方法比手动算轮廓点简单且高效。

第四层,缓存渲染结果。静态路径可以渲染到Bitmap缓存,每帧直接贴图,避免重复构建和光栅化。

实测下来,一个包含 50 个以内片段的路径,性能开销可以忽略;超过 200 个片段,在低端机上就要留意了;超过 500 个,基本要考虑缓存方案。这个数字不是绝对的,和路径的填充面积、是否描边、画笔复杂度都有关,但可以作为粗略的警戒线。

5.4 调试路径的实用手段

路径画得不对,光看代码很难定位。几个实用的调试手段:

一是用Paint的STROKE模式把路径描出来,同时用不同颜色标记起点和终点。起点画个小圆,终点画个叉,一眼就能看出路径走向对不对。

二是分段绘制。把复合路径拆成单个片段,每个片段用不同颜色画,看哪一段出了问题。这个方法在排查弧线角度错误时特别有效。

三是用PathMeasure打印路径长度和关键点坐标。PathMeasure能沿着路径采样任意位置的坐标和切线,把采样点打印出来,和预期值对比,误差一目了然。

PathMeasure pm = new PathMeasure(path, false); float len = pm.getLength(); float[] pos = new float[2]; for (float d = 0; d <= len; d += len / 10) { pm.getPosTan(d, pos, null); // 打印 pos[0], pos[1] 核对 }

这套组合拳下来,绝大多数路径问题都能快速定位。我个人的习惯是,只要路径超过三个片段,就先描边看走向,确认无误再上填充和动画,能省掉大量返工。

6. 写在最后的几句实操体会

这五个方法单独看都不复杂,难的是组合时的衔接和参数控制。我自己的经验是,先把每个方法的“当前点”变化规律在纸上画一遍,搞清楚每次调用后画笔停在哪里,再动手写代码,比边写边试快得多。尤其是arcTo的forceMoveTo参数,理解它和当前点的关系,能避开一大半的连线 bug。

另一个体会是,贝塞尔曲线的控制点不要凭感觉给。先用估算公式算出初始值,再微调,效率比盲试高一个数量级。曲线画多了会有手感,但手感建立在理解了参数和形状的对应关系之上,否则只是碰运气。

最后分享一个小习惯:每写完一段路径代码,立刻用描边模式跑一遍,确认走向和接缝,再继续往下写。路径问题越早发现越好改,等整个图形拼完再回头找,成本翻倍。这个习惯帮我省下的调试时间,比任何技巧都多。

返回列表