Kivy - GUI cross -platform development

онлайн тренажер по питону
Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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:

  1. App class initialization
  2. Calling build() to create the UI
  3. Starting the main event loop
  4. Processing user input
  5. 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 RecycleView for 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_hint for 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 sqlite3 module
  • 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.

News