This section presents some code examples using Kivy’s RelativeLayout. RelativeLayout is a layout that allows you to place widgets at arbitrary positions. It also explains how to calculate it based on the position and size of its parent and other widgets.
What is RelativeLayout?
RelativeLayout is a Kivy layout in which widgets are positioned relative to their parents. Widgets can be placed in any position using the pos_hint and pos properties, which set the widget’s position.
Code Example
relativelayout1.py
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
class RootWidget(RelativeLayout):
pass
class RelativeLayout1(App):
def build(self):
return RootWidget()
if __name__ == "__main__":
RelativeLayout1().run()
relativelayout1.kv
<RelativeLayout>:
Button:
text: "Button 1"
size_hint: 1, None
height: 100
pos_hint: {'x': 0.1, 'y': 0.7}
Button:
text: "Button 2"
size_hint: None, None
size: 100, 40
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
Button:
text: "Button 3"
size_hint: 0.2, 0.2
pos_hint: {'right': 0.8, 'top': 0.3}
After executing this code, the widget will appear as shown in the figure below.

To set the widget’s position, use the pos_hint or pos property, where pos_hint is a percentage and pos is an absolute coordinate. These are explained in more detail in the following article.
The size of the widget is determined by the size_hint property. These details are explained in the following article.
Code Example to Obtain Widget Position
Here is an example of code that uses RelativeLayout to get the position of a widget. It also explains how to calculate the position and size based on the position and size of the parent and other widgets.
relativelayout2.py
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.widget import Widget
from random import randint
class ColoredArea(Widget):
pass
class RootWidget(RelativeLayout):
# Place the initial position of Labels 1 through 3 off the screen.
def on_kv_post(self, base_widget):
self.ids.label1.pos = (2000,2000)
self.ids.label2.pos = (2000,2000)
self.ids.label3.pos = (2000,2000)
return super().on_kv_post(base_widget)
def move_labels(self):
for i in range(1, 4):
label = self.ids[f'label{i}']
new_x = randint(0, self.ids.colored_area.width - label.width)
new_y = randint(0, self.ids.colored_area.height - label.height)
label.pos = (new_x + self.ids.colored_area.x, new_y + self.ids.colored_area.y)
self.ids[f'label{i}_pos'].text = f'Label {i} pos: ({new_x}, {new_y})'
for child in self.children:
if not hasattr(child, 'name'): # Exclude colored_area because it has no text.
print(f"Root1-Child {child.text} size: {child.size}, pos: {child.pos}")
print(f"Root2-Parent size: {self.size}, pos: {self.pos}")
print(f"Root2-Child size: {self.ids.button.size}, pos: {self.ids.button.pos}")
class RelativeLayout2(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
RelativeLayout2().run()
relativelayout2.kv
<RootWidget>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.8, 0.5
Rectangle:
size: self.size
pos: self.pos
ColoredArea:
id: colored_area
name: 'name'
Button:
id: button
text: "Move Labels"
size_hint: None, None
size: 150, 50
pos_hint: {'center_x': 0.5, 'y': 0.9}
on_press:
root.move_labels()
# Obtains the size and position of its own widget.
print(f"Self(button) pos-x: {self.x} pos-y: {self.y}")
print(f"Self(button) pos-center_x: {self.center_x} pos-center_y: {self.center_y}")
print(f"Self(button) width: {self.width} height: {self.height}")
print(f"Self(button) size: {self.size}")
print(f"Self(button) pos: {self.pos}")
# Get size and position of other widgets by id.
print(f"label1_pos pos-x: {label1_pos.x} pos-y: {label1_pos.y}")
print(f"label1_pos pos-center_x: {label1_pos.center_x} pos-center_y: {label1_pos.center_y}")
print(f"label1_pos width: {label1_pos.width} height: {label1_pos.height}")
print(f"label1_pos size: {label1_pos.size}")
print(f"label1_pos pos: {label1_pos.pos}")
# Get the size and position of the parent.
print(f"Parent pos-x: {self.parent.x} pos-y: {self.parent.y}")
print(f"Parent pos-center_x: {self.parent.center_x} pos-center_y: {self.parent.center_y}")
print(f"Parent.width: {self.parent.width} Parent.height: {self.parent.height}")
print(f"Parent.size: {self.parent.size}")
print(f"Parent.pos: {self.parent.pos}")
Label:
id: label1
text: "1"
size_hint: None, None
size: 50, 50
font_size: 50
Label:
id: label2
text: "2"
size_hint: None, None
size: 50, 50
font_size: 50
Label:
id: label3
text: "3"
size_hint: None, None
size: 50, 50
font_size: 50
Label:
id: label1_pos
text: "Label 1 pos: "
size_hint: None, None
size: 200, 50
pos_hint: {'center_x': 0.5}
y: int(button.y - 100)
Label:
id: label2_pos
text: "Label 2 pos: "
size_hint: None, None
size: label1_pos.size
pos_hint: {'center_x': 0.5}
y: int(button.y - 140)
Label:
id: label3_pos
text: "Label 3 pos: "
size_hint: None, None
size: label1_pos.size
pos_hint: {'center_x': 0.5}
y: int(button.y - 180)
<ColoredArea>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.8, 0.5
Rectangle:
size: self.size
pos: self.pos
size_hint: None, None
size: (int(self.parent.width * 0.9), int(self.parent.height * 0.6))
pos_hint: {'center_x': 0.5, 'center_y': 0.4}
Code Explanation
In this code, the label randomly changes position in the light purple area when the button is pressed, and the position is retrieved and displayed. It also retrieves the position and size of the parent and other widgets and displays them in the console.
Code Structure
This code has the following tree structure.
<RootWidget>
+--- RelativeLayout
+---+--- ColoredArea # colored_area is the area where the label can be moved.
+---+--- Button # button
+---+--- Label # label1 Label to be moved.
+---+--- Label # label2 Label to be moved.
+---+--- Label # label3 Label to be moved.
+---+--- Label # label1_pos Label to indicate location.
+---+--- Label # label2_pos Label to indicate location.
+---+--- Label # label3_pos Label to indicate location.
Moving Area of Labels
The ColoredArea subclass defines the label movement area. Labels move within this colored area.
class ColoredArea(Widget):
pass
<ColoredArea>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.8, 0.5
Rectangle:
size: self.size
pos: self.pos
size_hint: None, None
size: (int(self.parent.width * 0.9), int(self.parent.height * 0.6))
pos_hint: {'center_x': 0.5, 'center_y': 0.4}
<ColoredArea>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.8, 0.5
Rectangle:
size: self.size
pos: self.pos
A rectangle is drawn in canvas. The drawing method is the same as the background color of the layout. See the following article for details.
The background color of the layout and this colored area have the same RGBA value, but different colors are displayed. This happens because the alpha value (transparency) does not work for the layout background color.
size_hint: None, None
size: (int(self.parent.width * 0.9), int(self.parent.height * 0.6))
The above sets the size of the colored area. The size of the parent RelativeLayout is obtained and used as the basis. The size is specified as 90% width and 60% height. it is returned as a float type, so it is converted to an int type. the kv code will generate an error if this is not done.
pos_hint: {'center_x': 0.5, 'center_y': 0.4}
The colored area is placed at 50% on the x-axis and 40% on the y-axis.
Places Labels for Movement Outside the Screen Area
No position is set for the label for movement. If not set, the labels are overlapped at the default position of [0 , 0] (lower left). This does not look good, so the initial position of the label is placed off-screen.
class RootWidget(RelativeLayout):
# Place the initial position of Labels 1 through 3 off the screen.
def on_kv_post(self, base_widget):
self.ids.label1.pos = (2000,2000)
self.ids.label2.pos = (2000,2000)
self.ids.label3.pos = (2000,2000)
return super().on_kv_post(base_widget)
Label:
id: label1
text: "1"
size_hint: None, None
size: 50, 50
font_size: 50
The reason why the label position is not set here is that if the position is set, the label will only move within the set range. The expected behavior is to move within the colored area.
The on_kv_post() is a callback function that is called when the kv file has finished loading. It can be placed in the constructor, but this is a more reliable way to obtain the ids.
Move Labels to Random Positions
In move_labels() of the RootWidget subclass, the process of randomly changing the position of labels is described. The id of the labels to be repositioned are label1, label2, and label3. The following three processes are performed here.
- Obtains the numeric value of the id of the labels.
- Obtains the start and end values of the random number.
- Set the position of the parent widget.
- Display the position in the colored area in the text of Label.
def move_labels(self):
for i in range(1, 4):
label = self.ids[f'label{i}']
new_x = randint(0, self.ids.colored_area.width - label.width)
new_y = randint(0, self.ids.colored_area.height - label.height)
label.pos = (new_x + self.ids.colored_area.x, new_y + self.ids.colored_area.y)
self.ids[f'label{i}_pos'].text = f'Label {i} pos: ({new_x}, {new_y})'
Obtain the Numeric Value Used for the id of the Label
for i in range(1, 4):
label = self.ids[f'label{i}']
The range() is Python built-in functions that creates a list of consecutive integers from the start number to the end number.
range(start, stop [, step ])
For start, specify an integer to start with; for stop, specify an integer to end with; and for step, advance by the number specified in step. For example, range(0,10,2) would create a list of [0,2,4,6,8]; a negative value for step would reverse the order to [8,6,4,2,0].
The number specified for stop is not counted. For example, in the case of range(1,4), a list of [1,2,3] is created.
In this example, since the id of the labels to be moved are label1, label2, and label3, range() is used to describe the numerical values of these labels.
Obtaining the Start and End Values of Random Numbers
The new_x calculates the width of the colored area and specifies the range of the x-axis to randint(). Similarly, new_y calculates the height of the colored area and specifies the range of the y-axis to randint().
new_x = randint(0, self.ids.colored_area.width - label.width)
new_y = randint(0, self.ids.colored_area.height - label.height)
The randint() is Python built-in functions that returns a randomly generated integer of type int.
randint(start, stop )
The start is the starting integer for which the random number is generated, and stop is the ending integer.
In this example self.ids.colored_area.width gets the width of the colored area. label.width gets the width of self.ids.label{i}. The result of this acquisition is [1080,50], and 1080 – 50 = 1030 is specified as the parameter stop of randint(). In other words, a random number is generated between 0 and 1030, and the result is assigned to the variable new_x. new_y obtains the height in the same way.
The reason for subtracting the width of the label from the width of the colored area is to prevent the label from exceeding the width of the colored area. Without this calculation, if a label is placed at position 1080 at the end of the width, the label will be displayed outside the colored area.

Setting the Position of the Parent Widget
label.pos = (new_x self.ids.colored_area.x, new_y self.ids.colored_area.y)
Although new_x and new_y are calculated for the range of the colored area, it is the position of the parent that is actually positioned using pos. In other words, pos cannot specify a position within the color range. Therefore, it is necessary to add the difference between the position of the parent and the color region.
In self.ids.colored_area.x and self.ids.colored_area.y, the origin (lower left corner) of the x-axis and y-axis of the colored area from the parent is acquired. The value obtained here is [60,90], in other words, the margin between the parent and the colored area. Adding this to new_x and new_y, the position of the parent can be calculated.

Label Text With Position in Colored Area
The x-axis and y-axis positions within the color region are displayed in the text of Label.
self.ids[f'label{i}_pos'].text = f'Label {i} pos: ({new_x}, {new_y})'
Example of Getting the Position and Size of a Widget
An example of how to write to get the position and size of a widget is shown in the console.
No id Name Can Be Obtained
This time, I had a little trouble because I could not get the id name.
for child in self.children:
if not hasattr(child, 'name'): # Exclude colored_area because it has no text.
print(f"Root1-Child {child.text} size: {child.size}, pos: {child.pos}")
The above code uses children to get a list of children. The variable child is assigned the list of children objects in the RootWidget tree.
The hasattr() is Python built-in functions that checks for the existence of object attributes. It returns True if the specified attribute exists, or False if it does not.
hasattr(object, name )
object is an instance and name is an attribute name.
This is an error because there is no text defined in the ColoredArea. To avoid the error, the colored_area must be excluded. Since the id name cannot be obtained, the name property is defined instead. name property is often used to determine the screen name for screen transitions, but it is useful when you want to make some kind of determination.
ColoredArea:
id: colored_area
name: 'name'
Place Widgets Based on Other Widget
Aligning widgets in a RelativeLayout can be a bit tedious. In such cases, it is sometimes better to position widgets based on the positions of other widget.
Label:
id: label1_pos
text: "Label 1 pos: "
pos_hint: {'center_x': 0.5}
y: int(button.y - 100)
In the above, the Label position is specified based on the Button position.
Make It the Same Size as Other widgets
If you want to make the widget the same size as other widgets, you can specify it by specifying the widget’s id_name.size as shown below.
Label:
id: label2_pos
text: "Label 2 pos: "
size_hint: None, None
size: label1_pos.size
Comment