Kivy应用API调用异常:加载界面切换与AttributeError求助
Kivy应用界面切换与API加载错误修复方案
错误根源分析
从报错信息和代码来看,问题出在四个地方:
- ScreenManager中错误将
LoadingScreen注册为ContentScreen实例,导致界面逻辑混乱 - KV文件中
ContentScreen的布局缩进错误,内部控件的id无法被Screen实例捕获 - 同步API调用阻塞UI线程,
LoadingScreen来不及渲染就完成跳转 clear_input方法错误访问root的ids,应该直接获取MainScreen实例的控件
修复步骤
1. 修正ScreenManager的屏幕注册
在app.py的build方法中,把loading_screen的实例从ContentScreen改成LoadingScreen:
def build(self): sm = ScreenManager() sm.add_widget(MainScreen(name='main_screen')) sm.add_widget(ContentScreen(name='content_screen')) # 此处修复:把ContentScreen改为LoadingScreen sm.add_widget(LoadingScreen(name='loading_screen')) return sm
2. 修复KV文件中ContentScreen的缩进
ScrollView作为ContentScreen的子控件,必须缩进对齐,否则内部控件的id不会被Screen识别:
<ContentScreen>: # 此处添加缩进,ScrollView是ContentScreen的子控件 ScrollView: do_scroll_x: False do_scroll_y: True BoxLayout: orientation: 'vertical' padding: 10 spacing: 10 Label: id: content_label pos_hint: {"center_x": 0.5, "center_y": 0.5} font_size: '16' text_size: self.width, None Button: text: 'Back' size_hint:(None, None) size:(86, 34) background_color: (1.0, 0.0, 0.0, 1.0) font_size:'16' on_release: app.root.current = 'main_screen' app.clear_input() pos_hint: {"center_x": 0.5, "center_y": 0.5}
3. 改为异步API调用避免阻塞UI
同步的requests.post会卡住UI线程,导致LoadingScreen无法显示。改用线程处理API请求,通过Clock更新UI:
在app.py中导入threading和kivy.clock.Clock:
import threading from kivy.clock import Clock
修改load_content方法:
def load_content(self, query): def fetch_data(): url = 'http://127.0.0.1:5000/recipes/ask_mobile' data = {'content': query} headers = {'Content-Type': 'application/json'} response = requests.post(url, json=data, headers=headers) # 用Clock调度UI更新操作,确保在主线程执行 if response.status_code == 200: res_data = response.json() Clock.schedule_once(lambda dt: self.update_content_screen(res_data['data'])) else: print('Failed to retrieve data:', response.status_code) # 加载失败也切换回主界面 Clock.schedule_once(lambda dt: setattr(self.root, 'current', 'main_screen')) # 启动线程执行API调用 threading.Thread(target=fetch_data).start() def update_content_screen(self, content_text): # 更新ContentScreen的内容并切换界面 self.root.current = 'content_screen' self.root.get_screen('content_screen').ids.content_label.text = content_text
4. 修复clear_input方法的控件访问
clear_input方法需要获取MainScreen实例,再访问其内部的content输入框:
def clear_input(self): # 获取MainScreen实例,再访问其ids下的content main_screen = self.root.get_screen('main_screen') main_screen.ids.content.text = ''
完整修改后的代码
app.py
from kivy.app import App from kivy.lang import Builder from kivy.uix.screenmanager import ScreenManager, Screen import requests import threading from kivy.clock import Clock Builder.load_file('mainscreen.kv') class LoadingScreen(Screen): pass class ContentScreen(Screen): pass class MainScreen(Screen): pass class SkylrApp(App): def build(self): sm = ScreenManager() sm.add_widget(MainScreen(name='main_screen')) sm.add_widget(ContentScreen(name='content_screen')) sm.add_widget(LoadingScreen(name='loading_screen')) return sm def clear_input(self): main_screen = self.root.get_screen('main_screen') main_screen.ids.content.text = '' def load_content(self, query): def fetch_data(): url = 'http://127.0.0.1:5000/recipes/ask_mobile' data = {'content': query} headers = {'Content-Type': 'application/json'} response = requests.post(url, json=data, headers=headers) if response.status_code == 200: res_data = response.json() Clock.schedule_once(lambda dt: self.update_content_screen(res_data['data'])) else: print('Failed to retrieve data:', response.status_code) Clock.schedule_once(lambda dt: setattr(self.root, 'current', 'main_screen')) threading.Thread(target=fetch_data).start() def update_content_screen(self, content_text): self.root.current = 'content_screen' self.root.get_screen('content_screen').ids.content_label.text = content_text if __name__ == '__main__': SkylrApp().run()
mainscreen.kv
<MainScreen>: BoxLayout: orientation: 'vertical' padding: 10 spacing: 10 Image: source: 'images/icon.png' size_hint: (1, 1) BoxLayout: size_hint_y: None height: '30' TextInput: id: content multiline: False hint_text: 'What would you like to make?' Button: text: 'Create' size_hint:(None, None) size:(86, 34) background_color: (1.0, 0.0, 0.0, 1.0) font_size:'16' on_release: app.root.current = 'loading_screen' app.load_content(content.text) pos_hint: {"center_x":0.5, "center_y":0.5} <LoadingScreen>: BoxLayout: orientation: 'vertical' Label: text: 'Loading...' font_size: '20' size_hint_y: None height: '50' <ContentScreen>: ScrollView: do_scroll_x: False do_scroll_y: True BoxLayout: orientation: 'vertical' padding: 10 spacing: 10 Label: id: content_label pos_hint: {"center_x": 0.5, "center_y": 0.5} font_size: '16' text_size: self.width, None Button: text: 'Back' size_hint:(None, None) size:(86, 34) background_color: (1.0, 0.0, 0.0, 1.0) font_size:'16' on_release: app.root.current = 'main_screen' app.clear_input() pos_hint: {"center_x": 0.5, "center_y": 0.5}
内容的提问来源于stack exchange,提问作者Graham Patrick
相关产品推荐
相关产品推荐

