Background of CustomPaint Widget

The RenderObjectWidget we learned earlier is powerful but complex. Even when you simply want to draw something, you need to inherit from RenderObject, implement createRenderObject, and override the paint method.

CustomPaint solves this complexity. Just pass the paint behavior as a first-class function and you’re done!

Why CustomPaint?

Avoid the complexity of RenderObjectWidget and focus only on drawing

❌ RenderObject 방식

1. RenderObjectWidget 상속
2. createRenderObject() 구현
3. RenderBox 클래스 작성
4. paint() 메서드 오버라이드
5. 레이아웃 로직 구현
총 50+ 줄의 보일러플레이트 코드! 😫

✅ CustomPaint 방식

CustomPaint({
painter: (canvas, size) => {
// 그리기 코드
}
})
단 5줄로 끝! 🎉

💡 핵심 아이디어

CustomPaint는 내부적으로 RenderObject를 생성하고 관리합니다. 개발자는 그리기 로직만 함수로 전달하면 되므로, 복잡한 클래스 구조를 이해할 필요가 없습니다.

Advantages of CustomPaint

  1. Simple API: Just pass the painter function
  2. Fast Prototyping: No complex class structure needed
  3. Reusability: Easy to replace painter functions
  4. Performance: Uses internally optimized RenderObject

Using CustomPaint

Basic CustomPaint Usage

Implement custom drawing by passing a function to the painter property

Canvas API 사용법

canvas: {
  paint: (context, size) => {
    const ctx = context.canvas;
    
    // 색상 설정
    ctx.fillStyle = '#3B82F6';
    ctx.strokeStyle = '#FF0000';
    
    // 선 설정
    ctx.lineWidth = 2;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'miter';
    
    // 그리기
    ctx.fillRect(x, y, w, h);
    ctx.beginPath();
    ctx.arc(x, y, r, 0, Math.PI * 2);
    ctx.fill();
  }
}

최적화 팁

// shouldRepaint 사용
CustomPaint({
  painter: {
    dependencies: myData,
    shouldRepaint: (oldPainter) => {
      // 데이터가 변경되었을 때만 다시 그림
      return oldPainter.dependencies !== myData;
    },
    canvas: {
      paint: (context, size) => {
        // 그리기 로직
      }
    }
  }
})

Basic Usage

// For SVG renderer
CustomPaint({
  size: new Size(300, 200),
  painter: {
    svg: {
      createDefaultSvgEl(context) {
        return {
          circle: context.createSvgEl('circle')
        };
      },
      paint(els, size) {
        els.circle.setAttribute('cx', `${size.width / 2}`);
        els.circle.setAttribute('cy', `${size.height / 2}`);
        els.circle.setAttribute('r', '50');
        els.circle.setAttribute('fill', '#3B82F6');
      }
    }
  }
})

// For Canvas renderer
CustomPaint({
  size: new Size(300, 200),
  painter: {
    canvas: {
      paint(context, size) {
        const ctx = context.canvas;
        ctx.fillStyle = '#3B82F6';
        ctx.beginPath();
        ctx.arc(size.width / 2, size.height / 2, 50, 0, Math.PI * 2);
        ctx.fill();
      }
    }
  }
})

shouldRepaint Optimization

You can control when to repaint for performance:

CustomPaint({
  painter: {
    dependencies: myData, // Dependency data
    shouldRepaint: (oldPainter) => {
      // Return true to repaint
      // Return false to keep previous painting
      return oldPainter.dependencies !== myData;
    },
    svg: {
      // SVG implementation
    },
    canvas: {
      // Canvas implementation
    }
  }
})

SVG vs Canvas Rendering

Flitter’s CustomPaint supports two rendering methods:

SVG Rendering

  • Maintains quality when scaling
  • Suitable for vector graphics
  • Create SVG elements with createDefaultSvgEl()
  • Set attributes with setAttribute in paint()
svg: {
  createDefaultSvgEl(context) {
    return {
      line: context.createSvgEl('path'),
      point: context.createSvgEl('circle')
    };
  },
  paint(els, size) {
    els.line.setAttribute('d', pathData);
    els.line.setAttribute('stroke', '#3B82F6');
    els.point.setAttribute('cx', '50');
    els.point.setAttribute('cy', '50');
  }
}

Canvas Rendering

  • Suitable for complex pixel manipulation
  • Advantageous for high-performance animations
  • Same approach as web Canvas API
canvas: {
  paint(context, size) {
    const ctx = context.canvas;
    ctx.fillStyle = '#3B82F6';
    ctx.fillRect(0, 0, size.width, size.height);
    ctx.strokeStyle = '#FF0000';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(100, 100);
    ctx.stroke();
  }
}

Source Code Location

To examine the implementation of CustomPaint:

  • packages/flitter/src/component/CustomPaint.ts: CustomPaint 위젯
  • packages/flitter/src/engine/canvas/: Canvas 구현
  • packages/flitter/src/engine/paint.ts: Paint 클래스

Key Summary

  1. CustomPaint hides the complexity of RenderObject
  2. Provides separate svg/canvas implementations in the painter object
  3. Performance can be optimized with dependencies and shouldRepaint
  4. Supports both SVG and Canvas rendering methods
  5. You only need to provide implementation for the renderer you use
  6. Can be easily combined with animations

In the next chapter, we will examine practical examples that utilize everything we have learned so far.