Customizing Components in Flutter
When the existing components provided by Flutter cannot meet our needs, or when we need to encapsulate some common components for code sharing, we need to create custom components. In Flutter, there are three ways to customize components: through composition of other components, custom painting, and implementing RenderObject
. This section will introduce the characteristics of each method, with more details provided in subsequent chapters.
69.1 Composing Multiple Widgets
This method involves assembling multiple components to create a new one. For example, the Container
widget we introduced earlier is a composite component made up of DecoratedBox
, ConstrainedBox
, Transform
, Padding
, Align
, and other components.
In Flutter, the concept of composition is very important. Flutter provides many foundational components, and our UI development essentially consists of combining these components as needed to achieve various layouts.
69.2 Custom Painting with CustomPaint
If we encounter a situation where existing components cannot achieve the required UI, we can use custom painting. For instance, if we need a circular progress bar with a color gradient, we can't use the CircularProgressIndicator
because it does not support applying a gradient to the progress bar when displaying precise progress (its valueColor
property only changes the indicator's color during the rotation animation). In this case, the best approach is to create a custom component to draw the desired appearance using CustomPaint
and Canvas
provided by Flutter.
69.3 Custom Painting with RenderObject
Components in Flutter that have a UI appearance, such as Text
and Image
, are rendered by corresponding RenderObject
classes (which we will discuss in detail in the "Core Principles of Flutter" chapter). For example, Text
is rendered by RenderParagraph
, while Image
is rendered by RenderImage
. RenderObject
is an abstract class that defines an abstract method called paint(...)
:
void paint(PaintingContext context, Offset offset)
PaintingContext
represents the drawing context of the component, and you can obtain a Canvas
through PaintingContext.canvas
. The drawing logic is primarily implemented using the Canvas API. Subclasses need to override this method to implement their own drawing logic, such as how RenderParagraph
implements text drawing and RenderImage
implements image drawing.
It's important to note that ultimately, RenderObject
also uses the Canvas API for drawing. So what is the difference between implementing RenderObject
and custom painting with CustomPaint
and Canvas
? The answer is simple: CustomPaint
is a convenience proxy class for developers. It directly inherits from SingleChildRenderObjectWidget
and connects the Canvas
and Painter
(which developers need to implement, as discussed in later chapters) through the paint
method of RenderCustomPaint
to achieve the final drawing.
69.4 Summary
“Composition” is the simplest method for customizing components, and in any scenario requiring a custom component, we should first consider whether it can be achieved through composition. The methods of custom painting using CustomPaint
and RenderObject
are fundamentally the same, as both require developers to manually call the Canvas API to draw the UI. The advantages are powerful flexibility, allowing for the implementation of any UI appearance; the downside is that developers must understand the details of the Canvas API and implement the drawing logic themselves. In the following sections of this chapter, we will provide detailed examples of custom UI methods.