Custom Widgets
A custom widget is a Widget subclass that combines stock widgets (or a canvas) into a reusable unit with its own properties and events - the "icon button," the "recipe card," the "stat row" that appears across an app. It is the composition tool of the toolkit: the layout chapter arranged stock widgets, the properties chapter gave state its reactive form, and the custom widget is where the two meet into a named, reusable component. This chapter builds the three canonical shapes - a composite widget (children in the rule), a canvas widget (the drawing is the content), and a widget with custom events - and gives the naming and property conventions that keep a component library clean.
Composite: Children Defined in the Rule
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import StringProperty, NumericProperty
class StatRow(BoxLayout):
"""A label + value row: the unit that repeats across a stats screen."""
title = StringProperty("")
value = StringProperty("0")
# expose the inner widgets so the app can reach them if needed
value_label = ObjectProperty(None)
def __init__(self, **kw):
super().__init__(**kw)
self.orientation = "horizontal"
self.spacing = 8
self.padding = 6
self.add_widget(Label(text="title", size_hint_x=0.5))
self.value_label = Label(text="0", size_hint_x=0.5, halign="right")
self.add_widget(self.value_label)
# the properties drive the children - the composite is reactive
self.bind(title=lambda i, v: setattr(self.children[1], "text", v))
self.bind(value=self._set_value)
def _set_value(self, *args):
self.value_label.text = args[1]
The composite builds its children in __init__ (or in the KV rule, the declarative alternative) and exposes a small property surface - title, value - that the parent sets, with the bindings inside the widget wiring the properties to the inner widgets. The parent sees StatRow(title="Steps", value="12") and does not know or care that it is two labels; the internals are the component's business. This is the composition-over-inheritance principle from the OOP tutorial applied to UI: the StatRow has labels, it does not inherit label behavior. The KV alternative (a <StatRow>: rule with the children and title:<< bindings) is the same structure declared instead of constructed - use the rule when the children are static, __init__ when they are built from data.
Canvas Widget: The Drawing Is the Content
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse
from kivy.properties import ColorProperty, NumericProperty
class Avatar(Widget):
"""A colored circle with an initial - the profile avatar."""
initial = StringProperty("?")
color = ColorProperty([0.3, 0.5, 0.8, 1])
size_hint = (None, None)
size = (80, 80)
def on_size(self, *args):
self._redraw()
def _redraw(self):
self.canvas.clear()
with self.canvas:
Color(*self.color)
r = min(self.width, self.height)
Ellipse(pos=(self.x, self.y), size=(r, r))
# the initial, centered
from kivy.graphics import Text
run = Text(text=self.initial, font_size=r * 0.4,
color=[1, 1, 1, 1])
run.center = self.center
The canvas widget's content is the drawing itself: an Ellipse for the circle, a Text instruction for the initial, all bound to the widget's properties (color, initial, the size). The on_size redraw keeps it correct across layout reflows. This shape covers the "the component is a picture" cases - avatars, badges, charts, custom indicators - where there are no stock children to compose and the canvas is the whole UI. The convention is the same: a small property surface (what the parent sets), a redraw that is a pure function of those properties, and the drawing rebuilt when the inputs change. The size_hint: (None, None) plus explicit size gives it a fixed footprint in a layout, which is the normal case for an icon-sized component.
Custom Events: Extending the Dispatcher
from kivy.uix.widget import Widget
from kivy.event import EventDispatcher
class SearchField(Widget, EventDispatcher):
"""A field that emits on_query_submitted when the user commits."""
__events__ = ("on_query_submitted",)
def submit(self, text):
# the public method that fires the event
self.dispatch("on_query_submitted", text)
# the consumer binds like any Kivy event:
# field.bind(on_query_submitted=handler)
# where handler(instance, query) is the signature
When a component's interaction is richer than a single on_press - a search field that "submits a query," a picker that "selects an item" - the clean interface is a custom event. Mixing in EventDispatcher and declaring __events__ gives the widget named events with dispatch/bind, so the consumer writes field.bind(on_query_submitted=handler) - the same vocabulary as every built-in event. The component fires the event (self.dispatch("on_query_submitted", text)) at the moment the interaction completes, and the consumer is decoupled from the component's internals. This is the EventDispatcher layer from the event chapter used at the component level: the component says "here is the signal," and the app decides what it means. Reserve it for real interaction boundaries; a plain method callback is enough for the simple cases.
Component Conventions
- CapWords names for components (
StatRow,Avatar), matching the widget classes they extend - the reader sees the type immediately. - A small property surface. Expose the inputs the parent sets (
title,value,color); keep the inner widgets and the canvas as implementation detail. - Bind inside, not outside. The property-to-child wiring lives in the component's
__init__/__init__bindings; the parent sets properties and does not reach into the children. - Events for interaction boundaries. A component that completes an action emits a custom event (or uses the standard
on_presswhen it is a button); the consumer binds. - One component, one job. A
StatRowshows a stat; it does not also navigate, fetch, or style the screen. The composition of components is the screen's job.
Summary
A custom widget is a Widget subclass with three canonical shapes: the composite (stock children built in __init__ or a KV rule, exposed through a small property surface with the bindings inside), the canvas widget (the drawing is the content, a pure function of its properties, redrawn on on_size), and the event-emitter (an EventDispatcher mixin with __events__ for interaction boundaries the consumer binds to). The conventions are the composition discipline from the OOP tutorial applied to UI: CapWords names, a minimal public property surface, internal bindings, events for the signals that cross the component boundary, and one job per component. Master these and a Kivy app stops being a pile of stock widgets and becomes a small library of named, reusable components that the screens compose - which is the maintainable shape.