Customizing your widgets with your own color patterns and adding custom behaviour to widgets helps you make great apps. Even better if you can re-use once written widgets everywhere in your app.
Introduction
Kivys design is based on the mobile for linux framework. It has a very simplistic design and can be considered elegant but there are more modern frameworks like the Material Design available which is used on Android systems. In this article are we going to make our Chuck Norris Jokes App look more Material Design like. You will also learn how you can define a widget once and re-use it everywhere in your app.
We will create outlined and contained buttons with the ripple behaviour to indicate a touch as well as a simple card design to present our jokes.
The Code
Let's start by working on our kv file. First the background of our root widget shall be a flat white surface which is achieved by drawing a white rectangle on the canvas which is just the size of the widget. It will automatically be resized when you maximize the window of your app.
In addition you have to create a card and a ContainedFlatButton and a OutlinedFlatButton. Don't worry about the exact definition for now, here we just use it.
ChuckNorrisJokes:
canvas.before:
Color:
rgba: self.color
Rectangle:
pos: self.pos
size: self.size
Card:
id: card
title: 'Chuck Norris Facts'
text: root.joke
icon_url: root.icon_url
pos_hint: {'center_x': .5, 'top': .9}
ContainedFlatButton:
id: btn1
text: 'Download new Joke'
pos_hint: {'center_x': .3, 'y': .1}
on_press:
root.get_new_joke()
OutlinedFlatButton:
id: btn2
text: 'Reset'
pos_hint: {'center_x': .7, 'y': .1}
on_press:
root.reset_joke()
Below the definition of our layout are we going to create so called rules. One for each widget we have used in the code above. A rule is enclosed by <RuleName>: and finished with a :.
We start with the contained flat button. Accoring to the Material Design guides should it look somewhat like this here:
We need a rounded and filled rectangle as backround with a label on top of it. The important thing is that we bind the labels text to the text variable of the root widget which is ContainedFlatButton. By this we can simply modify the text variable once we implement the rule in our app and the text will be changed. If we don't do that the place holder "I am a button" will be shown.
<ContainedFlatButton>:
text: 'I am a button'
size_hint: None, None
minimum_height: dp(36)
minimum_width: dp(64)
padding_horizontal: dp(16)
padding_vertical: dp(8)
width: max(root.minimum_width, lbl.texture_size[0] + 2 * root.padding_horizontal)
height: max(root.minimum_height, lbl.texture_size[1] + 2 * root.padding_vertical)
canvas.before:
Color:
rgba: self.color
RoundedRectangle:
pos: self.pos
size: self.size
radius: 6,6,6,6
Label:
id: lbl
text: root.text
pos: root.pos
The next rule is the OutlinedFlatButton. The main difference is that we do not draw a rounded rectangle but we draw a line around the outline of the widget.
<OutlinedFlatButton>:
text: 'I am a button'
size_hint: None, None
minimum_height: dp(36)
minimum_width: dp(64)
padding_horizontal: dp(16)
padding_vertical: dp(8)
width: max(root.minimum_width, lbl.texture_size[0] + 2 * root.padding_horizontal)
height: max(root.minimum_height, lbl.texture_size[1] + 2 * root.padding_vertical)
canvas.before:
Color:
rgba: self.color
Line:
width: dp(1)
rounded_rectangle: (self.pos[0], self.pos[1], self.size[0], self.size[1], 6)
Label:
id: lbl
text: root.text
color: root.color
pos: root.pos
Finally the card has to be created. There are no strict definition for a card by the Material Design guides but this here is kind of the template we are trying to follow.
That requires some more lines of code but it is straight forward. Again we start with drawing an outline around the whole widget. Since the Card has inherited from the BoxLayout we add another BoxLayout in the first row of the card which is supposed to show an image and a title for the card. In the next row there will be only a label showing our downloaded joke.
<Card>:
title: 'I am a title'
text: 'I am a text'
icon_url: ''
padding: dp(6)
size_hint: None, None
width: dp(400)
height: box.height + lbl.height + root.padding[0] * 2
orientation: 'vertical'
canvas.before:
Color:
rgba: self.outline_color
Line:
width: dp(1)
rounded_rectangle: (self.pos[0], self.pos[1], self.size[0], self.size[1], 6)
BoxLayout:
id: box
orientation: 'horizontal'
size_hint: 1,None
height: dp(80)
AsyncImage:
size_hint: None, 1
source: root.icon_url
Label:
text: root.title
color: root.color
halign: 'left'
valign: 'top'
font_size: 25
size_hint: 1, .8
size: self.texture_size
text_size: self.width, None
Label:
id: lbl
text:root.text
color: root.color
halign: 'left'
valign: 'top'
size_hint: 1, None
size: self.texture_size
text_size: self.width, None
The image here wher I highlighted the canvas of each widget give you a better view of how everything is aligned. And you can pretty clearly see the padding of 6dp.
Taking care of the Python file now starts with defining the color scheme. The basis is this pattern here from which we only use the primary and on_primary colors. To make them available for every widget are they added to the class which inherits from App. The only thing we have to do is to convert the hex values in the material design color scheme to the rgba values Kivy requires. Lucky us that this is taken care of by a Kivy provided funtion.
class ChuckNorrisJokesApp(App):
colors = {'primary':'#6200EE',
'background': '#FFFFFF',
'on_primary':'#FFFFFF',
'on_background':'#000000'}
def __init__(self, **kwargs):
super(ChuckNorrisJokesApp, self).__init__(**kwargs)
self._colors = {}
for key, value in self.colors.items():
self._colors[key] = get_color_from_hex(value)
Our buttons are a bit mor complex now. We cannot simply use the button class anymore to inherit from. First our button is a layout. A floatlayout to be specific. But we also want it to behave like a button and to deal with the on_press event and others. Thats why it also needs to inherit the ButtonBehavior. Last we want the so called ripple behaviour which is specified in the material design which is a simple overlayed animation over the button to indicate that it is being pressed. Therefore we lastly inherit from TourchRippleBehavior.
class ContainedFlatButton(TouchRippleBehavior, ButtonBehavior, FloatLayout):
def __init__(self, **kwargs):
super(ContainedFlatButton, self).__init__(**kwargs)
self.color = App.get_running_app()._colors['primary']
def on_touch_down(self, touch):
if self.collide_point(touch.x, touch.y):
touch.grab(self)
self.ripple_show(touch)
return super(ContainedFlatButton, self).on_touch_down(touch)
def on_touch_up(self, touch):
touch.ungrab(self)
self.ripple_fade()
return super(ContainedFlatButton, self).on_touch_up(touch)
The outlined button is created in a similar way. The only change is that we need to set the color of the ripple to match our primary color. The standard color is white which is fine for the contained button but would have no effect on the white backround of our outlined button.
class OutlinedFlatButton(TouchRippleBehavior, ButtonBehavior, FloatLayout):
def __init__(self, **kwargs):
super(OutlinedFlatButton, self).__init__(**kwargs)
self.color = App.get_running_app()._colors['primary']
def on_touch_down(self, touch):
if self.collide_point(touch.x, touch.y):
touch.grab(self)
self.ripple_color = App.get_running_app()._colors['primary']
self.ripple_fade_to_alpha = 0,6
self.ripple_show(touch)
return super(OutlinedFlatButton, self).on_touch_down(touch)
def on_touch_up(self, touch):
touch.ungrab(self)
self.ripple_fade()
return super(OutlinedFlatButton, self).on_touch_up(touch)
The simplest class is the Card. Since we don't have to take care of specific events it boils down to defining the colors for the outline. We take the on_primary color (black) and set the transparent value to 50%.
class Card(BoxLayout):
def __init__(self, **kwargs):
super(Card, self).__init__(**kwargs)
color = App.get_running_app()._colors['on_background']
self.color = color.copy()
color[3] = 0.5
self.outline_color = color
Source Code on GitHub
The latest version of the code is always available for you on GitHub
https://github.com/HaikoKrais/ChuckNorrisJokesApp
What's Next
After learning about the basics, from creating an app using the basic widgets proviede by Kivy, modifying them to suit your design ideas and working a bit with the kv language is it now time to learn about animating widgets and learning how to built a classic game. Sneak.