Bird
Raised Fist0
Fluttermobile~20 mins

Custom painters (CustomPaint) in Flutter - Mini App: Build & Ship

Choose your learning style10 modes available
Build: Custom Paint Circle Screen
This screen shows a custom painted circle in the center using CustomPaint widget.
Target UI
┌───────────────────────────────┐
│           AppBar              │
│  Custom Painted Circle Below  │
│                               │
│           (  ●  )             │
│                               │
│                               │
└───────────────────────────────┘
Use CustomPaint widget to draw a blue circle.
Circle should be centered and have radius 50.
Use a CustomPainter subclass to draw the circle.
Screen should have an AppBar with title 'Custom Painter Demo'.
Starter Code
Flutter
import 'package:flutter/material.dart';

class CustomPaintCircleScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Custom Painter Demo'),
      ),
      body: Center(
        child: CustomPaint(
          size: Size(100, 100),
          painter: CirclePainter(), // TODO: Implement CirclePainter
        ),
      ),
    );
  }
}

class CirclePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // TODO: Draw a blue circle with radius 50 at center
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}
Task 1
Task 2
Task 3
Solution
Flutter
import 'package:flutter/material.dart';

class CustomPaintCircleScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Custom Painter Demo'),
      ),
      body: Center(
        child: CustomPaint(
          size: Size(100, 100),
          painter: CirclePainter(),
        ),
      ),
    );
  }
}

class CirclePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;
    final center = Offset(size.width / 2, size.height / 2);
    canvas.drawCircle(center, 50, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

We created a CirclePainter class extending CustomPainter. Inside the paint method, we made a Paint object with blue color and fill style. Then we calculated the center of the canvas using the size parameter. Finally, we drew a circle with radius 50 at the center using canvas.drawCircle. The shouldRepaint returns false because the painting does not change dynamically.

The CustomPaint widget uses this painter and sets its size to 100x100, so the circle fits nicely in the center of the screen.

Final Result
Completed Screen
┌───────────────────────────────┐
│           AppBar              │
│  Custom Painted Circle Below  │
│                               │
│           (  ●  )             │
│                               │
│                               │
└───────────────────────────────┘
User sees a blue circle centered on the screen below the AppBar.
No interactive elements on this screen.
Stretch Goal
Add a slider below the circle to change the circle's radius dynamically.
💡 Hint
Use a StatefulWidget and update the radius in setState when slider value changes, then call setState to repaint.

Practice

(1/5)
1. What is the main purpose of a CustomPainter in Flutter?
easy
A. To draw custom graphics directly on the screen
B. To manage app navigation between screens
C. To handle user input events like taps and swipes
D. To create standard UI buttons and text fields

Solution

  1. Step 1: Understand CustomPainter role

    A CustomPainter is used to draw graphics directly on the canvas in Flutter apps.
  2. Step 2: Compare with other options

    Options A, B, and C relate to standard UI, navigation, and input events, not drawing.
  3. Final Answer:

    To draw custom graphics directly on the screen -> Option A
  4. Quick Check:

    CustomPainter = Draw graphics [OK]
Hint: CustomPainter is for drawing, not UI controls or navigation [OK]
Common Mistakes:
  • Confusing CustomPainter with widget layout
  • Thinking it handles user input
  • Assuming it manages navigation
2. Which method must you override when creating a CustomPainter subclass to draw on the canvas?
easy
A. paint
B. build
C. createState
D. initState

Solution

  1. Step 1: Identify CustomPainter methods

    The paint method is where drawing commands are written.
  2. Step 2: Exclude unrelated methods

    build, createState, and initState belong to widgets, not painters.
  3. Final Answer:

    paint -> Option A
  4. Quick Check:

    Draw in paint() method [OK]
Hint: paint() draws; build() is for widgets [OK]
Common Mistakes:
  • Using build() instead of paint()
  • Confusing widget lifecycle methods with painter methods
  • Forgetting to override paint()
3. What will be the visible result of this Flutter code snippet?
class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.red;
    canvas.drawCircle(Offset(size.width / 2, size.height / 2), 50, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

Widget build(BuildContext context) {
  return CustomPaint(
    size: Size(200, 200),
    painter: MyPainter(),
  );
}
medium
A. A blue square filling the 200x200 area
B. A red circle centered in a 200x200 area
C. Nothing is drawn because shouldRepaint returns false
D. A red circle at the top-left corner

Solution

  1. Step 1: Analyze paint method

    The paint method draws a red circle at the center (width/2, height/2) with radius 50.
  2. Step 2: Understand shouldRepaint effect

    shouldRepaint returning false means no redraw needed, but initial paint still happens.
  3. Final Answer:

    A red circle centered in a 200x200 area -> Option B
  4. Quick Check:

    Draw circle center = red circle visible [OK]
Hint: shouldRepaint false still draws initially [OK]
Common Mistakes:
  • Thinking shouldRepaint false means no drawing
  • Assuming circle is at top-left
  • Confusing circle with square shape
4. Identify the error in this CustomPainter subclass:
class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.green;
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true
  }
}
medium
A. Canvas cannot draw rectangles
B. paint method should return a value
C. Missing semicolon after return true
D. shouldRepaint must return false

Solution

  1. Step 1: Check syntax in shouldRepaint

    The return statement lacks a semicolon after true, causing a syntax error.
  2. Step 2: Verify other parts

    paint method correctly draws a rectangle; canvas supports rectangles; shouldRepaint can return true.
  3. Final Answer:

    Missing semicolon after return true -> Option C
  4. Quick Check:

    Syntax error = missing semicolon [OK]
Hint: Check semicolons after return statements [OK]
Common Mistakes:
  • Forgetting semicolon after return
  • Thinking paint must return a value
  • Assuming canvas can't draw rectangles
5. You want to create a CustomPainter that draws a circle only when the color changes. Which is the best way to implement shouldRepaint to optimize performance?
class CirclePainter extends CustomPainter {
  final Color color;
  CirclePainter(this.color);

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = color;
    canvas.drawCircle(Offset(size.width/2, size.height/2), 40, paint);
  }

  @override
  bool shouldRepaint(covariant CirclePainter oldDelegate) {
    // What to write here?
  }
}
hard
A. return oldDelegate.color == color;
B. return true;
C. return false;
D. return oldDelegate.color != color;

Solution

  1. Step 1: Understand shouldRepaint purpose

    shouldRepaint returns true if the painter needs to redraw due to changes.
  2. Step 2: Compare colors to decide repaint

    Repaint only if the new color differs from the old one, so compare oldDelegate.color and color.
  3. Step 3: Evaluate options

    return oldDelegate.color != color; returns true when colors differ, optimizing redraw. return true; always repaints (less efficient). return false; never repaints (ignores changes). return oldDelegate.color == color; repaints when colors are same (wrong logic).
  4. Final Answer:

    return oldDelegate.color != color; -> Option D
  5. Quick Check:

    Repaint if color changed = oldDelegate.color != color [OK]
Hint: Repaint only if properties differ for efficiency [OK]
Common Mistakes:
  • Always returning true wastes performance
  • Always returning false ignores changes
  • Using equality check wrongly in shouldRepaint
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard