Introduction
Kivy is a powerful open‑source framework for building graphical user interfaces and cross‑platform applications with Python. It supports Android, iOS, Windows, Linux and macOS, making it an excellent choice for developing mobile and desktop apps from a single codebase.
The library offers tools for creating adaptive interfaces, handling keyboard, mouse, sensor and multitouch input, and includes its own KV markup language to separate logic from UI. Kivy leverages OpenGL for rendering, providing high performance and smooth animations.
What Is Kivy and Its Features
Key Advantages of the Framework
Kivy stands out among other GUI libraries thanks to several important characteristics. The framework is written entirely in Python with Cython‑accelerated core components, ensuring strong performance.
Core features include:
- Native multitouch and gesture support
- Hardware acceleration via OpenGL ES 2.0
- Automatic UI scaling for different screen resolutions
- Modular architecture with extensibility
- Built‑in animation and effect capabilities
Framework Architecture
Kivy is built on a modular architecture where each component can be replaced or extended. The core consists of several essential modules: Clock for time management, Window for window handling, Factory for object creation, and Cache for performance optimization.
Installation and Setup
Basic Kivy Installation
pip install kivy
For Android development you will also need the buildozer utility:
pip install buildozer
Platform‑Specific Dependency Installation
Windows
pip install kivy[base,media]
Linux (Ubuntu/Debian)
sudo apt-get install python3-kivy
macOS
brew install pkg-config sdl2 sdl2_image sdl2_ttf sdl2_mixer gstreamer
pip install kivy
Verify Installation
After installing, confirm everything works with a simple test:
import kivy
print(kivy.__version__)
Creating Your First Application
Minimal Kivy App
from kivy.app import App
from kivy.uix.button import Button
class MyApp(App):
def build(self):
return Button(text="Press Me")
MyApp().run()
Running this code opens a window with a button that fills the entire area.
Kivy App Structure
Every Kivy application follows this basic structure:
- App — the main class that launches and manages the app
- Widget — the base UI element from which all components inherit
- Layout — containers that organize widgets (BoxLayout, GridLayout, etc.)
- Screen — separate screens for navigation
Application Lifecycle
A Kivy app goes through several stages:
- App class initialization
- Calling
build()to create the UI - Starting the main event loop
- Processing user input
- Graceful shutdown when the window closes
Core Components and Widgets
Basic UI Widgets
Label — displaying text
from kivy.uix.label import Label
label = Label(text='Hello, world!',
font_size='20sp',
color=[1, 0, 0, 1]) # red
Button — interactive button
from kivy.uix.button import Button
button = Button(text='Press Me',
size_hint=(0.5, 0.3),
pos_hint={'center_x': 0.5, 'center_y': 0.5})
TextInput — text entry field
from kivy.uix.textinput import TextInput
text_input = TextInput(text='Enter text',
multiline=False,
font_size=16)
Containers and Layouts
BoxLayout — linear arrangement
from kivy.uix.boxlayout import BoxLayout
layout = BoxLayout(orientation='vertical', spacing=10, padding=20)
layout.add_widget(Label(text='First item'))
layout.add_widget(Button(text='Button'))
GridLayout — tabular arrangement
from kivy.uix.gridlayout import GridLayout
grid = GridLayout(cols=2, rows=2, spacing=[10, 10])
for i in range(4):
grid.add_widget(Button(text=f'Button {i+1}'))
FloatLayout — free positioning
from kivy.uix.floatlayout import FloatLayout
float_layout = FloatLayout()
btn1 = Button(text='Top‑Left',
size_hint=(0.3, 0.2),
pos_hint={'x': 0, 'top': 1})
float_layout.add_widget(btn1)
KV Markup Language
Building UI with KV
The KV language separates UI description from application logic, improving readability and maintainability.
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class MainWidget(BoxLayout):
def button_pressed(self):
print("Button was pressed!")
class MyKvApp(App):
def build(self):
return MainWidget()
MyKvApp().run()
mykv.kv
<MainWidget>:
orientation: 'vertical'
padding: 20
spacing: 10
Label:
text: 'Welcome to Kivy!'
font_size: '24sp'
size_hint_y: 0.3
Button:
text: 'Press Me'
size_hint_y: 0.2
on_press: root.button_pressed()
TextInput:
hint_text: 'Enter a message'
size_hint_y: 0.2
KV Syntax and Capabilities
Property binding
<MyWidget>:
Button:
text: 'Counter: ' + str(root.counter)
on_press: root.counter += 1
Using IDs
<MyWidget>:
TextInput:
id: text_input
Button:
text: 'Clear'
on_press: text_input.text = ''
Event Handling and Interaction
Kivy Event System
Kivy uses a robust event system based on properties and signals. Every widget can emit events and react to them.
Processing User Input
Mouse and Touch Events
class TouchWidget(Widget):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print(f"Touch at: {touch.pos}")
return True
return super().on_touch_down(touch)
def on_touch_move(self, touch):
if self.collide_point(*touch.pos):
print(f"Move at: {touch.pos}")
Keyboard Events
from kivy.core.window import Window
class KeyboardWidget(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._keyboard = Window.request_keyboard(
self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_keyboard_down)
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
print(f'Key pressed: {keycode[1]}')
Binding Events to Widgets
Programmatic binding
button = Button(text='Click')
button.bind(on_press=self.on_button_click)
def on_button_click(self, instance):
print(f"Button clicked: {instance.text}")
KV‑based binding
Button:
text: 'Button'
on_press: app.handle_button_press(self)
on_release: app.handle_button_release(self)
Graphics and Canvas
Drawing on the Canvas
The Canvas is a low‑level drawing API. Each widget has three canvases: before (drawn before content), canvas (main), and after (drawn after content).
from kivy.graphics import Color, Rectangle, Line, Ellipse
class PaintWidget(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas:
Color(1, 0, 0, 1) # red
self.rect = Rectangle(pos=(50, 50), size=(100, 100))
Color(0, 1, 0, 1) # green
Line(points=[10, 10, 200, 100, 300, 200], width=2)
Animations and Transitions
Simple animation
from kivy.animation import Animation
# Move animation
anim = Animation(x=200, y=200, duration=2)
anim.start(widget)
# Sequential animation
anim1 = Animation(x=100, duration=1)
anim2 = Animation(y=100, duration=1)
anim_sequence = anim1 + anim2
anim_sequence.start(widget)
Complex transitions
from kivy.animation import Animation
from kivy.uix.widget import Widget
class AnimatedWidget(Widget):
def animate_rotation(self):
# Spring‑like rotation
anim = Animation(rotation=360, duration=2, t='out_bounce')
anim.bind(on_complete=self.reset_rotation)
anim.start(self)
def reset_rotation(self, animation, widget):
widget.rotation = 0
Multimedia and Resources
Working with Images
from kivy.uix.image import Image
# Load from file
img = Image(source='path/to/image.jpg')
# Image with custom settings
img = Image(source='logo.png',
size_hint=(0.5, 0.5),
pos_hint={'center_x': 0.5, 'center_y': 0.5},
allow_stretch=True,
keep_ratio=True)
Playing Audio
from kivy.core.audio import SoundLoader
class AudioWidget(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sound = SoundLoader.load('sound.wav')
def play_sound(self):
if self.sound:
self.sound.play()
Working with Video
from kivy.uix.videoplayer import VideoPlayer
video = VideoPlayer(source='movie.mp4',
state='play',
options={'allow_stretch': True})
Screen Navigation
ScreenManager for Managing Screens
from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class GameScreen(Screen):
pass
class MyScreenApp(App):
def build(self):
sm = ScreenManager(transition=SlideTransition())
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
sm.add_widget(GameScreen(name='game'))
return sm
Screen Transitions in KV
<MenuScreen>:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Play'
on_press: root.manager.current = 'game'
Button:
text: 'Settings'
on_press: root.manager.current = 'settings'
<SettingsScreen>:
BoxLayout:
orientation: 'vertical'
Label:
text: 'Settings Screen'
Button:
text: 'Back'
on_press: root.manager.current = 'menu'
Responsive Design and Adaptive Layouts
Responsive Design Principles in Kivy
Kivy provides several mechanisms for building adaptive interfaces:
Using size_hint
# Widget occupies 50% width and 30% height of its parent
widget = Button(text='Responsive Button',
size_hint=(0.5, 0.3))
# Fixed width, adaptive height
widget = Button(text='Semi‑Fixed',
size_hint=(None, 0.3),
width=200)
Responsive layouts in KV
<ResponsiveLayout>:
cols: 2 if root.width > root.height else 1
Button:
text: 'Button 1'
Button:
text: 'Button 2'
Handling Different Screen Resolutions
from kivy.metrics import dp, sp
from kivy.core.window import Window
class ResponsiveWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Window.bind(size=self.on_window_resize)
self.update_layout()
def on_window_resize(self, window, width, height):
self.update_layout()
def update_layout(self):
if Window.width < dp(600):
self.orientation = 'vertical'
else:
self.orientation = 'horizontal'
Building Mobile Applications
Configuring Buildozer for Android
Project initialization
buildozer init
Key settings in buildozer.spec
[app]
title = My Kivy App
package.name = mykivyapp
package.domain = org.example
version = 0.1
requirements = python3,kivy,plyer
[buildozer]
log_level = 2
[app:android]
android.permissions = INTERNET,WRITE_EXTERNAL_STORAGE
android.api = 31
android.minapi = 21
android.ndk = 25b
Building the APK
# Debug build
buildozer android debug
# Release build
buildozer android release
# Deploy to a connected device
buildozer android deploy run
Preparing for Google Play Release
Signing the APK
# Create a keystore
keytool -genkey -v -keystore my-release-key.keystore -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias
# Sign the APK
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore my_app-release-unsigned.apk my-alias
Optimizing the Application
# Minify code and resources
from kivy.logger import Logger
Logger.setLevel('WARNING') # Suppress debug messages
# Image compression
# Use WebP instead of PNG
# Remove unused assets
Testing and Debugging
Kivy Debug Tools
Built‑in inspector
from kivy.modules import inspector
from kivy.config import Config
Config.set('modules', 'inspector', '')
Logging
from kivy.logger import Logger
Logger.info('Application: App started')
Logger.warning('Application: Warning message')
Logger.error('Application: Error encountered')
UI Unit Testing
import unittest
from kivy.tests.common import GraphicUnitTest
class ButtonTest(GraphicUnitTest):
def test_button_press(self):
from kivy.uix.button import Button
button = Button(text='Test')
self.render(button)
# Simulate press
button.dispatch('on_press')
# Verify result
self.assertIsNotNone(button.last_touch)
Database and API Integration
SQLite Integration
import sqlite3
from kivy.storage.jsonstore import JsonStore
class DatabaseManager:
def __init__(self, db_path):
self.db_path = db_path
self.init_database()
def init_database(self):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE
)
''')
conn.commit()
conn.close()
def add_user(self, name, email):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('INSERT INTO users (name, email) VALUES (?, ?)',
(name, email))
conn.commit()
conn.close()
Working with REST APIs
import requests
from kivy.network.urlrequest import UrlRequest
class ApiManager:
def __init__(self, base_url):
self.base_url = base_url
def get_data(self, endpoint, callback):
url = f"{self.base_url}/{endpoint}"
UrlRequest(url, on_success=callback, on_error=self.on_error)
def on_error(self, request, error):
print(f"API error: {error}")
Advanced Features
Device Sensor Access
from plyer import accelerometer, gyroscope, compass
class SensorManager:
def __init__(self):
self.accelerometer_enabled = False
def enable_accelerometer(self):
accelerometer.enable()
self.accelerometer_enabled = True
def get_acceleration(self):
if self.accelerometer_enabled:
return accelerometer.acceleration
return None
Camera Integration
from kivy.uix.camera import Camera
import time
class CameraWidget(Camera):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.resolution = (640, 480)
self.play = True
def take_picture(self):
timestamp = time.strftime('%Y%m%d_%H%M%S')
filename = f'IMG_{timestamp}.jpg'
self.export_to_png(filename)
File System Operations
from kivy.utils import platform
from kivy.storage.jsonstore import JsonStore
import os
class FileManager:
def __init__(self):
if platform == 'android':
from android.permissions import request_permissions, Permission
request_permissions([Permission.WRITE_EXTERNAL_STORAGE])
self.app_dir = '/sdcard/MyKivyApp'
else:
self.app_dir = os.path.expanduser('~/MyKivyApp')
os.makedirs(self.app_dir, exist_ok=True)
def save_settings(self, settings):
store = JsonStore(os.path.join(self.app_dir, 'settings.json'))
for key, value in settings.items():
store.put(key, value=value)
Complete Kivy Methods and Functions Reference
| Category | Class / Function | Description | Key Methods |
|---|---|---|---|
| Application | App | Base application class | build(), run(), stop(), get_running_app() |
| Clock | Time management | schedule_once(), schedule_interval(), unschedule() | |
| Config | Application configuration | get(), set(), write() | |
| Widgets | Widget | Base class for all UI elements | add_widget(), remove_widget(), clear_widgets() |
| Label | Text display | text, font_size, color, halign, valign | |
| Button | Interactive button | on_press, on_release, state | |
| TextInput | Text entry field | text, hint_text, password, multiline | |
| Image | Image display | source, texture, reload() | |
| Slider | Value selector | value, min, max, step, on_value | |
| ProgressBar | Progress indicator | value, max | |
| Switch | Toggle switch | active, on_active | |
| CheckBox | Checkbox control | active, group, on_active | |
| Spinner | Dropdown selector | text, values, on_text | |
| Containers | BoxLayout | Linear arrangement | orientation, spacing, padding |
| GridLayout | Tabular arrangement | cols, rows, spacing | |
| FloatLayout | Free positioning | size_hint, pos_hint | |
| RelativeLayout | Relative positioning | to_parent, to_widget | |
| StackLayout | Stacked arrangement | orientation, spacing | |
| AnchorLayout | Anchored positioning | anchor_x, anchor_y | |
| ScatterLayout | Scalable arrangement | do_rotation, do_translation | |
| Screens | ScreenManager | Screen navigation manager | current, transition, add_widget() |
| Screen | Individual screen | name, manager | |
| Transitions | SlideTransition | Sliding transition | direction, duration |
| FadeTransition | Fade‑out transition | duration | |
| SwapTransition | Instant transition | - | |
| WipeTransition | Wipe transition | direction | |
| Events | Touch | Touch / click object | pos, grab_current, grab() |
| KeyboardGuardian | Keyboard management | request_keyboard() | |
| Graphics | Canvas | Drawing surface | add(), remove(), clear() |
| Color | Color definition | rgba, rgb, hsv | |
| Rectangle | Rectangle shape | pos, size, texture | |
| Ellipse | Ellipse / circle | pos, size, angle_start, angle_end | |
| Line | Line shape | points, width, cap, joint | |
| Animation | Animation | Property animation | start(), stop(), cancel() |
| Transition | Animation easing functions | in_quad, out_bounce, in_out_sine | |
| I/O | FileChooser | File selection widget | path, filters, on_selection |
| Popup | Popup window | title, content, size_hint | |
| ModalView | Modal dialog | auto_dismiss, on_open, on_dismiss | |
| Multimedia | SoundLoader | Sound loading | load(), play(), stop() |
| VideoPlayer | Video playback | source, state, volume | |
| Camera | Camera interface | resolution, play, export_to_png() | |
| Storage | JsonStore | JSON‑based storage | put(), get(), delete() |
| DictStore | Dictionary‑based storage | store, find() | |
| Network | UrlRequest | HTTP requests | url, on_success, on_error |
| Utilities | Factory | Dynamic object creation | register(), unregister() |
| Builder | KV builder | load_file(), load_string() | |
| Logger | Logging system | info(), warning(), error() | |
| Properties | StringProperty | String property | allownone, defaultvalue |
| NumericProperty | Numeric property | min, max, errorvalue | |
| BooleanProperty | Boolean property | defaultvalue | |
| ObjectProperty | Object property | allownone, baseclass | |
| ListProperty | List property | defaultvalue | |
| DictProperty | Dictionary property | defaultvalue | |
| Platforms | platform | Platform detection | android, ios, win, linux, macosx |
| Metrics | dp | Density‑independent pixels | - |
| sp | Scale‑independent pixels | - | |
| mm, cm, inch | Physical measurement units | - |
Practical Examples and Projects
Calculator App
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
class CalculatorApp(App):
def build(self):
main_layout = GridLayout(rows=2)
# Display
self.solution = TextInput(
multiline=False, readonly=True, halign="right"
)
main_layout.add_widget(self.solution)
# Buttons
buttons = [
['7', '8', '9', '/'],
['4', '5', '6', '*'],
['1', '2', '3', '-'],
['.', '0', 'C', '+'],
['=']
]
buttons_layout = GridLayout(cols=4)
for row in buttons:
for button in row:
btn = Button(text=button)
btn.bind(on_press=self.on_button_press)
buttons_layout.add_widget(btn)
main_layout.add_widget(buttons_layout)
return main_layout
def on_button_press(self, instance):
current = self.solution.text
button_text = instance.text
if button_text == 'C':
self.solution.text = ''
elif button_text == '=':
try:
self.solution.text = str(eval(current))
except:
self.solution.text = 'Error'
else:
self.solution.text = current + button_text
CalculatorApp().run()
Task Manager
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
class TaskManager(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = 'vertical'
self.tasks = []
# New task input
input_layout = BoxLayout(size_hint_y=None, height=50)
self.task_input = TextInput(hint_text='Enter a new task')
add_button = Button(text='Add', size_hint_x=None, width=100)
add_button.bind(on_press=self.add_task)
input_layout.add_widget(self.task_input)
input_layout.add_widget(add_button)
self.add_widget(input_layout)
# Task list
self.task_list = BoxLayout(orientation='vertical', size_hint_y=None)
self.task_list.bind(minimum_height=self.task_list.setter('height'))
scroll = ScrollView()
scroll.add_widget(self.task_list)
self.add_widget(scroll)
def add_task(self, instance):
task_text = self.task_input.text.strip()
if task_text:
task_layout = BoxLayout(size_hint_y=None, height=40)
task_label = Label(text=task_text, text_size=(None, None))
delete_button = Button(text='Delete', size_hint_x=None, width=100)
delete_button.bind(on_press=lambda x: self.delete_task(task_layout))
task_layout.add_widget(task_label)
task_layout.add_widget(delete_button)
self.task_list.add_widget(task_layout)
self.task_input.text = ''
def delete_task(self, task_layout):
self.task_list.remove_widget(task_layout)
class TaskApp(App):
def build(self):
return TaskManager()
TaskApp().run()
Frequently Asked Questions
How can I optimize the performance of a Kivy app?
To boost performance, consider:
- Using Canvas instructions instead of many widgets for static graphics
- Keeping the widget tree shallow
- Employing
RecycleViewfor large data lists - Optimizing images (appropriate formats and sizes)
- Avoiding frequent UI updates inside loops
Can Kivy be used for game development?
Yes. Kivy is well‑suited for 2D games because it offers:
- Built‑in animation support
- Efficient OpenGL rendering
- Multitouch and gesture handling
- Audio and music capabilities
- Cross‑platform deployment
How do I ensure my UI adapts to different screen sizes?
For adaptive UI, use:
size_hintfor relative dimensions- dp/sp units instead of raw pixels
- Conditional logic in KV files based on screen size
- Separate layouts for portrait and landscape
- Testing on multiple devices and emulators
What options do I have for database integration in Kivy?
Kivy supports various data‑handling approaches:
- SQLite via the standard
sqlite3module - JsonStore for lightweight settings storage
- ORMs such as SQLAlchemy or Peewee
- Cloud databases accessed through REST APIs
Does Kivy support push notifications?
For notifications, use the plyer library:
from plyer import notification
notification.notify(
title='Notification Title',
message='Notification message',
app_name='My App',
app_icon=None,
timeout=10,
)
Conclusion
Kivy is a powerful and flexible toolkit for creating cross‑platform applications with Python. It is especially attractive to developers who want to build mobile apps without learning platform‑specific languages.
Key benefits include a single codebase for all platforms, a rich widget system, built‑in multitouch support, and a modern approach to UI development. The KV markup language cleanly separates logic from presentation, simplifying maintenance and scaling.
Thanks to an active developer community, extensive documentation, and numerous examples, Kivy remains one of the top choices for Python developers aiming to deliver modern, responsive, and feature‑rich applications across a wide range of devices and operating systems.
The Future of AI in Mathematics and Everyday Life: How Intelligent Agents Are Already Changing the Game
Experts warned about the risks of fake charity with AI
In Russia, universal AI-agent for robots and industrial processes was developed