In this article I am going to show you how to develop a stop watch and timer application with the kivymd framework. The first part of this tutorial will take you through creating the stopwatch and the second part through creating the the timer.
Final Product
The final application will look as below:
The UI
Lets get started with the user interface.
Prerequisites
For this project, I'm using:
kivy==2.1.0
kivymd==1.1.1
Make sure both are installed in a virtual environment.
Create two files:
main.py
main.kv
Inside main.py
add the following code:
import kivy
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.dialog import MDDialog
from kivymd.uix.list import TwoLineIconListItem, IconLeftWidget
from kivy.properties import NumericProperty, StringProperty
from datetime import timedelta
from kivy.clock import Clock
kivy.require('2.1.0')
class MainApp(MDApp):
def build(self):
# Setting the theme
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Gray"
self.theme_cls.primary_hue = "200"
if __name__ == '__main__':
app = MainApp()
app.run()
And inside main.kv, add the following code:
#:kivy 2.1.0
MDScreenManager:
MDScreen:
name: 'main_screen'
MDBottomNavigation:
selected_color_background: "orange"
text_color_active: "lightgrey"
MDBottomNavigationItem:
name: 'screen 1'
text: 'Stop Watch'
icon: 'timer'
MDBoxLayout:
orientation: 'vertical'
MDLabel:
text: 'Stop Watch'
halign: 'center'
theme_text_color: "Custom"
text_color: 1, 1, 1, 1
font_style: 'H2'
MDBottomNavigationItem:
name: 'screen 2'
text: 'Timer'
icon: 'timer-sand'
MDBoxLayout:
orientation: 'vertical'
MDTopAppBar:
right_action_items: [["plus", ]]
md_bg_color: app.theme_cls.bg_dark
MDLabel:
text: 'Timer'
halign: 'center'
theme_text_color: "Custom"
text_color: 1, 1, 1, 1
font_style: 'H2'
If you run this application right now, you will get the following:
The stopwatch
First, we are going to build the stopwatch. The stop watch is going to be fairly simple. It will consist of a Label
, to show the elapsed time, and three buttons, one to reset the stopwatch, another to start, pause or stop the stopwatch and the last one to record the lap times. Lastly, it will have a list within a ScrollView
widget in which we will be adding the recorded lap times.
Start by modifying main.kv
as follows :
#...
# Replace the label with the text "Timer" with the following
MDLabel:
id: stopwatch_lbl
text: app.stopwatch_time
halign: 'center'
theme_text_color: "Custom"
text_color: 1, 1, 1, 1
font_style: 'H2'
# Add a scroll view and a list
MDScrollView:
MDList:
id: count_list
# Add a float layout
MDFloatLayout:
pos_hint: {'center_x': .5}
size_hint_y: .3
size_hint_x: .5
spacing: 50
# Add three icon buttons
MDIconButton:
id: reset_btn
icon: 'undo-variant'
pos_hint: {'center_x': .2}
on_press: app.reset_stopwatch()
disabled: True
MDIconButton:
id: play_pause_btn
icon: 'play'
pos_hint: {'center_x': .5}
on_press: app.start_or_stop_stopwatch()
MDIconButton:
id: record_lap_btn
icon: 'timelapse'
pos_hint: {'center_x': .8}
on_press: app.time_lap()
disabled: True
#...
main.py
#...
class MainApp(MDApp):
stopwatch_time = StringProperty()
milliseconds = NumericProperty()
seconds = NumericProperty()
minutes = NumericProperty()
def build(self):
# Setting the theme
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Gray"
self.theme_cls.primary_hue = "200"
def on_start(self):
self.stopwatch_time = "00:00:00"
def start_or_stop_stopwatch(self):
pass
def reset_stopwatch(self):
pass
def time_lap(self):
pass
#...
The latest changes have added icon buttons and a dynamic label to the interface. By setting the text of the label to app.stopwatch_time
, we can change the text displayed on the interface by simply changing the value of the attribute stopwatch_time
. Kivy properties make it easy to pass updates from the python side to the user interface and hence we used the StringProperty
object.
In the above code we have also defined seconds, milliseconds and minutes as Numeric Properties which we are going to use to make updates to the stopwatch.
The on_start
function is going to set the stopwatch to 00:00:00
when the application starts.
The start_or_stop_stopwatch
function will start the stopwatch if it isn't running or stop the stopwatch if it is.
The reset_stopwatch
function will reset the stopwatch to 00:00:00
.
The time_lap
funtion will measure the time between laps.
On running the application, you will get the following:
Now lets get our stopwatch to work.
main.py
# ...
class MainApp(MDApp):
#...
# Lap counter
count = 1
watch_started = False # Whether watch is running or not
# store the last recorded lap time
last_lap_time = {
'minutes': 0,
'seconds': 0,
'milliseconds': 0
}
#...
count
is going to keep track of the list items that are added when timing a lap which we'll see in action soon.
watch_started
is going to keep track of whether or not the stop watch is running.
The dictionary last_lap_time
contains data on the last recorded lap time which is updated every time we hit the lap button.
main.py
#...
class MainApp(MDApp):
#...
# add this function
def get_string_time(self, dt):
"""Function to increment milliseconds and convert the time elapsed to string format to which the label is set"""
self.increment_milliseconds()
milliseconds = str(self.milliseconds)
seconds = str(self.seconds)
minutes = str(self.minutes)
if len(milliseconds) < 2:
milliseconds = '0' + milliseconds
if len(seconds) < 2:
seconds = '0' + seconds
if len(minutes) < 2:
minutes = '0' + minutes
self.stopwatch_time = minutes + ":" + seconds + ":" + milliseconds
# Modify start_or_stop_stopwatch to look as follows
def start_or_stop_stopwatch(self):
"""Function to stop the stopwatch if it is not running otherwise stop it"""
if self.watch_started:
self.watch_started = False
self.root.ids['record_lap_btn'].disabled = True
self.root.ids['play_pause_btn'].icon = 'play'
self.root.ids['reset_btn'].disabled = False
Clock.unschedule(self.get_string_time) # Unschedule the get_string_time function
else:
self.watch_started = True
self.root.ids['play_pause_btn'].icon = 'pause'
self.root.ids['record_lap_btn'].disabled = False
Clock.schedule_interval(self.get_string_time, 0.1) # schedule the get_string_time function to run every 10ms
#...
The get_string_time
function converts the timer from numbers to a string, that is, it gets the values of the milliseconds, seconds and minutes and converts them to a string. For the sake of display, it checks whether the values have two digits, and if they do not, it adds a 0
before the digit to make them two and hence we can always display our minutes, seconds and milliseconds with two digits.
We have modified the start_or_stop_stopwatch
and now if the stopwatch was not running, it will change the status of watch_started
to True
to indicate that it is now running. It will then change the 'play' icon to the 'pause' icon, which will allow us to pause the stopwatch. The record_lap_button
is then enabled so that we can record our laps. Finally, we will schedule the get_string_time
function with kivy clock to run every 10 milliseconds so that the stopwatch is updated every 10 milliseconds.
If the stopwatch was running, it will stop/pause the stopwatch and then disable the lap button since we can't record laps if the watch is not running. It will then switch the icon to the 'play' icon. The reset button is then enabled as well to allow us to reset our stopwatch. Finally, it will unschedule
the get_string_time
to stop it from executing every 10 milliseconds.
main.py
class MainApp(MDApp):
#...
# Modify reset_stopwatch as follows
def reset_stopwatch(self):
"""Set the stopwatch to 00:00:00"""
if self.watch_started:
Clock.unschedule(self.get_string_time)
self.stopwatch_time = "00:00:00"
self.milliseconds = 0
self.seconds = 0
self.minutes = 0
# disable reset and lap buttons
self.root.ids['reset_btn'].disabled = True
self.root.ids['record_lap_btn'].disabled = True
# Reset lap time
self.last_lap_time['minutes'] = 0
self.last_lap_time['seconds'] = 0
self.last_lap_time['milliseconds'] = 0
# Modify time_lap
def time_lap(self):
"""Get the time between laps and add a list item to list"""
lap_time = f"Count {self.count}: " + self.stopwatch_time
list_item = TwoLineIconListItem(
IconLeftWidget(
icon="av-timer"
),
text=lap_time,
secondary_text=self.calculate_time_difference(),
theme_text_color="Custom",
text_color=(1, 1, 1, 1),
secondary_theme_text_color="Custom",
secondary_text_color=(1, 1, 1, 1)
)
self.root.ids['count_list'].add_widget(list_item, index=-1)
self.count += 1
# add the following function
def increment_milliseconds(self):
"""Increment the milliseconds by 10ms"""
self.milliseconds += 10
if self.milliseconds == 100:
self.increment_seconds()
self.milliseconds = 0
# add the following function
def increment_seconds(self):
"""Increment the seconds by 1 second"""
self.seconds += 1
if self.seconds == 60:
self.increment_minutes()
self.seconds = 0
# add the following function
def increment_minutes(self):
"""Increment the minutes by 1 minute"""
self.minutes += 1
# add the following function
def calculate_time_difference(self):
"""Calculate the time difference between the laps records
"""
lap_time = timedelta(
minutes=self.minutes,
seconds=self.seconds,
milliseconds=self.milliseconds
) - timedelta(
minutes=self.last_lap_time['minutes'],
seconds=self.last_lap_time['seconds'],
milliseconds=self.last_lap_time['milliseconds']
)
lap_time = str(lap_time)[2:-3]
lap_time = lap_time.split(':')
lap_time = [i.split('.') for i in lap_time]
minutes = int(lap_time[0][0])
seconds = int(lap_time[1][0])
milliseconds = int(lap_time[1][1])
self.last_lap_time['minutes'] = self.minutes
self.last_lap_time['seconds'] = self.seconds
self.last_lap_time['milliseconds'] = self.milliseconds
return f"{minutes}:{seconds}:{milliseconds}"
#....
Ok, I know it's a lot of code but I'll do my best to explain it.
The reset_stopwatch
method will check if the stopwatch is running. If it is, it will unschedule the get_string_time
method. It will then set stopwatch_time
to 00:00:00
which will update our label to '00:00:00'. Next it will set the milliseconds, seconds and minutes to 0 and also disable the reset and the lap buttons. Lastly it will set the minutes, seconds and milliseconds keys in the last_lap_time
dictionary to zero.
The time_lap
method gets the current count and creates a string with the count and current stopwatch time: lap_time = f"Count {self.count}: " + self.stopwatch_time
. Next a two line icon list item is created with the lap_time
as text. The secondary text is calculated by the calculate_time_difference
method, which subtracts the last recorded lap time from the current time. The list item is then added to the the list.
Running the application now, we get our functional stopwatch:
All the code for this app is available in this Github repository.
In the next part of the tutorial, we look at how to create the timer so stay tuned!🙂
Top comments (3)
Greetings, I am currently attempting to transform my python code into an APK, however, after converting it and installing the file, it unfortunately crashes right after the kivy logo appears. I am using google collab to convert my code into APK.
Spec file
main.py
plz help me
Greetings, I am currently attempting to transform my python code into an APK, however, after converting it and installing the file, it unfortunately crashes right after the kivy logo appears. I am using google collab to convert my code into APK. Please help!!
Spec file
main.py
Can you please show me how to include a text file or a JSON file as a database. I wanna make a kivy desktop application in onefile. It's giving me error. Please help me out.