I am making a simple weather app. The user enters a location and gets results in plain text. I have made a loading screen that has a spinning animation. When the program is loading these weather results, it changes to the loading screen and the animation spins well.
I have the animation set for a duration of 20 seconds for now. This allows the first instance of my load screen display more than enough time to yield results and then change to the results screen (normally about 5 seconds). That seemingly works well.
My issue is: Once the animation has completed its 20 second run (in the background) it doesn't re start when called for again. It seems the app will allow it to be called once only. For it to animate again, the app must be closed completely and restarted.
Ideally I'd like the animation to stop spinning and be reset as soon as the load screen has been exited (so once the results have been delivered). The user can get weather multiple times, so I need it to start again every time the load screen is displayed.
I have tried the undesired solution of having it constantly spinning in the background so that when the load screen is shown, its spinning.
But, even then I am having trouble getting it to repeat... (anim.repeat = True doesn't work for me)
Here is a working example that has been stripped back as much as possible to still behave the same as my real app.
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.animation import Animation
import time
from threading import Thread
class HomeScreen(Screen):
pass
class Results1Screen(Screen):
pass
class LoadScreen(Screen):
pass
GUI = Builder.load_file("main.kv")
class TestApp(App):
def build(self):
return GUI
def change_screen(self, screen_name):
screen_manager = self.root.ids['screen_manager']
screen_manager.current = screen_name
def spin_widget(self):
widget = self.root.ids["load_screen"].ids["spinning_widget"]
anim = Animation(angle=-2000, duration=20)
anim.repeat = True # this doesn't seem to work?
anim.start(widget)
def thread1(self): # this starts the actual "work" of the app by calling get_briefing() below
p2 = Thread(target=lambda: self.get_briefing())
p2.start()
def get_briefing(self):
time.sleep(5)# I have added a 5sec sleep to emulate work being done
self.change_screen("results_1_screen") # screen change from loading screen to results screen after work complete
TestApp().run()
main.kv (just for screen managing)
#:import utils kivy.utils
#:include kv/homescreen.kv
#:include kv/results1screen.kv
#:include kv/loadscreen.kv
GridLayout:
cols: 1
FloatLayout:
canvas:
Rectangle:
size: self.size
pos: self.pos
pos_hint: {"top": 1, "left": 1}
size_hint: 1, 0
ScreenManager:
id: screen_manager
HomeScreen:
name: "home_screen"
id: home_screen
Results1Screen:
name: "results_1_screen"
id: results_1_screen
LoadScreen:
name: "load_screen"
id: load_screen
homescreen.kv
<HomeScreen>:
FloatLayout:
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
Button:
text: "GO!"
pos_hint: {"top": .2, "center_x": .5}
size_hint: .5, .08
padding: 20, 20
on_release:
app.spin_widget()
app.change_screen("load_screen") # widget spinning and going to load screen
app.thread1() # start the app working in background
results1screen.kv (in real app, this has desired results displayed)
<Results1Screen>:
FloatLayout:
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
Label:
text: "RESULTS"
pos_hint: {"top": .9, "center_x": .5}
size_hint: 1, .1
Button:
text: "back"
size_hint: .1, .1
on_release:
app.change_screen("home_screen")
loadscreen.kv
<LoadScreen>:
FloatLayout:
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
Label:
text: "loading"
SpinningWidget:
id: spinning_widget
angle: 0
size_hint: .1, .1
pos_hint: {"center_x": .5, "center_y": .6}
<SpinningWidget#Image>
angle: 0
canvas.before:
PushMatrix
Rotate:
angle: root.angle
axis: 0, 0, 1
origin: root.center
canvas.after:
PopMatrix
I have tried a couple of ways of exiting or resetting the spin widget function after the get_briefing function has finished but to no avail. I have tried anim.cancel() and things like exit(self.spin_widget()) in an attempt to reset it, ready for its next call up.
Can someone please help me out, I can't seem to find an answer in Kivy docs or anywhere else online for that matter. Thank you.
Try resetting the angle to 0 before each Animation:
def spin_widget(self):
widget = self.root.ids["load_screen"].ids["spinning_widget"]
widget.angle = 0
anim = Animation(angle=-2000, duration=20)
anim.repeat = True # this doesn't seem to work?
anim.start(widget)
The problem is that after the first Animation, the angle value is -2000, so any Animation after that will not actually change the angle. Same problem with repeat.
Related
How can I change the value of text in Label on the 2nd screen by pressing a Button on the 1st screen?
In my example, I have 2 screens, on the first there are 3 buttons; one should change the text to "1st text", second should change the text to "2nd text" and the third is used to move between these two screens.
On the second screen, there is a Label which text should be changed by pressing the buttons. Then, there is also the button used to move to the first screen.
My .py looks like:
import kivy
kivy.require("1.10.1")
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.app import App
class Screen1(Screen):
pass
class Screen2(Screen):
pass
class Select_text(App):
def build(self):
sm = ScreenManager(transition=FadeTransition())
sm.add_widget(Screen1(name = "scr1"))
sm.add_widget(Screen2(name = "scr2"))
return sm
app = Select_text()
app.run()
My .kv seems like:
<Screen1>:
id: scr1
orientation: "vertical"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Background.png"
Button:
id: change_to_1
pos: (root.width-self.width)/2, 400
size: 1200, 200
size_hint: None, None
text: "Change the text on the 2nd screen to »1st text«"
#on_press: (I don‘t know what should be there)
Button:
id: change_to_2
pos: (root.width-self.width)/2, 800
size: 1200, 200
size_hint: None, None
text: "Change the text on the 2nd screen to »2nd text«"
#on_press: (I don‘t know what should be there)
Button:
id: go_to_other_screen
pos: (root.width-self.width)/2, 1400
size: 600, 200
size_hint: None, None
text: "Go to other screen"
on_press: root.manager.current = "scr2"
<Screen2>:
id: scr2
orientation: "vertical"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Background.png"
Label:
id: text
text: "Text which should be changed"
pos: (root.width-self.width)/2, 800
size: 600, 200
Button:
id: go_to_other_screen
pos: (root.width-self.width)/2, 1400
size: 600, 200
size_hint: None, None
text: "Go to other screen"
on_press: root.manager.current = "scr1"
I tried to search on the internet, but it didn't solve the main issue.
Thanks for any answer.
In the kv file call a function in the screen 1 class. You can then use the get_screen function to access the other screen and change its text in that function.
Would probably look something like:
(in the kv file)
on_press: root.functionname()
(main python file)
def functionname(self):
self.manager.get_screen('scr2').ids.text.text = "whatever you want here"
May i also suggest changing the id of the text ur changing to something else, because it looks a bit confusing with ids.text.text
For more info about the get_screen function if you are confused about that https://medium.com/nerd-for-tech/kivy-use-get-screen-to-access-objects-from-other-screens-8d4d6f288f3
I am trying to figure out a way to keep the three widgets I have in my main.kv file header setup and just move the two outside ones further to the edges of the screen. This is like a top banner that is displayed on every screen and houses the time, an 'home button' and a 'settings button'. I have an absolute dog's breakfast of a main.kv layout and I don't really know how to change it so that its fully functional and keeps the widgets visible. I will include the framework for the section I am talking about.
main.kv
GridLayout:
cols: 1
FloatLayout:
GridLayout:
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
rows: 1
pos_hint: {"top": 1, "left": 1}
size_hint: 1, .07
GridLayout:
rows: 1
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
pos_hint: {"top": .93, "center_x": .5}
size_hint: 1, .03
ImageButton:
id: home_button
source: "icons/home.png"
opacity: 1 if self.state == 'normal' else .5
on_release:
app.change_screen("home_screen")
app.clear_results()
Label:
id: utc_time
markup: True
font_size: '20sp'
text: ""
ImageButton:
id: settings_button
source: "icons/cog.png"
opacity: 1 if self.state == 'normal' else .5
on_release:
app.change_screen("settings_screen")
I have found that bringing my three widgets back one tab (not under GridLayout but as a child of FloatLayout) allows me to reposition them out more to where I'd like. This doesn't work though because I am referencing them in my main.py and adding or removing these widgets with reference to current screen. The problem I keep getting is that it keeps wanting to add widgets on top of widgets. I will include that function as well.
main.py
def home_setting_widgets(self, screen_name): # this is going to display the home and setting widgets once the app has already been opened
home_button = self.root.ids["home_button"]
utc_time = self.root.ids["utc_time"]
settings_button = self.root.ids["settings_button"]
grid_layout = self.root.children[0].children[2]
if screen_name == "login_screen" or screen_name == "signup_screen" or screen_name == "welcome_screen":
grid_layout.remove_widget(home_button) # remove widgets if on the 3 screens listed
grid_layout.remove_widget(utc_time)
grid_layout.remove_widget(settings_button)
self.home_and_setting = False
else:
if self.home_and_setting == False:
grid_layout.add_widget(home_button) # add widgets
grid_layout.add_widget(utc_time)
grid_layout.add_widget(settings_button)
self.home_and_setting = True
I think the problem if I move them out of the one GridLayout is that they won't be referenced as a group but as individual widgets. I currently have them bunched and referenced in the grid_layout = self.root.children[0].children[2] line.
I have tried removing GridLayouts and their contents from main.kv, I have tried 'tabbing' the widgets to different spots, I have tried referencing the widgets in many different ways.
All I want to do is move the 'home button' and 'settings button' further apart slightly and it has me this stuck. If there is a way to move them without breaking everything, please tell me. Your help is much appreciated, thank you.
Try adding some spacing to the GridLayout that has the "Home", "Time", and "Settings"
GridLayout:
rows: 1
spacing: 1000
canvas:
I can't get the image from kivy docs page to upload, but the GridLayout kivy docs has the "spacing" info you are for.
I am doing an App in kivy using kivyMD but I Want to insert a MDcard, the thing is that I want the MDCard look like this:
But it looks like this:
I know it can be with border_radius but I dont know how to do it,
here is my code:
PY:
import kivy
from kivymd.app import MDApp
from kivymd.uix.card import MDCard
class Home(MDCard):
pass
class Manage(MDApp):
title = 'QUICKP'
def build(self):
return Home()
if __name__ == '__main__':
Manage().run()
KV:
<Home>
MDCard:
size_hint: None, None
size: "280dp", "180dp"
pos_hint: {"center_x": .5, "center_y": .5}
You do not need to modify the card.py file nor do you have to use canvas you just have to place the following in your kivy code
border_radius: 20
radius: [15]
In border_radius you put the border limit that you will be able to use and in radius you put the border level that your mdcard will have.
People advise to use canvas its OK, but it increase your coding and make it very complex. Just go to the Kivymd>uix>card.py line 631 change the default from 3dp to for example 20dp or whatever you want. then go back to your code and type radius: [] and insert your desire number from 20 to 0.
Done!!!
#BernardoOlisan You can use this directly
MDCard:
size_hint: None, None
size: "280dp", "180dp"
pos_hint: {"center_x": .5, "center_y": .5}
elevation: 15
radius: 10
MDLabel:
text: 'Your title'
halign: 'center'
pos_hint: {"center_x": .5, "center_y": .5}
It works for sure
I had the same problem, and there is a border_radius property for MDCards, but it does not work. But i have solved this with this code:
<MyCard>:
orientation: 'vertical'
size_hint: 0.1, 1
canvas.before:
Color:
rgba: app.theme_cls.primary_color
RoundedRectangle:
radius: [10]
size: self.size
pos: self.pos
FloatLayout:
size: self.size
pos: self.pos
pos_hint: {"x": -0.1, "y": -0.6}
And here is a part from .py file:
class Plan(RectangularElevationBehavior, RectangularRippleBehavior, FloatLayout):
ripple_scale = 1.4
text = StringProperty()
text_label = StringProperty()
So, it creates a MyCard(FloatLayout class) with Canvas in it, which inherits from FloatLayout, RippleBehaviour and ElevationBehaviour. And then you can add to that FloatLayout in it to manage content of it. But if it looks strange because of the position, try playing with pos_hint-s.
I am trying to build a basic Kivy app. After adding the basic elements, and running the app, all of the elements are crammed into the bottom left corner. It shows up like this on android and Linux.
Main.py:
from kivy.app import App
from kivy.uix.widget import Widget
class SublimeLauncher(Widget):
pass
class SublimeLauncherApp(App):
def build(self):
return SublimeLauncher()
if __name__ == "__main__":
SublimeLauncherApp().run()
sublimelauncher.kv:
#:kivy 1.2.0
<SublimeLauncher>:
FloatLayout:
BoxLayout:
orientation: 'vertical'
spacing: 10
Label:
text: "Enter the path to the folder to open.\nPress OK if you would like to open without a directory"
TextInput:
id: folderpath
Button:
text: 'OK'
I first tried it with just the BoxLayout, but read somewhere the root widget is always as big as the app. How do I declare the size of the app? Or the layout? How would you go about doing something like a dialog box?
Maybe I am missing something very basic, but I can't seem to figure it out.
Edit: here is what I am seeing..
Your layout has a default size of 100x100 pixels. You can try to color it to see how much space does it take:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
kv = '''
<SublimeLauncher>:
BoxLayout:
canvas:
Color:
rgb: 1, 0, 0
Rectangle:
size: self.size
orientation: 'vertical'
spacing: 10
Label:
text: "Enter the path to the folder to open.\\nPress OK if you would like to open without a directory"
TextInput:
id: folderpath
Button:
text: 'OK'
'''
Builder.load_string(kv)
class SublimeLauncher(Widget):
pass
class SublimeLauncherApp(App):
def build(self):
return SublimeLauncher()
if __name__ == "__main__":
SublimeLauncherApp().run()
Setting non-default size:
kv = '''
<SublimeLauncher>:
BoxLayout:
size: 250, 250
canvas:
Color:
rgb: 1, 0, 0
Rectangle:
size: self.size
orientation: 'vertical'
spacing: 10
Label:
text: "Enter the path to the folder to open.\\nPress OK if you would like to open without a directory"
TextInput:
id: folderpath
Button:
text: 'OK'
'''
Builder.load_string(kv)
Taking full space:
kv = '''
<SublimeLauncher>:
BoxLayout:
size: root.size
canvas:
Color:
rgb: 1, 0, 0
Rectangle:
size: self.size
orientation: 'vertical'
spacing: 10
Label:
text: "Enter the path to the folder to open. \\nPress OK if you would like to open without a directory"
TextInput:
id: folderpath
Button:
text: 'OK'
'''
Builder.load_string(kv)
I recently wrote a post of a little trick I use when I am programming interfaces. The trick will let you see a border around all the widgets (layouts included) you add to the screen. This would be the result for your code:
It takes advantage of inheritance and the Kivy rules to overwrite the base class of all widgets. You just have to add:
<Widget>:
canvas.after:
Line:
rectangle: self.x+1,self.y+1,self.width-1,self.height-1
at the beginning of this file:
<SublimeLauncher>:
FloatLayout:
BoxLayout:
orientation: 'vertical'
spacing: 10
Label:
text: "Enter the path to the folder to open.\nPress OK if you would like to open without a directory"
TextInput:
id: folderpath
Button:
text: 'OK'
As your root widget is not a layout (you made SublimeLauncher inherit Widget), it doesn't set its children size/positions. So your FloatLayout have the defaults, since you don't override them manually either.
pos: 0, 0
size: 100, 100
And these defaults of course constraints the child, since FloatLayout by constraint their size based on their size_hint property.
You want to give them more space, as Nykakin pointed out.
Also, as your text is bigger than the Label (you didn't set halign and text_size either) its texture is centered on the center of the Label, and so some part of it is out of screen. You want to have a look at kivy/examples/widgets/textalign.py
I'm using Kivy, a Python Library, and i'm having trouble getting a scroll view to work, and organizing it into a .kv file.
I have a big system that's broken up into many .py and .kv files.
I am also using kivy's screen manager
ui_manager.py
#ui_manager.py
class UIManager():
_ScreenManager = None
def __init__(self, inScreenManager):
self.ScreenManager = inScreenManager # The main app constructs the manager, sends to me, then returns it as the root widget.
main.py
#main.py
class MyApp(App):
def build(self):
screenManager = ScreenManager()
uiManager = ui_manager.UIManager(screenManager)
return screenManager
Obviously I ommited a little code, but this is almost exactly what happens.
One of the screns needs to scroll. It will have a long graphic and many buttons.
The Kivy documentation, as usual, only tells me how to do it via code, NOT via the kivy language (.kv files)
http://kivy.org/docs/api-kivy.uix.scrollview.html
I want a class, OrangeWidget, to hold the scrolling widget, so I can play with its data later on.
Here is my best attempt at getting an organized scrollview working
orange_widget.py
#orange_widget.py
class OrangeWidget(Screen):
pass
class OrangeGraphic(Widget):
pass
Orange.kv
<OrangeLineWidget>
ScrollView:
size_hint: (None, None)
size: (400, 1200)
OrangeLineGraphic:
pos: root.pos
<OrangeGraphic>
canvas:
Color:
rgba: 1, .5, 0, 1
Rectangle:
pos: self.center_x - 15, 0
size: 30, self.height * 2
Label:
text: "Hello"
I know it's a lot, and it's not looking well organized already, but i'm just trying to figure out how to get the dang thing working properly.
Here is the current result: It won't scroll, and for whatever reason it's been "boxed" to the right, instead of taking up the full screen
as answered on the ML, here you don't define the Label pos, your OrangeGraphic being a simple widget, not a layout, you need to do it:
<OrangeGraphic>
canvas:
Color:
rgba: 1, .5, 0, 1
Rectangle:
pos: self.center_x - 15, 0
size: 30, self.height * 2
Label:
text: "Hello"
pos: root.pos
size: root.size
Looks like the trick, for this particular "sliding" screen, was to use a "Relative Layout"
https://groups.google.com/forum/#!topic/kivy-users/RwuI8QGm3fw
Here is the updated code:
orange_widget.py
class OrangeWidget(Screen):
def __init__(self, **kwargs):
super(OrangeWidget, self).__init__(**kwargs)
scrollView = ScrollView(size_hint=(1, 1))
# add custom widget into that layout
customWidget = OrangeGraphicWidget(height=1200, size_hint_y=None)
#layout.bind(minimum_height=layout.setter('height'))
scrollView.add_widget(customWidget)
self.add_widget(scrollView)
class OrangeGraphicWidget(RelativeLayout):
pass
OrangeWidget.kv
<OrangeWidget>
<OrangeGraphicWidget>
canvas:
Color:
rgba: 1, .5, 0, 1
Rectangle:
pos: self.center_x - 15, 20
size: 30, self.height - (self.height / 10)
Button:
text: "Button 1"
pos: root.pos
size_hint: (None, None)
Button:
text: "Button 2"
pos_hint: {'center_x': .5, 'center_y': .95}
size_hint: (None, None)