Recreating kivy app with scrollview to be used as screen with screenmanager - kivy

I've been trying to use scrollview in a screen, that will be used as part of an app and I managed to find the following code that creates an app with scrollview. But I'm not able to change it into screen class.
Here is the python code:
from kivy.app import App
from kivy.uix.button import Button
class ScrollButton(Button):
pass
class TestApp(App):
def build(self):
super(TestApp, self).build()
container = self.root.ids.container
for i in range(30):
container.add_widget(ScrollButton(text=str(i)))
return self.root
if __name__ == '__main__':
TestApp().run()
.kv file
ScreenManager:
Screen:
ScrollView:
size_hint: None, None
size: 600, 320
pos_hint: {'center_x': .5, 'center_y': .5}
GridLayout:
cols: 1
padding: 10
spacing: 10
height: self.minimum_height
size_hint: None, None
do_scroll_x: False
id: container
<ScrollButton>
size_hint: None, None
size: 600, 40
I would appreciate if anybody could show how to do it/give me some directions on how to change this to fit my purpose. Any alternative way of doing this is also welcome.

Many people recommend making the ScreenManager in the build method in the python file instead, here's how to do that:
Python
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang.builder import Builder
Builder.load_file("styling.kv")
class ScrollButton(Button):
pass
class MainScreen(Screen):
def on_kv_post(self, instance):
container = self.ids['container']
for i in range(30):
container.add_widget(ScrollButton(text=str(i)))
class TestApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(MainScreen())
return sm
if __name__ == '__main__':
TestApp().run()
Kivy
<ScrollButton>:
size_hint: None, None
size: 600, 40
<MainScreen>:
name: 'MainScreen'
ScrollView:
size_hint: None, None
size: 600, 320
pos_hint: {'center_x': .5, 'center_y': .5}
GridLayout:
cols: 1
padding: 10
spacing: 10
height: self.minimum_height
size_hint: None, None
do_scroll_x: False
id: container
Changes made
ScreenManager made in .py instead of .kv
Created Screen class in .py, so we can use python methods from the Screen
Widget adding moved to the Screen's class, using on_kv_post (this method is fired only one time when the Screen is ready)
Made the screen a template class instead of an object in .kv which the Screen in the python file uses to make the final Screen
PS: This is how it's usually done

Related

KivyMD, MDFloatingActionButton: correct placement

I've been trying to use floating action buttons alongside FileChooser:
import kivy
kivy.require("2.1.0")
from kivy.utils import platform
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
from kivymd.icon_definitions import md_icons
import os
USERPATH = os.path.expanduser("~")
if platform == "android":
from android.storage import primary_external_storage_path
from android.storage import secondary_external_storage_path
from android.permissions import request_permissions, Permission
USERPATH = primary_external_storage_path()
request_permissions(
[Permission.WRITE_EXTERNAL_STORAGE, Permission.READ_EXTERNAL_STORAGE]
)
KV = '''
#:kivy 2.1.0
#:set dark_gray (.5, .5, .5, 1)
MDBoxLayout:
orientation: "vertical"
MDTabs:
id: ps_tabs
TabList:
id: ps_tab_list
icon: "folder"
FileChooserListView:
id: ps_filechooser
canvas.before:
Color:
rgba: dark_gray
Rectangle:
size: self.size
pos: self.pos
MDFloatingActionButton:
id: fc_playdir
icon: "folder"
pos_hint: {"center_x": .4, "center_y": .5}
MDFloatingActionButton:
id: fc_playfile
icon: "file"
pos_hint: {"center_x": .6, "center_y": .5}
TabDetails:
id: ps_tab_details
icon: "book"
MDScrollView:
'''
class TabList(FloatLayout, MDTabsBase):
"""The engaged power supplies tab."""
class TabDetails(FloatLayout, MDTabsBase):
"""The engaged power supply details tab."""
class Ron(MDApp):
def build(self):
return Builder.load_string(KV)
def on_start(self):
self.theme_cls.primary_palette = "Gray"
self.theme_cls.material_style = "M3"
if platform != "android":
self.root.ids.ps_tabs.lock_swiping = True
self.root.ids.ps_filechooser.rootpath = USERPATH
if __name__ == "__main__":
Ron().run()
Mouse clicks, as well as taps, shoot right through the buttons and into the FileChooser, no matter what I do. I tried placing the buttons into their own box layout, tried moving them up and down the widget tree. Nothing helps, while the buttons remain visible. Besides, on visiting the second tab, one can see two thick black button footprints. Something's very wrong and I can't think of anything else to try.

Using builder in Kivy

I want to create a simple app that has GUI with a button that allows me to vibrate an android phone. Im using a .kv file for the layout and the Builder in my .yp file
.py file:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
from plyer import vibrator
class AndroidApp(GridLayout):
def vibrate(self):
vibrator.vibrate()
kv = Builder.load_file("android_app.kv")
class MainApp(App):
def build(self):
return kv
if __name__ == "__main__":
MainApp().run()
.kv file:
#:import utils kivy.utils
<AndroidApp>:
FloatLayout:
canvas.before:
Color:
rgb: utils.get_color_from_hex("#ffffff")
Rectangle:
size: self.size
pos: self.pos
GridLayout:
rows: 1
cols: 2
Label:
text:"Android Vibrate"
Button:
text:"Android Vibrate"
on_press:
root.vibrate()
When I try to run the app, I get the following error:
enter image description here
This should be straightforward app but somehow I find a way to make it crash. The android_app.py & .kv files are in the same folder. Any ideas why the window wont be created? I aprreciate any help.
Thanks,
Alex
You have to return the main class, not the result from building. Here I'm using Builder.load_string() for convenience, and not importing player, but otherwise the only substantive change is that I changed return kv to return AndroidApp()
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
KV = '''
#:import utils kivy.utils
<AndroidApp>:
FloatLayout:
canvas.before:
Color:
rgb: utils.get_color_from_hex("#ffffff")
Rectangle:
size: self.size
pos: self.pos
GridLayout:
rows: 1
cols: 2
Label:
text:"Android Vibrate"
Button:
text:"Android Vibrate"
on_press:
root.vibrate()
'''
class AndroidApp(GridLayout):
def vibrate(self):
vibrator.vibrate()
Builder.load_string(KV)
class MainApp(App):
def build(self):
return AndroidApp()
if __name__ == "__main__":
MainApp().run()

Kivy Layout Issue

I am trying to create a GUI using Kivy. However, I cannot resolve some formatting issues.
Here is a slimmed down version of my KV file:
BoxLayout:
MainCanvas:
size_hint: 1,1
size: (root.width,root.height*.9)
DoubleEllipseWidget:
ActionBar:
id: _action
size_hint: 1,0.1
size: (root.width,root.height*.1)
pos_hint: {'bottom':1}
ActionView:
use_separator: True
ActionPrevious:
title: 'Test App:'
with_previous: False
ActionOverflow:
disabled: True
ActionButton:
important: True
text: 'Button 1'
#on_release: some_function
ActionButton:
text: 'Button 2'
#on_release: some_function
ActionButton:
text: 'Button 3'
#on_release: some_function
<DoubleEllipseWidget>
size: [200, 200]
canvas:
Color:
rgba: 0, 0, 0, 1
Ellipse
size: [198, 198]
pos: [600-200-100, 800-200-100]
Color:
rgba: 1, 1, 1, 1
Ellipse
size: [200, 200]
pos: [600-200-100, 800-200-100]
TextInput:
on_parent:self.focus = True
text: 'center of circle'
background_color: (0,0,0,0)
foreground_color: (0,0,0,1)
What I am trying to get to is very easily explained.
Essentially, there should be a menu bar running horizontally along the screen window (10% of total height and 100% of width). I believe I have done this.
The remaining 95% height should be the main canvas - I believe I have also done this.
The final part is getting a particular widget to be placed into the center of the main canvas. This is where I am stuck and would appreciate some help.
The widget I need to center is made up of two circles (one centered on top of the other, with one being slightly smaller than the other). Then, on top of the top-most circle should be a TextInput.
After taking the advice from this community, I stripped all of the logic back until I was left with just the basics (class definitions and layout), and was still having issues.
However, I have now learnt that what I assumed was a layout issue was likely not. A Kivy bug report last commented on in Nov 2018 (TextInput focus problem #3863), suggests there are unexplained instances where textinputs can lose focus. Below is the suggested workaround which also worked for me. Thanks for those that tried to help me.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.textinput import TextInput
from kivy.lang import Builder
Builder.load_string('''
<Base>:
Button:
text: 'Press for Popup'
on_press: root.edit_text()
<TextInputX>
id: texter
keyboard_mode: 'managed'
is_focusable: True
focus: True
keyboard_on_key_down:
texter.show_keyboard()
''')
class Base(BoxLayout):
def __init__(self, **kwargs):
super(Base, self).__init__(**kwargs)
def edit_text(self, *args):
self.clear_widgets()
content = TextInputX()
# content.focus = True
popup = Popup(content=content)
popup.open()
class TextInputX(TextInput):
def __init__(self, **kwargs):
super(TextInputX, self).__init__(**kwargs)
# self.focus = True
class SampleApp(App):
def build(self):
return Base()
if __name__ == '__main__':
SampleApp().run()

Positioning images with FloatLayout

This is driving me insane. I'm trying to position an image to the top of the main window with FloatLayout. Below is a simplified example.
It seems that a button is fine, but an image defaults to a 100x100 square (yes, I think I've read that somewhere) and the top bounding box of the image is at the top of the screen, not the image.
How can I force the image (top of the rectangle) to the top of the window like the button is?
Screen_showing example_button_and_image
import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.image import Image
from kivy.uix.floatlayout import FloatLayout
from kivy.config import Config
Config.set('graphics', 'width', '480')
Config.set('graphics', 'height', '800')
class MyApp(App):
def setOrientation(self, orient):
""""""
self.orient = orient
def build(self):
return FloatLayout()
if __name__ == "__main__":
app = MyApp()
app.setOrientation(orient="vertical")
app.run()
<FloatLayout>:
Image:
source: 'image_400x90.png'
pos_hint: {'left':1, 'top':1}
size_hint: None, None
allow_stretch: False
keep_ratio: True
Button:
font_size: 30
color: 0,1,0,1
size_hint: 0.3, 0.1
text: "TopRight"
pos_hint: {'right':1, 'top':1}
That happend because the Image is a Widget and "the real image" (the picture) is a texture of that Widget, and by default, the image is centered and fits inside the widget bounding box. If you don’t want that, you can set allow_stretch to True and keep_ratio to False. (docs)
For understand that you can add a canvas with a rectangle in the Image Widget like this:
Image:
canvas:
Color:
rgba: 1, 1, 1, 0.5
Rectangle:
pos: self.pos
size: self.size
source: 'dog.jpg'
pos_hint: {'left':1, 'top':1}
size_hint: None, None
allow_stretch: True
keep_ratio: False
And then you can see why the image doesn't do what you want
another pic (the image(texture) is centered and fits inside the widget):
One thing you can do is set allow_stretch: True and keep_ratio: False
This is the result: (set the size with size_hint)
Favcau gives one solution above (and thanks), but I have found what I think is more obvious. All it needs is to have the size defined in the kv file.
Image:
id: image2
source: 'image_400x90.jpg'
pos_hint: {'left':1, 'top':1}
size_hint: None, None
size: 400, 90
allow_stretch: True
keep_ratio: False

Combining image and text within a button in kivy

What's the preferred way to combine an image/icon and text within a button? For example, how would you create a button with text = 'my button', and a graphical icon to the left of that text?
Regarding to question #2.
The way Kivy works is embedding Widget instances. Since Image and Button are subclasses of Widget, then all you have to do is embed an Image inside a the Button. Notice that the positioning inside a widget is fixed. You have to give explicit coordinates.
That said, you can always embed a Layout to organize the stuff you are putting inside the Button.
Here is the simple ex
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string("""
<ButtonsApp>:
orientation: "vertical"
Button:
text: "B1"
Image:
source: 'kivy.png'
y: self.parent.y + self.parent.height - 200
x: self.parent.x
Label:
text: "A label"
""")
class ButtonsApp(App, BoxLayout):
def build(self):
return self
if __name__ == "__main__":
ButtonsApp().run()
EDIT: An example of how a relative layout can be embedded inside a button
In this case I am using a StackLayout to organize an Image and a Label inside. As I said, Button is a Widget and Kivy works embedding widgets inside widgets. It doesn't matter if they are labels, buttons or layouts.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string("""
<ButtonsApp>:
orientation: "vertical"
Button:
StackLayout:
pos: self.parent.pos
size: self.parent.size
orientation: 'lr-tb'
Image:
source: 'kivy.png'
size_hint_x: None
width: 74
Label:
size_hint_x: None
width: 100
text: "The text"
Label:
text: "A label"
""")
class ButtonsApp(App, BoxLayout):
def build(self):
return self
if __name__ == "__main__":
ButtonsApp().run()
Meanwhile, there is another way.
You can use icon fonts such as Font Awesome and combine them with text.
Either import the font directly and wrap the text in their font tag, or simply use some of the libraries that take care of that.
#: import icon ...
Button:
markup: True
text: "%s Comment" % icon('comment', 32)
size_hint_x: None
width: 100
Kivy-iconfonts converts css/tff combinations that are distributed for web sites into a json format that it loads during runtime using the import statement as shown in the example above.
I extended this in my fork to fetch Font Awesome icons during runtime and put them into the working directory of your application. That gives you the advantage of not having to distribute the fonts with the application.
Define a new Button class or modify the one in Kivy.uix.button
class MyButton(Button):
#add these three properties in the class
icon = ObjectProperty(None)
icon_size = (0,0)
icon_padding = NumericProperty(0) #Enter any default value like 50 if you will
#always use an icon, or specify this field
#while creating the button
def __init__(self, **kwargs):
#no changes here, just for reference
return super(MyButton, self).__init__(**kwargs)
KV file:
<MyButton>:
state_image: self.background_normal if self.state == 'normal' else self.background_down
disabled_image: self.background_disabled_normal if self.state == 'normal' else self.background_disabled_down
canvas:
Color:
rgba: self.background_color
BorderImage:
border: self.border
pos: self.pos
size: self.size
source: self.disabled_image if self.disabled else self.state_image
Color:
rgba: (1, 1, 1, 1) if root.icon != None else (1,1,1,0)
Rectangle:
source: root.icon
size: (self.texture_size[1],self.texture_size[1]) if self.icon_size == (0,0) else self.icon_size
pos: int(self.center_x - self.texture_size[0] / 2.)-dp(root.icon_padding), int(self.center_y - self.texture_size[1] / 2.)
Color:
rgba: 1, 1, 1, 1
Rectangle:
texture: self.texture
size: self.texture_size
pos: int(self.center_x - self.texture_size[0] / 2.)+dp(root.icon_padding), int(self.center_y - self.texture_size[1] / 2.)
Now just create a button widget
MyButton(text = 'Hello',icon = 'icon.png', icon_padding = 50)

Resources