Kivy recipe: settings screen customization

So I've decided to build a kognitivo styled settings screen. Here is some documentation on how to build the custom settings screen. But here is the problem (actually like everywhere in kivy): You want the settings screen to be styled as your app in general, but almost all the styles are hardcoded somehere (spoiler: in kivy's style.kv).

So let's start.

Step 1. Configure App class

Add these to methods to your App class in with the following content

    def build_settings(self, settings):
        with open("settings.json", "r") as settings_json:
            settings.add_json_panel('Kognitivo settings', self.config,

    def build_config(self, config):
        config.setdefaults('section1', {
            'key1': 'value1',
            'key2': '42'

Create settings.json your project root with content like:

        "type": "title",
        "title": "Reminders"
        "type": "options",
        "title": "Enable notifications",
        "desc": "Description of my first key",
        "section": "section1",
        "key": "key1",
        "options": ["value1", "value2", "another value"]

        "type": "numeric",
        "title": "My second key",
        "desc": "Description of my second key",
        "section": "section1",
        "key": "key2"

add json as extension to package in your buildozer.spec:

source.include_exts = py,png,jpg,kv,atlas,ttf, json  
Step 2. Create custom settings

I think that the logic of settings screen customization is honestly the best I have ever seen in kivy. It uses the classes of the child widgets as property value, which makes it easy customizable. Because of some unknown reason this flexibilty is only implemented for settings and setting interface classes. But there is a good alternative for SettingsItem also with the register concept.

So let's begin. Create Settings class somewhere, where you store your widget classes (I do it under widgets/

class KognitivoSettingsInterface(InterfaceWithNoMenu):  

class KognitivoSettings(Settings):  


    def add_kivy_panel(self):

if you don't want to see kivy settings in your settings dialog (who but developers would want to see it?)

In kivy language

    interface_cls: 'KognitivoSettingsInterface'

and in your set settings_cls to new settings class:

class KognitivoApp(App):  
    manager = ObjectProperty()
    settings_cls = KognitivoSettings
Step 3. Customizing setting items

Add constructor to your Settings class:

class KognitivoSettings(Settings):  
    def __init__(self, **kwargs):
        super(KognitivoSettings, self).__init__(**kwargs)
        self.register_type('title', KognitivoSettingTitle)

Now for evert 'title' typed setting your custom widget (KognitivoSettingTitle in my case) will be used. Let's do some customization on it:


class KognitivoSettingTitle(Label):  
    title = Label.text

kivy lang

    text_size: self.width - 32, None
    size_hint_y: None
    height: max(dp(20), self.texture_size[1] + dp(20))
    color: settings.TEXT_COLOR
    font_size: '15sp'
    bold: True
    halign: 'center'

It's stolen from style.kv, but if don't do it, it's just not working and I'm too lazy to find out why. But you can try to overload only what you want like:

    color: 0, 0, 1, 1

If you want more, you just have to overload the main widget classes (Button, Label, Popup, Modal). Take a look at how they are built in kivy/data/styles.kv and place the code in your main kivy language file (names the same as your app). Here is some example how you could globally overload the font name:

#:import transition kivy.uix.screenmanager.RiseInTransition

    font_name: KIVY_DEFAULT_FONT

    transition: transition()

To have the correct kivy files load order you need to place kivy loading in build method, right before craeating the root widget:

class KognitivoApp(App):  
    def build(self):
        root = RootWidget()
Step 4. Custom setting item class.

For Kognitivo I needed to have a time picker, so I decided to build 'time' setting item class. So this is how you can do it.

Register the new type in your Settings class constructor like this (see title example above):

self.register_type('time', SettingTime)  

Add SettingTime class:

class SettingTime(SettingString):  
    up_button = ObjectProperty(None)
    down_button = ObjectProperty(None)

    def _validate(self, instance):
            datetime.strptime(self.textinput.text, '%H:%M')
            self.value = self.textinput.text
        except ValueError:

    def on_button_press(self, instance):
        interval = timedelta(minutes=30) if instance.direction == 'up' else timedelta(minutes=-30)
        current_datetime = datetime.strptime(self.textinput.text, '%H:%M')
        self.textinput.text = (current_datetime + interval).time().strftime('%H:%M')

    def _create_popup(self, instance):

        # create popup layout
        content = BoxLayout(orientation='vertical', spacing='5dp')
        self.popup = popup = Popup(title=self.title, content=content, size_hint=(.9, .4))

        self.textinput = TimeSettingLabel(text=self.value)

        self.up_button = TimeSettingButton(direction='up')
        self.down_button = TimeSettingButton(direction='down')

        # construct the content, widget are used as a spacer

        # 2 buttons are created for accept or cancel the current value
        btnlayout = BoxLayout(size_hint_y=None, height='50dp', spacing='5dp')
        btn = Button(text='Ok')
        btn = Button(text='Cancel')

        # all done, open the popup !

I needed to overload the create_popup method as much as I needed to customize the content of it. Thanks at least for writing it in the separate method :)))

Take a look at the class, it contents nothing that special, so you maybe need to overload the _validate method to perform the validation (there are no error messages by default, if you want some, you need implement it by yourself), and _create_popup to customize the content.

Note that if you have customized some parent classes (like SettingString in my case), you have to inherit from that class, not from the kivy's one.

Here is how it looks like at the end in my case:

That's it. Good luck :)

comments powered by Disqus