I'm trying to make something like a "Bing images" layout.
That is:
Images are divided into several columns of the same width.
All images are the same width.
Images are added down in such a way that the images added first are
at the top of the layout.
Layout can be added to ScrollView to scroll with mousewheel
I did not find a way to do this using Stack Layout, so I decided to create my own layout.
I stopped here:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.properties import NumericProperty
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
KV = '''
#:import Window kivy.core.window.Window
ScrollView
size_hint: (1, None)
size: Window.size
MyLayout
id:my_l
Button
text:'1'
Button
size_hint_y: None
height: 900
text:'2'
Button
text:'3'
Button
text:'4'
Button
text:'5'
size_hint_y: None
height: 900
<MyLayout>:
#height: self.minimum_height
cols: 3
spacing: 10
size_hint_y:None
row_width: 300
'''
class MyLayout(FloatLayout):
def __init__(self, **kwargs):
super(MyLayout, self).__init__(**kwargs)
cols = NumericProperty(3)
row_width = NumericProperty(300)
spacing = NumericProperty(0)
def do_layout(self, *args):
self.i = 0
self.last_x = [self.height]*self.cols
for child in self.children[::-1]:
child.width = self.row_width
if isinstance(child, Image):
child.height = child.width / child.image_ratio
child.size_hint_y= None
child.size_hint_x= None
self.i+=1
if self.i == self.cols+1: self.i = 1
child.x = self.x+(self.i-1)*(self.row_width+self.spacing)
child.y = self.last_x[self.i-1]-child.height
self.last_x[self.i-1]-=child.height+self.spacing
def on_pos(self, *args):
self.do_layout()
def on_size(self, *args):
self.do_layout()
def add_widget(self, widget):
super(SuperGrid, self).add_widget(widget)
self.do_layout()
def remove_widget(self, widget):
super(SuperGrid, self).remove_widget(widget)
self.do_layout()
class MyApp(App):
def build(self):
self.root = Builder.load_string(KV)
Window.bind(on_dropfile=self.add)
def add(self, *args):
name= list(args)[1]
self.root.ids.my_l.add_widget(Image(source=name))
MyApp().run()
It is already partially working (you can run it and dragndrop some images from your folders to see what I'm about), but the problem is that I don't understand how to connect a ScrollView to it.
It looks like I need to add a line with something like height: self.minimum_height to KV string.
but it’s not clear where in layout class I need to calculate minimum_height.
How to make the code work with ScrollView?
You just need to calculate the height of your MyLayout instance. In your kv file add:
size_hint: (1, None)
in your MyLayout section
Then, in the do_layout method, calculate the height of your MyLayout. Do a self.height = just once at the end do_layout (to avoid infinite loop due to on_size method). For example here is a modified version of your do_layout:
def do_layout(self, *args):
self.i = 0
col_heights = [0] * self.cols # keeps track of the height of each column
self.last_x = [self.height]*self.cols
for child in self.children[::-1]:
child.width = self.row_width
if isinstance(child, Image):
child.height = child.width / child.image_ratio
child.size_hint_y= None
child.size_hint_x= None
self.i+=1
if self.i == self.cols+1:
self.i = 1
col_heights[self.i-1] += child.height + self.spacing
child.x = self.x+(self.i-1)*(self.row_width+self.spacing)
child.y = self.last_x[self.i-1]-child.height
self.last_x[self.i-1]-=child.height+self.spacing
if len(self.children) > 0:
self.height = max(col_heights)
Related
How do I increase the width of the dropdown list within a spinner? My button as you can see in the photo is small, and the values in my drop-down list do not appear completely. I did a little research on the internet and I could see that some people say that I need to create a class spinner and add these features. But I don't know how to do this. Could someone show me a code example of how I do this?
main.kv (simplified code)
...
Spinner:
id: spinnerrpi
size_hint: None, None
width: '30sp'
height: '30sp'
border: 0,0,0,0
background_normal: 'seta1.png'
background_down: 'seta2.png'
values: "Branco Neve","Banco Gelo","Amarelo","Rosa Claro","Bege"
on_text: app.spinner_rpiso(spinnerrpi.text)
...
main.py (simplified code)
...
class PrimeiraJanela(Screen):
pass
class GerenciadorDeJanelas(ScreenManager):
pass
class MainApp(App):
texture = ObjectProperty()
def build(self):
self.title = 'MyApp'
self.texture = Image(source = 'wave.png').texture
sm = ScreenManager()
sm.add_widget(PrimeiraJanela(name = 'primeira'))
sm.current = 'primeira'
return sm
def spinner_rpiso(self, value):
if (value=='Branco Neve'):
self.root.get_screen('primeira').ids.rpi.text = str('0.90')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Banco Gelo'):
self.root.get_screen('primeira').ids.rpi.text = str('0.70')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Amarelo'):
self.root.get_screen('primeira').ids.rpi.text = str('0.70')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Rosa Claro'):
self.root.get_screen('primeira').ids.rpi.text = str('0.60')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Bege'):
self.root.get_screen('primeira').ids.rpi.text = str('0.60')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
def exit(self):
App.get_running_app().stop()
aplicativo = MainApp()
aplicativo.run()
Here is an extension of Spinner that honors an option_width property:
class SpinnerWithOptionWidth(Spinner):
option_width = NumericProperty(0) # the new property
def __init__(self, **kwargs):
self.invisible_attacher = None
super(SpinnerWithOptionWidth, self).__init__(**kwargs)
def on_is_open(self, instance, value):
# This method is modified from Spinner
attacher = self
if value:
if self.option_width > 0:
if self.invisible_attacher is None:
# The DropDown is the same width as the widget it attaches to
# so make an invisible widget with the desired width
self.invisible_attacher = Widget(opacity=0, size_hint=(None, None))
self.add_widget(self.invisible_attacher)
self.invisible_attacher.pos = (self.center_x - self.option_width/2, self.y)
self.invisible_attacher.size = (self.option_width, self.height)
attacher = self.invisible_attacher
# open th DropDown
self._dropdown.open(attacher)
else:
if self._dropdown.attach_to:
if self.invisible_attacher:
self.remove_widget(self.invisible_attacher)
self.invisible_attacher = None
self._dropdown.dismiss()
I have added GridLayout to ScrollView, and I'm adding widgets in GridLayout dynamically from python program.Instead of using more space of window it's resizing height of older widgets.What am i doing wrong here?
I tried putting BoxLayout inside GridLayout but it's not working.
I also tried to add widgets directly to ScrollView but i found out ScrollView only supports one widget.
My kv code:
<Downloading>:
my_grid: mygrid
GridLayout:
cols: 1
size_hint_y : None
hight: self.minimum_height
id: mygrid
My python code:
class Downloading(ScrollView):
set_text = ObjectProperty()
my_grid = ObjectProperty()
def __init__(self, select, link, path, username, password):
self.select = select
self.link = link
self.path = path
self.username = username
self.password = password
self.p_bar = []
self.stat = []
self.parent_conn, self.child_conn = Pipe()
p = Process(target=main, args=(self.child_conn, self.select,
self.link, self.path,
self.username, self.password))
p.start()
super().__init__()
self.event = Clock.schedule_interval(self.download_GUI, 0.1)
def newFile(self, title):
# self.newId = "stat" + str(len(self.p_bar) + 1)
self.stat.append(Label(text=''))
self.p_bar.append(ProgressBar())
self.my_grid.add_widget(Label(text=title))
self.my_grid.add_widget(self.stat[-1])
self.my_grid.add_widget(self.p_bar[-1])
def download_GUI(self, a):
temp = self.parent_conn.recv()
print(temp)
if temp == "new":
self.downloading = True
return
if self.downloading:
self.newFile(temp)
self.downloading = False
return
if type(temp) == type({}):
self.complete = temp['complete']
if not self.complete:
status = "{0}//{1} # {2} ETA: {3}".format(temp['dl_size'],
temp['total_size'],temp['speed'],temp['eta'])
self.stat[-1].text = status
self.p_bar[-1].value = temp['progress']
return
if temp == "end":
self.event.cancel()
Clock.schedule_once(exit, 3)
I believe you just need to set the height for each widget that you add to your GridLayout. For example:
self.my_grid.add_widget(Label(text=title, size_hint=(1, None), height=50))
You might need to do the same for the other widgets you are adding. The GridLayout may give the initially added widgets more space than that, but will not squeeze them any any tighter than your specified height.
After applying following changes to kv file
<Downloading>:
size: self.size
my_grid: mygrid
GridLayout:
cols: 1
size_hint_y : None
row_default_height: '25dp'
row_force_default: True
id: mygrid
And adding this line in the end of constructor (__init__), it is working as expected.
self.my_grid.bind(minimum_height=self.my_grid.setter('height'))
I use Kivy's recycleview to show a list of data in a table like manner. I used the example from the docs as a base for my implementation.
In my program the RecycleDataView is based on a BoxLayout and its child widgets are generated dynamically.
This seems to work but the order in which the items are displayed is sometimes reversed and keeps changing if your resize the window. Even worse, if you scroll down, the layout gets completely crazy. This does not happen if I would use a simple label as item class, so I guess the problem is with my dynamicaly created widget logic but I don't understand why.
Here is some minimal code that shows the issue.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
Builder.load_string('''
<RV>:
viewclass: 'RVItem'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
''')
class Attribute:
def __init__(self, name, values):
self.name = name
self.values = values
class RVItem(RecycleDataViewBehavior, BoxLayout):
index = None
attribute = ObjectProperty()
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
self.create_widgets(data.pop('attribute', None))
return super(RVItem, self).refresh_view_attrs(
rv, index, data)
def create_widgets(self, value: Attribute):
"""Dynamically create the needed Widgets"""
if value is None:
return
self.add_widget(Label(text=value.name, height=self.height, size_hint=(1, None)))
if not isinstance(value.values, dict):
self.add_widget(Label(text=value.values, height=self.height, size_hint=(1, None)))
else:
for _, v in value.values.items():
self.add_widget(Label(text=v, height=self.height, size_hint=(1, None)))
image_button = Button(text='+')
#image_button.source = 'wm_ui/glyphs/plus.png'
image_button.size_hint = None, None
image_button.size = "30sp", "30sp"
image_button.bind(on_press=self.add_button_pressed)
self.add_widget(image_button)
def add_button_pressed(self, s):
print("Would add a new item to the recycleview if implemented.")
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'attribute': Attribute(str(x), "test")} for x in range(100)]
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
I changed some Widgets to their base classes for simplicity (like ImageButton and Label)
When you run the application you should see that the order if the items is reversed and starting with 10 instead of 100 for some reason.
After resizing the window with the mouse in one of the window's corners you should see the contents continuously reordering.
And if you scroll down, things get even more crazy.
Unfortunately I have no idea what causes the behavior. I developed some Kivy apps before but this is my first really deep dive that uses more than just labels and a few inputs.
Here is a modification of your code that seems to work correctly:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
Builder.load_string('''
<RV>:
viewclass: 'RVItem'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
''')
class Attribute:
def __init__(self, name, values):
self.name = name
self.values = values
class RVItem(RecycleDataViewBehavior, BoxLayout):
index = None
attribute = ObjectProperty()
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
self.create_widgets(data['attribute'])
return super(RVItem, self).refresh_view_attrs(
rv, index, data)
def create_widgets(self, value: Attribute):
rv = App.get_running_app().root
rv.cache_widgets(self.children)
self.clear_widgets()
label = rv.get_label()
label.text = value.name
self.add_widget(label)
if isinstance(value.values, dict):
for _,v in value.values.items():
label = rv.get_label()
label.text = v
self.add_widget(label)
else:
label = rv.get_label()
label.text = value.values
self.add_widget(label)
image_button = rv.get_button()
image_button.text = '+'
image_button.size_hint = None, None
image_button.size = "30sp", "30sp"
image_button.bind(on_press=self.add_button_pressed)
self.add_widget(image_button)
def add_button_pressed(self, s):
print("Would add a new item to the recycleview if implemented.")
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.label_cache = []
self.button_cache = []
self.data = [{'attribute': Attribute(str(x), "test")} for x in range(100)]
for i in range(100):
if i % 5 == 0:
self.data[i]['attribute'].values = {'1': 'test1', '2': 'test2', '3': 'test3'}
def get_button(self):
if len(self.button_cache) > 0:
return self.button_cache.pop()
else:
return Button()
def get_label(self):
if len(self.label_cache) > 0:
return self.label_cache.pop()
else:
return Label()
def cache_widgets(self, widgets):
for w in widgets:
if isinstance(w, Button):
self.button_cache.append(w)
else:
self.label_cache.append(w)
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
In this version, the refresh_view_attrs method always sets all the attributes of the RVItem. The line
self.create_widgets(data.pop('attribute', None))
is replaced with
self.create_widgets(data['attribute'])
because the pop() actually removes data, which I don't think you want to do.
The RV class now has a cache for Label widgets and another for Button widgets, and they get recycled (similar to what the RecycleView does. The create_widgets method removes all the children of the RVItem and adds them to the cache, then recycles or creates widgets, as needed, to fill out the RVItem.
I have added additional items to the values dict for some of the data, to help illustrate how this works.
How do you update a progress bar that is displayed in Kivy. In the following example I get AttributeError: 'super' object has no attribute '__getattr__'. The issue is in the following line
self.ids.progress.value = value
I can see why since the progressBar widget is in <LoadingPopup> and not <MainScreen> but after trying several different things I can't reference the progressBar widget in <LoadingPopup> from the do_update method.
Thanks in advance
import threading
from functools import partial
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty
from kivy.uix.spinner import Spinner
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.popup import Popup
Builder.load_string('''
<LoadingPopup>:
title: "Popup"
size_hint: None, None
size: 400, 400
auto_dismiss: False
BoxLayout:
orientation: "vertical"
ProgressBar:
id: progress
size_hint: (1.0, 0.06)
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Spinner:
id: first
text: ' First Number'
values: ['1','2','3','4','5','6','7','8','9']
Spinner:
id: second
text: ' Second Number'
values: ['1','2','3','4','5','6','7','8','9']
Label:
id: result
text: ' Result'
color: 0,0,0,1
Button:
id: res
on_press: root.doit_in_thread(first.text,second.text)
text: 'Multiply'
''')
class LoadingPopup(Popup):
def __init__(self, obj, **kwargs):
super(LoadingPopup, self).__init__(**kwargs)
class MainScreen(FloatLayout):
changet = StringProperty()
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
def doit_in_thread(self, fir, sec):
popup = LoadingPopup(self)
popup.open()
threading.Thread(target=partial(self.onMul, fir, sec, popup)).start()
def do_update(self, value, text, *args):
self.ids.progress.value = value
self.ids.result.text = text
def onMul(self,fir,sec, popup):
a = (int(fir)*int(sec))
print(a)
b = 0
old_value = 0
endRange = 1000000
for i in range(endRange):
progress = int(((i+1)*100)/endRange)
if progress != old_value and progress % 5 == 0:
text = str(b*(int(fir)*int(sec)))
Clock.schedule_once(partial(self.do_update, progress, text))
old_value = progress
b+=1
popup.dismiss()
class TestApp(App):
def build(self):
return MainScreen()
if __name__ == "__main__":
TestApp().run()
One way to do it, is to keep a reference of the popup so you can address it later:
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
self.popup = LoadingPopup(self)
def doit_in_thread(self, fir, sec):
self.popup.open()
threading.Thread(target=partial(self.onMul, fir, sec, self.popup)).start()
def do_update(self, value, text, *args):
self.popup.ids.progress.value = value
self.ids.result.text = text
Python Code
Replace all references of popup with self.popup so that you can access it in the methods inside class MainScreen.
Replace self.ids.progress.value with self.popup.ids.progress.value
Since a = (int(fir)*int(sec), replace str(b*(int(fir)*int(sec))) with str(b * a)
Don't need to pass popup to onMul method, access it using self.popup
Snippet
class MainScreen(FloatLayout):
changet = StringProperty()
def doit_in_thread(self, fir, sec):
self.popup = LoadingPopup(self)
self.popup.open()
threading.Thread(target=partial(self.onMul, fir, sec)).start()
def do_update(self, value, text, *args):
self.popup.ids.progress.value = value
self.ids.result.text = text
def onMul(self, fir, sec):
a = (int(fir)*int(sec))
print(a)
b = 0
old_value = 0
endRange = 1000000
for i in range(endRange):
progress = int(((i+1)*100)/endRange)
if progress != old_value and progress % 5 == 0:
text = str(b*(int(fir)*int(sec)))
# text = str(b * a)
print("\ttext=", text)
Clock.schedule_once(partial(self.do_update, progress, text))
old_value = progress
b+=1
self.popup.dismiss()
kv file
The result is not visible because the default background colour of Label widget is black, and the text colour for result is also set to black, color: 0,0,0,1. Therefore, remove color: 0,0,0,1.
Snippet
Label:
id: result
text: ' Result'
Output
Please consider the following code:
Builder.load_string(
"""
<TestView>:
canvas.before:
Color:
rgba: 0, 1, 0, 1
Rectangle:
pos: self.pos
size: self.size
""")
class TestView(GridLayout):
def __init__(self, **kwargs):
super(TestView, self).__init__(**kwargs)
self.cols = 1
self.spacing = 20
self.add_widget(Label(text="I'm on the TestView", height=80 ))
class TestScreen(Screen):
def __init__(self, **kwargs):
super(TestScreen, self).__init__(**kwargs)
self.content_grid_layout = GridLayout(cols=1, spacing=20, size_hint_y=None)
self.content_grid_layout.bind(minimum_height=self.content_grid_layout.setter('height'))
self.content_grid_layout.add_widget(Label(text="A"))
self.content_grid_layout.add_widget(Label(text="B"))
self.content_grid_layout.add_widget(TestView())
self.scroll_view = ScrollView(size_hint=(1, 1), size=(self.width, self.height))
self.scroll_view.add_widget(self.content_grid_layout)
self.add_widget(self.scroll_view)
sm = ScreenManager()
sm.add_widget(TestScreen(name="test_screen"))
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()
(I've left out the imports to save space). So my specific problem here is that I expect the background of TestView to be painted green, but it isn't here. It would be if you just add TestView straight onto the Screen's layout, i.e. change the init of TestScreen to just do:
self.content_grid_layout = GridLayout(cols=1, spacing=20, size_hint_y=None)
self.content_grid_layout.add_widget(TestView())
self.add_widget(self.content_grid_layout)
However, I need my content to be on a ScrollView here. So I also note that if I just comment out this line:
self.content_grid_layout.bind(minimum_height=self.content_grid_layout.setter('height'))
from the original code, everything appears to work as expected and the background of the TestView is painted green. However, if I do that in turns out the ScrollView isn't doing what I expect (it doesn't scroll).
If I give the TestView an explicit height, then everything works as expected, e.g. add:
self.size_hint_y = None
self.height = 40
to the TestView init. So I guess the problem is something not really knowing what height to give the TestView, but then weirdly being able to put the Label in approximately the right place, but not paint the background, or paint it off the screen or something. Anyway, hard-coding the TestView doesn't work for me because IRL it will have some dynamic content that will have varying heights.
I feel like in a sane world the height of a layout would be the height of its contents, unless otherwise specified, but maybe that's just me. I think if I can size the TestView to its contents then I'd get the behaviour I'm expecting?
Edit:
OK, I think I can get it to do what I want by following the strategy described here: https://groups.google.com/d/msg/kivy-users/xRl2l8-1qLs/zzpk-QG4C0MJ
(in particular the CustomGridLayout described there which is like my TestView I guess).
So the basic idea it seems is that we set the custom GridLayout (TestView) to zero height initially, then manually update its height for each child we add, so something like:
class TestView(GridLayout):
def __init__(self, **kwargs):
super(TestView, self).__init__(**kwargs)
self.cols = 1
self.spacing = 20, 20
self.size_hint_y = None
self.height = 0
self.height = self.height + 30 + self.spacing[1]
self.add_widget(Label(text="I'm on the TestView 1", size_hint_y=None, height=30))
self.height = self.height + 30 + self.spacing[1]
self.add_widget(Label(text="I'm on the TestView 2", size_hint_y=None, height=30))
self.height = self.height + 30 + self.spacing[1]
self.add_widget(Label(text="I'm on the TestView 3", size_hint_y=None, height=30))
I'm not going to lie, I think this is pretty ugly. It really seems that the layout should be able to work out its new height when I add a widget to it without having to spoon feed it like this. Anyway, it seems to work. I'm going to leave the question open in case someone has a not-horrible way of doing this.
By commenting out the line you mentioned (self.content_grid_layout.bind(minimum_height=self.content_grid_layout.setter('height'))) and adding height: self.minimum_height to the Builder string I was able to get your TestView to be green. The TestScreen is also scrollable.
Consider changing the TestScreen initializer to this:
def __init__(self, **kwargs):
super(TestScreen, self).__init__(**kwargs)
self.content_grid_layout = GridLayout(cols=1, spacing=20, size_hint_y=None)
# self.content_grid_layout.bind(minimum_height=self.content_grid_layout.setter('height'))
self.content_grid_layout.add_widget(Label(text="A"))
self.content_grid_layout.add_widget(Label(text="B"))
self.content_grid_layout.add_widget(TestView())
self.scroll_view = ScrollView(size_hint=(1, 1), size=(self.width, self.height))
self.scroll_view.add_widget(self.content_grid_layout)
self.add_widget(self.scroll_view)
And the Builder string to this:
Builder.load_string(
"""
<TestView>:
height: self.minimum_height
canvas.before:
Color:
rgba: 0, 1, 0, 1
Rectangle:
pos: self.pos
size: self.size
""")
OK, I think I've cracked it - Edvardas' notion of binding the TestView's minimum height to its height is correct (and I guess maybe this should have been obvious to me since it's also what we do for the 'content_grid_layout'). However, it wasn't working because we also need 'size_hint_y: None'.
I don't think we should take out the height / minimum height binding from 'content_grid_layout' because that stops scrolling working.
Doing this it works as expected and we don't have to manually set the TestView's height. Here's a full working example to save any confusion - I've changed the TestView from a GridLayout to a BoxLayout but it works as a GridLayout too:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
Builder.load_string(
"""
<TestView>:
size_hint_y: None # <----------------- MAKE SURE YOU DO THIS!
height: self.minimum_height # <----------------- AND THIS!
canvas.before:
Color:
rgba: 0, 0.8, 0.06, 0.5
RoundedRectangle:
pos: self.pos
size: self.size
radius: [16,16,16,16]
""")
class TestView(BoxLayout):
def __init__(self, **kwargs):
super(TestView, self).__init__(**kwargs)
self.orientation = 'vertical'
self.padding = [20, 20, 20, 20]
self.spacing = 20
self.add_widget(Label(text="I'm on the TestView 1"))
self.add_widget(Label(text="I'm on the TestView 2"))
self.add_widget(Label(text="I'm on the TestView 3"))
class TestScreen(Screen):
def __init__(self, **kwargs):
super(TestScreen, self).__init__(**kwargs)
self.content_grid_layout = GridLayout(cols=1, spacing=20, size_hint_y=None)
self.content_grid_layout.bind(minimum_height=self.content_grid_layout.setter('height'))
# Give us something to scroll:
for i in range(20):
btn = Button(text=str(i), size_hint_y=None, height=40)
self.content_grid_layout.add_widget(btn)
if i == 5:
self.content_grid_layout.add_widget(TestView())
self.scroll_view = ScrollView(size_hint=(1, 1), size=(self.width, self.height))
self.scroll_view.add_widget(self.content_grid_layout)
self.add_widget(self.scroll_view)
sm = ScreenManager()
sm.add_widget(TestScreen(name="test_screen"))
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()