I am very new to kivy, and have been trying to figure out how to order rectangles created with the canvas class in the z dimension. Using the .kv language I created a root widget that creates a grid of rectangles. That part worked out fine, then I created a child widget consisting of 1 blue rectangle that I was able to move around with the kivy clock. The problem is that I want to display the blue rectangle on top of everything else.
This is my python code (full of unnecessary imports):
import kivy
from kivy.config import Config
Config.set('graphics', 'resizable', False)
Config.set('graphics', 'width', '1000')
Config.set('graphics', 'height', '700')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import *
from kivy.core.window import Window
from kivy.properties import (ObjectProperty, ReferenceListProperty, ListProperty)
import random
from kivy.clock import Clock
from kivy.vector import Vector
class board(FloatLayout):
car = ObjectProperty(None)
def __init__(self, **k):
super(board, self).__init__(**k)
with self.canvas:
l = 2
w = 4
for b in range(1, l+1):
for a in range(1,w+1):
for i in range(3):
for x in range(2):
numb = random.randint(1, 2)
if numb == 1:
Color(1, 0, 0, 1)
Rectangle(pos=(a*70+20*(x)-50, b*80+20*(i)-20), size=(10, 10))
Color(0,1,0,1)
print(a)
print(b)
Rectangle(pos=(a*70-10, 20), size=(20, l*80+35))
Rectangle(pos=(0, 20), size=(20, l*80+35))
Rectangle(pos=(20, 20), size=(w*70-30, 20))
Rectangle(pos=(20, b*80 + 35), size=(w*70-30, 20))
class Car(Widget):
velocity = ListProperty([1, 1])
def __init__(self, **k):
super(Car, self).__init__(**k)
self.canvas.add(Color(1,0,0,1))
self.canvas.add(Rectangle(size=(50,50)))
Clock.schedule_interval(self.update, 1.0/60.0)
def update(self, *args):
self.x += self.velocity[0]
self.y += self.velocity[1]
print('hello')
class RobotApp(App):
def build(self):
ba= Car()
return board()
if __name__ == '__main__':
RobotApp().run()
This is my kivy code:
#:kivy 1.11.1
<Car>:
canvas:
Color:
rgba: 0, 0, 1, 1
Rectangle:
pos:self.pos
size: 10, 10
<board>:
car: Cars
canvas:
Rectangle:
pos: 0, 0
size: 500, 700
Car:
id: Cars
pos: self.parent.pos
Thanks to Inclement, I was able to figure out the problem. I had to change three lines:
One in the python file.
with self.canvas:
to
with self.canvas.before:
Two in the kivy file.
<Car>:
canvas:
to
<Car>:
canvas.after:
then
<board>:
canvas:
to
<board>:
canvas.before:
my blue square now displays on top, thanks!
Related
I want to create a list of coloured labels. The thing is that I could do it with the kv file, but I need to do it through the build() method. So I tried replicate what I have done, but it does not work. And I can't understand why.
This is what I've coded
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import *
class RL(RelativeLayout): # Creates the background colour for each label
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas:
Color(.7, 0, .5, 1)
Rectangle(size_hint=self.size)
class MainMenu(BoxLayout):
N_LBLS = 8
labels_text = []
RL_list = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text='do something')
button.bind(on_release=self.change_text)
box = BoxLayout(orientation='vertical', padding= 10, spacing = 15)
for i in range(0, self.N_LBLS):
self.RL_list.append(RL())
self.labels_text.append(Label(text=f'{i}º label', size_hint=self.size))
self.RL_list[i].add_widget(self.labels_text[i])
box.add_widget(self.RL_list[i])
self.add_widget(button)
self.add_widget(box)
def change_text(self, instance):
for lbl in self.labels_text:
if lbl.text[0] == '5':
lbl.text = 'Text changed'
class MainApp(App):
def build(self):
return MainMenu()
if __name__ == '__main__':
MainApp().run()
It's supposed to make a button to the left, and a list of 8 coloured labels to the right.
The problem is that you are setting size_hint=self.size in each Label. The self.size is the size of the MainMenu, which is [100,100] when that code is executed. Note that size_hint is a multiplier that is applied to the parents size to calculate the widgets size. So a size_hint of [100,100] makes each Label 100 times bigger than the MainMenu. So your code is working, but the Labels are so large that the text is off the screen. Start by just removing size_hint=self.size.
And, to set a background color on a Label, you can just use the canvas of that Label, rather than some container. Here is a version of your code that does that:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
class ColorLabel(Label):
pass
Builder.load_string('''
<ColorLabel>:
bg_color: [.7, 0, .5, 1]
canvas.before:
Color:
rgba: self.bg_color
Rectangle:
pos: self.pos
size: self.size
''')
class MainMenu(BoxLayout):
N_LBLS = 8
labels_text = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text='do something')
button.bind(on_release=self.change_text)
box = BoxLayout(orientation='vertical', padding=10, spacing=15)
for i in range(0, self.N_LBLS):
self.labels_text.append(ColorLabel(text=f'{i}º label'))
box.add_widget(self.labels_text[i])
self.add_widget(button)
self.add_widget(box)
def change_text(self, instance):
for lbl in self.labels_text:
if lbl.text[0] == '5':
lbl.text = 'Text changed'
lbl.bg_color = [0, 1, 0, 1]
class MainApp(App):
def build(self):
return MainMenu()
if __name__ == '__main__':
MainApp().run()
It seems a lot of people have trouble with the on_press argument with Kivy, but I haven't found answers to my issue...
Here is what's happening:
I'm getting started with my first app in python/kivy. I know python, but perhaps not enough concerning classes... I am able to create a button, with a on_press action that opens a popup.
Now the goal is the following: I have a function affiche_grille, which displays a grid with lines on the screen. Inside each square, I create a button, with a text (a number). This works. I would like to bind an on_press event on these buttons : but now, the syntax does not work any more... Maybe is it because the button is created in a "with self.canvas" instruction, and the self.function is not appropriate any more ?
Here is the (complete after edit) code :
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.popup import Popup
from kivy.graphics import Color, Line
from kivy.core.window import Window
from kivy.core.text.text_layout import layout_text
from kivy.uix.floatlayout import FloatLayout
import numpy as np
from functools import partial # for on_press syntax
class Ecran_Principal(BoxLayout):
def build(self):
self.orientation='vertical'
self.liste_txt = np.zeros([9,9], dtype=object) # will contain Label
self.grille_affichee = np.zeros([9,9])
self.lGrille() # layout for the grid
self.lMenu() # layout for the menu
def lGrille(self):
LayGrille = GridLayout(cols=1,size_hint_y=0.8)
# window dimensions, in pixels
(L, H) = Window.size
H = int(0.8*H) # because of the 20% menu
if L > H: # landscape format - computer case
self.ymin = int(0.25*H) + int(0.05*H)
self.delta = int(0.1*H)
self.xmin = int((L-9*self.delta)/2.)
else: # portrait format - phone case
self.xmin = int(0.05*L)
self.delta = int(0.1*L)
self.ymin = int(0.25*H) + int((H-9*self.delta)/2.)
# end dimensions
self.deltaxrel = self.delta/H
self.deltayrel = self.delta/L
# grid definition (without numbers)
with self.canvas:
Color(1, 1, 1) # white
# automatic line traces
for i in range(4):
# big vertical lines
ymax = self.ymin+9*self.delta
xligne = self.xmin+i*3*self.delta
Line(points=[xligne, self.ymin, xligne, ymax], width=2)
# big horizontal lines
xmax = self.xmin+9*self.delta
yligne = self.ymin+i*3*self.delta
Line(points=[self.xmin, yligne, xmax, yligne], width=2)
# little intermediary lines
for ipetit in range(3):
if i ==3:
break
xpetit = xligne + ipetit*self.delta
Line(points=[xpetit, self.ymin, xpetit, ymax], width=1)
ypetit = yligne + ipetit*self.delta
Line(points=[self.xmin, ypetit, xmax, ypetit], width=1)
# end little lines
# end for
# grid display :
self.affiche_grille()
self.add_widget(LayGrille)
# end with
def affiche_grille(self):
# I tried to remove this 'with' instruction and does not change anything
with self.canvas:
for i in range(9): # line
for j in range(9): # colomn
valeur = self.grille_affichee[i,j]
val = "{0:.0f}".format(valeur)
layout = FloatLayout(size=(self.xmin + (j+0.5)*self.delta,
self.ymin + (8.5-i)*self.delta),
pos_hint=(None, None))
montexte = Button(text=val,
size_hint=(self.deltaxrel,
self.deltayrel),
pos=(self.xmin + (j+0.5)*self.delta,
self.ymin + (8.5-i)*self.delta),
background_color = (0,0.2,0,1),
background_normal = '',
on_press=partial(self.choisir_valeur, i, j)
)
self.liste_txt[i, j] = montexte
# THE BUTTONS AND THE TEXT ARE DISPLAYED,
# BUT NOTHING HAPPENS WHEN YOU PRESS THE BUTTONS
layout.add_widget(self.liste_txt[i, j])
# end j
# end i
# end with
def choisir_valeur(self, i, j):
print("Hello !") # NEVER DISPLAYED :(
#champ = TextInput(text=str(self.grille_affichee[i, j]),
# font_size=30,
# focus=True,
# multiline=False)
champ = Button(text=str(self.grille_affichee[i, j]))
popup = Popup(title='Value in line {} - colomn {}'.format(i, j),
content=champ,
size_hint=(0.5,0.5))
champ.bind(on_press=popup.dismiss)
popup.open()
def lMenu(self):
LayMenu = GridLayout(cols=2, rows=2, size_hint_y=0.2)
# Bouton Résoudre
self.BoutonResoudre=Button(text='Resoudre',size_hint=(0.5,0.1),pos_hint={'left': 0.},background_color=[0.9,0.9,0.9,1])
self.BoutonResoudre.bind(on_press=self.resoudre)
LayMenu.add_widget(self.BoutonResoudre)
# Bouton Remplir
self.BoutonScanner=Button(text='Scanner',size_hint=(0.5,0.1),pos_hint={'left': 0.5},background_color=[0.9,0.9,0.9,1])
self.BoutonScanner.bind(on_press=self.scanner)
LayMenu.add_widget(self.BoutonScanner)
# Bouton Indice
self.BoutonIndice=Button(text='Indice',size_hint=(0.5,0.1),pos_hint={'bottom': 0.},background_color=[0.9,0.9,0.9,1])
self.BoutonIndice.bind(on_press=self.indice)
LayMenu.add_widget(self.BoutonIndice)
# Bouton Quitter
self.BoutonQuitter=Button(text='Quitter',size_hint=(0.5,0.1),background_color=[0.9,0.9,0.9,1])
self.BoutonQuitter.bind(on_press=self.quitter)
LayMenu.add_widget(self.BoutonQuitter)
self.add_widget(LayMenu)
def resoudre(self, instance):
content = Button(text='Resolution du sudoku', font_size=20)
popup = Popup(title='RESOLUTION',content=content, size_hint=(0.5,0.5))
content.bind(on_press=popup.dismiss)
popup.open()
def scanner(self, instance):
content = Button(text='Remplissage auto par photo', font_size=20)
popup = Popup(title='SCAN PHOTO',content=content, size_hint=(0.5,0.5))
content.bind(on_press=popup.dismiss)
popup.open()
def indice(self, instance):
content = Button(text='Affichage d\'un indice', font_size=20)
popup = Popup(title='INDICE',content=content, size_hint=(0.5,0.5))
content.bind(on_press=popup.dismiss)
popup.open()
def quitter(self, instance):
content = Button(text='Au revoir !', font_size=20)
popup = Popup(title='QUITTER',content=content, size_hint=(0.5,0.5))
content.bind(on_press=exit())
popup.open()
class Sudoku(App):
def build(self):
ecran=Ecran_Principal()
ecran.build()
return ecran
if __name__ == '__main__':
Sudoku().run()
Everything is interpreted, but the buttons inside the grid are not working...
I've seen the functools.partial() tip, but it does not seem to be enough...
Does anyone have an idea of what is happening ? I am not very familiar with classes in python and I have certainly missed something.
Thank you in advance, and sorry if the question is too basic.
Well, you now know that you can't add a widget to canvas. Also, you shouldn't have a build method in your Ecran_Principal class. build() only belongs in the Sudoku() App class. Use __init__ instead.
I think if you try separating the visual stuff into kv language things will be easier. Below is an example utilizing spacing and padding with GridLayouts to 'draw' the game board. The buttons are hooked up with a simple callback.
btngrid.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.button import Button
class SmallGrid(GridLayout):
pass
class BigGrid(GridLayout):
pass
class GameBoard(AnchorLayout):
# A nice layout to use to keep things centered. Only one widget can be added to this.
def __init__(self, *args, **kwargs):
super(GameBoard, self).__init__(*args, **kwargs)
big = BigGrid()
for i in range(9):
small = SmallGrid()
for j in range(9):
small.add_widget(Button(text="{}, {}".format(i, j), on_release=self.callback))
big.add_widget(small)
self.add_widget(big)
def callback(self, button):
print button.text
class RootWidget(BoxLayout):
def __init__(self, *args, **kwargs):
super(RootWidget, self).__init__(*args, **kwargs)
self.orientation = 'vertical'
self.add_widget(GameBoard())
bottom_btns_container = GridLayout(cols=2, size_hint=(1, .2))
for i in range(4):
# Just for show, they don't do anything
bottom_btns_container.add_widget(Button(text="Button {}".format(i)))
self.add_widget(bottom_btns_container)
class BtnGridApp(App):
def build(self):
return RootWidget()
btngird.kv
<BigGrid>:
cols: 3
size_hint: (None, .8) # scales
width: self.height # scales
spacing: 5 # Width of lines
padding: 5 # perimeter border
# This draws a background for the whole grid.
# When used with spacing and padding, part of the background will show.
# Same with SmallGrid below
canvas.before:
Color:
rgba: [.9, .9, .9, 1]
Rectangle:
pos: self.pos
size: self.size
<SmallGrid>:
cols: 3
size_hint: (None, .8) # scales
width: self.height # scales
spacing: .25
canvas.before:
Color:
rgba: [.6, .6, .6, 1] # White lines
Rectangle:
pos: self.pos
size: self.size
<GameBoard>:
anchor_x: "center"
anchor_y: "center"
I'm trying to add the MDFloatingActionButton widget after clicking on button but I'm not getting it.
Someone could help me to solve this problem.
The goal is to create a list of buttons after clicking the FAB with the icon plus.
I tried adding code to add_widget() in a number of ways but none worked.
fab.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty
from kivymd.theming import ThemeManager
from kivymd.time_picker import MDTimePicker
from kivymd.button import MDFloatingActionButton
from kivy.animation import Animation
from kivy.core.window import Window
Window.clearcolor = (1, 1, 1, 1)
class MDFloatingActionButtonList(MDFloatingActionButton):
angle = NumericProperty(0)
def on_touch_up(self, touch):
if self.collide_point(*touch.pos):
if self.angle == 0:
self.angle += 45
#MDFloatingActionButton.add_widget()
else:
self.angle -= 45
class Fab(App):
theme_cls = ThemeManager()
def build(self):
return MDFloatingActionButtonList()
Fab().run()
fab.kv
<MDFloatingActionButtonList>:
canvas.before:
PushMatrix
Rotate:
angle: self.angle
axis: (0, 0, 1)
origin: self.center
canvas.after:
PopMatrix
MDFloatingActionButton:
id: float_act_btn
icon: 'plus'
opposite_colors: True
elevation_normal: 8
pos_hint: {'center_x': 0.5, 'center_y': 0.2}
Result:
Goal for example:
Oh boy, this is a tough one. The KivyMD project is poorly documented, even though the design is so pretty.
Ok, here is one example of how it might look:
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivymd.button import MDFloatingActionButton
from kivymd.menu import MDDropdownMenu
from kivymd.theming import ThemeManager
Window.clearcolor = (1, 1, 1, 1)
menu_items = [
{'viewclass': 'MDFloatingActionButton',
'text': 'Example',
'on_press': lambda: print("Hello")},
{'viewclass': 'MDFloatingActionButton',
'text': 'Example'},
{'viewclass': 'MDFloatingActionButton',
'text': 'Example'},
{'viewclass': 'MDFloatingActionButton',
'text': 'Example item'},
{'viewclass': 'MDFloatingActionButton',
'text': 'Example'},
{'viewclass': 'MDFloatingActionButton',
'text': 'Example'},
{'viewclass': 'MDFloatingActionButton',
'text': 'Example'},
]
class Fab(App):
theme_cls = ThemeManager()
layout = BoxLayout()
md = MDDropdownMenu(items=menu_items)
def build(self):
button = MDFloatingActionButton()
self.layout.add_widget(button)
button.bind(on_press=lambda x: self.md.open(button))
return self.layout
Fab().run()
Another way is to manually add the buttons to the window. But then you will have to handle the dissmiss (I did not implement it):
from kivy.app import App
from kivy.core.window import Window
from kivymd.button import MDFloatingActionButton
from kivymd.theming import ThemeManager
Window.clearcolor = (1, 1, 1, 1)
class Fab(App):
theme_cls = ThemeManager()
button = None
def build(self):
self.button = MDFloatingActionButton()
self.button.bind(on_press=lambda x: self.open_menu(self.button))
return self.button
def open_menu(self, instance):
x, y = instance.to_window(instance.x, instance.center_y)
for i in range(1, 5):
Window.add_widget(MDFloatingActionButton(center_y=y+100*i, x=x))
Fab().run()
Why don't you try this?
MDFloatingActionButtonSpeedDial
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
Screen:
MDFloatingActionButtonSpeedDial:
data: app.data
root_button_anim: True
'''
class Example(MDApp):
data = {
'language-python': 'Python',
'language-php': 'PHP',
'language-cpp': 'C++',
}
def build(self):
return Builder.load_string(KV)
Example().run()
Here is the output:
I am trying to create a breakout game using Python and Kivy. I have tried displaying other kivy features like labels and buttons and they all work perfectly. However, when I try to run the below code I keep getting a blank screen. Any help would be appreciated.
BreakoutApp.py
from kivy.app import App # App is base for any kivy app
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.modalview import ModalView
from kivy.properties import (ListProperty, NumericProperty, ObjectProperty, StringProperty)
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.uix.label import Label
from kivy.uix.scatter import Scatter
import random
__version__ = '0.1' # used in Android compilation
#Game is a big widget that will contain the entire game
# A subclass of Float layout as it will proportion its children in proportion to its own pos and size
class Game(FloatLayout): # Will contain everything
blocks = ListProperty([])
player = ObjectProperty() # The game's player instance
ball = ObjectProperty() # The game's ball instance
def setup_blocks(self):
for y_jump in range(5):
for x_jump in range(10):
print "in setup blocks"
block = Block(pos_hint={
'x': 0.05 + 0.09*x_jump,
'y': 0.05 + 0.09*y_jump})
self.blocks.append(block)
self.add_widget(block)
# App will initialise everything that kivy needs
class BreakoutApp(App):
def build(self):
print "in build self blocks"
g = Game()
g.setup_blocks()
return g
#f = FloatLayout()
#s = Scatter()
#l = Label(text="Hello!",
# font_size=150)
#f.add_widget(s)
#s.add_widget(l)
#return f
#Require a class for each game object
#kivy properties are special attributes declared at class level
class Player(Widget): # A moving paddle
position = NumericProperty(0.5)
direction = StringProperty("none")
def __init__(self, **kwargs):
super(Player, self).__init__(**kwargs)
with self.canvas:
Color(1, 0, 0, 1)
Rectangle(pos=self.pos, size=self.size)
class Ball(Widget): # A bouncing ball
# pos_hints are for proportional positioning, see below
pos_hint_x = NumericProperty(0.5)
pos_hint_y = NumericProperty(0.3)
proper_size = NumericProperty(0.)
velocity = ListProperty([0.1,0.5])
class Block(Widget): # Each coloured block to destroy
colour = ListProperty([1, 0, 0])
def __init__(self, **kwargs):
super(Block, self).__init__(**kwargs)
self.colour = random.choice([
(0.78,0.28,0), (0.28,0.63,0.28), (0.25,0.28,0.78)])
if __name__ == "__main__":
print "main method called"
BreakoutApp().run()
Breakout.kv
#:kivy 1.9.1
<Player>:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
<Ball>:
canvas:
Color:
rgb: 1, 0.55, 0
Rectangle:
pos: self.pos
size: self.size
<Block>:
canvas:
Color:
rgb: self.color #property defined above
Rectangle:
pos: self.pos
size: self.size
Color:
rgb: 0.1, 0.1, 0.1
Line:
rectangle:
[self.x, self.y, self.width, self.height]
Also, do I need to explicitly refer to the layout .kv file in the python file or are there any specific naming restrictions. I found some documentation online to name the two as found below.
You have in kv file color
<Block>:
canvas:
Color:
rgb: self.color #property defined above
yet in py file there is colour (with U):
class Block(Widget): # Each coloured block to destroy
colour = ListProperty([1, 0, 0])
If you change it, you'll get the desired output I believe:
<Block>:
canvas:
Color:
rgb: self.colour #property defined above
Proof:
Summary of test application: I am writing a Kivy app with a scrollable view (named Scroller) with many fields (named Field) to look at. These separate fields are really difficult to distinguish on occasion, so I decided to use alternating background colors for each field to help distinguish each other. My testing application uses 20 individual fields each of which alternates between dark grey and darker grey.
Testing trials:
Starting the application, the program looks great. The alternating background appear just fine. Even when I scroll down the application looks fine. However, the application seems to get bizarre when I scroll up on the application. The text scrolls with the application, but the background does not. Even better (sarcastically), the text starts to fade away into their neighbors background. The problem just seems to vanish when I scroll down again (passed the point of the furthest scroll up point).
Brief problem description: The Field's "background color" messes up the application during scrolling up events.
Side note: I have also noticed that the application got a little sluggish after scrolling too much. I am not that familiar with the drawing cycle of Kivy, but blitting backgrounds should not yield an excessive slowdown.
Testing application:
import kivy
kivy.require('1.0.7')
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.graphics import Color, Rectangle
class Main(App):
def build(self):
self.root = GridLayout(rows = 1)
self.root.add_widget(Scroller())
return self.root
class Scroller(ScrollView):
def __init__(self):
ScrollView.__init__(self)
self.view = GridLayout(cols = 1, size_hint = (1, None))
self.add_widget(self.view)
self.view.bind(minimum_height = self.view.setter('height'))
for i in range(20):
self.view.add_widget(Field('Test field {}'.format(i),i%2 is 0))
class Field(GridLayout):
def __init__(self, name, bg):
assert isinstance(name, str)
assert isinstance(bg, bool)
self.bg = bg
GridLayout.__init__(self,
rows = 1,
padding = 10,
size = (0, 60),
size_hint = (1, None))
self.add_widget(Label(text = name))
self.add_widget(Button(text = 'Test button',
size = (200, 0),
size_hint = (None, 1)))
self.bind(pos = self.change_background)
self.bind(size = self.change_background)
def change_background(self, *args):
with self.canvas.before:
if self.bg:
Color(0.2, 0.2, 0.2, mode = 'rgb')
else:
Color(0.1, 0.1, 0.1, mode = 'rgb')
Rectangle(pos = self.pos, size = self.size)
if __name__ in ('__main__', '__android__'):
app = Main()
app.run()
def change_background(self, *args):
self.canvas.before.clear()#<- clear previous instructions
with self.canvas.before:
if self.bg:
Color(0.2, 0.2, 0.2, mode = 'rgb')
else:
Color(0.1, 0.1, 0.1, mode = 'rgb')
Rectangle(pos = self.pos, size = self.size)
You are adding/piling instructions to the canvas every time the Field's position/size changes, without clearing the previous instructions.
You should also look into using kv as for anything more than a small snippet it ends up saving you a lot of time. You can convert you code using kv like so ::
import kivy
kivy.require('1.0.7')
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.properties import ObjectProperty, BooleanProperty
from kivy.lang import Builder
Builder.load_string('''
<Scroller>
# root is Scroller here
# create a new ObjectProperty in kv that holds the ref to Gridlayout
# so you can access the instance in python code
view: glayout
GridLayout:
id: glayout
cols: 1
size_hint: (1, None)
height: self.minimum_height
<Field>
canvas.before:
Color:
rgba: (0.2, 0.2, 0.2, 1) if self.bg else (0.1, 0.1, 0.1, 1)
Rectangle:
# binding properties is done implicitly and instructions aren't
# piled up while doing that.
pos: self.pos
# self here refers to Field as `self` is supposed to refer to the
# Widget not the drawing instruction
size: self.size
rows: 1
padding: 10
size: (0, 60)
size_hint: (1, None)
Label:
text: root.name
Button:
text: 'test button'
size: (200, 0)
size_hint: (None, 1)
''')
class Main(App):
def build(self):
self.root = GridLayout(rows = 1)
self.root.add_widget(Scroller())
return self.root
class Scroller(ScrollView):
def __init__(self, **kwargs):
super(Scroller, self).__init__(**kwargs)
for i in range(20):
# access self.view that was set in kv
self.view.add_widget(
Field(
name = 'Test field {}'.format(i),
bg = i%2 is 0))
class Field(GridLayout):
# use kivy's Properties so it becomes easier to observe and apply changes
# as a plus these can also be directly used in kv. As a advantage of using this now
# you can change name and bg dynamically and the changes should be reflected on
# screen
name = ObjectProperty('Test field uninitialized')
bg = BooleanProperty(False)
if __name__ in ('__main__', '__android__'):
app = Main()
app.run()