Kivy ScrollView中Canvas边框位置不更新,仅最后一个任务显示边框
问题解决:Kivy ScrollView中任务边框仅跟随最后一个任务的修复
错误原因
- 坐标系误用:绘制边框/背景时使用了控件的全局位置
instance.pos,但Kivy控件的Canvas基于自身局部坐标系(控件左下角为(0,0)),用全局坐标会导致滚动时边框位置偏移,只有最后创建的任务边框位置看似正确。 - 事件绑定不全:仅绑定了
size事件,未绑定pos事件,当ScrollView滚动导致控件位置变化时,不会触发重新绘制边框/背景。
修复代码
修改MainWidget中的相关方法和绑定逻辑:
class TasksBox(GridLayout): def __init__(self, **kwargs): super().__init__(**kwargs) self.size_hint = (1, None) self.cols = 1 self.bind(minimum_height=self.setter("height")) class ScrollBox(ScrollView): def __init__(self, **kwargs): super().__init__(**kwargs) self.size_hint = (1, None) self.size = (Window.width, Window.height) class MainWidget(GridLayout): def __init__(self, **kwargs): super().__init__(**kwargs) self.size_hint = (1, None) self.cols = 1 self.spacing = dp(10) self.padding = dp(10) self.size_hint = (1, 1) self.bind(size=lambda instance, value: self.update_size(instance, value, (1, 1, 1))) self.btn_height = dp(40) self.text_value = "" add_button = AddButton(self.btn_height).create() self.task_field = TaskField(self.btn_height).create() add_button.bind(on_press=self.create_task) self.task_field.bind(on_text_validate=self.create_task) self.task_field.bind(text=self.get_value) # text input + submit button box = BoxLayout(size_hint=(1, None), height=self.btn_height, orientation="horizontal") box.spacing = dp(20) box.add_widget(self.task_field) box.add_widget(add_button) self.add_widget(box) self.task_field.focus = True self.tasks_box = TasksBox() scroll_box = ScrollBox() scroll_box.add_widget(self.tasks_box) self.add_widget(scroll_box) def get_value(self, instance, value): self.text_value = value def create_task(self, instance): todo = Todo() if len(self.text_value) > 2: todo.create_task(self.text_value, d.datetime.now().strftime("%Y-%m-%d %H:%M"), "Todo") self.add_task(todo.get_tasks()) self.task_field.text = "" else: print("Please write a task! Contains more than 4 characters!") self.task_field.focus = True def add_task(self, tasks): task = tasks[len(tasks)-1] task_name = task["name"] task_id = task["id"] task_status = task["status"] task_date = task["date"] task_box = BoxLayout(size_hint=(1, None), height=self.btn_height) task_box.spacing = dp(10) # 任务名称Label - 绑定size和pos事件 task_name_box = Label(text=task_name, valign="middle", halign="left", size_hint=(.6, None), height=self.btn_height, color=(0, 0, 0), padding=(dp(10))) task_name_box.bind(size=task_name_box.setter("text_size")) # 同时绑定size和pos到update_border_size task_name_box.bind(size=lambda i, v: self.update_border_size(i, v, (0, 0, 0))) task_name_box.bind(pos=lambda i, v: self.update_border_size(i, i.size, (0, 0, 0))) # 任务ID Label - 绑定size和pos事件 task_id_box = Label(text=f"{task_id}", size_hint=(.1, None), height=self.btn_height, color=(1, 1, 1)) task_id_box.bind(size=lambda i, v: self.update_size(i, v, (0, .6, 1))) task_id_box.bind(pos=lambda i, v: self.update_size(i, i.size, (0, .6, 1))) # 任务状态Label - 绑定size和pos事件 task_status_box = Label(text=task_status, size_hint=(.1, None), height=self.btn_height, color=(0, 0, 0)) task_status_box.bind(size=lambda i, v: self.update_size(i, v, (.5, 1, .5))) task_status_box.bind(pos=lambda i, v: self.update_size(i, i.size, (.5, 1, .5))) # 任务日期Label - 绑定size和pos事件 task_date_box = Label(text=f"{task_date}", size_hint=(.2, None), height=self.btn_height, color=(163/255, 163/255, 163/255)) task_date_box.bind(size=lambda i, v: self.update_border_size(i, v, (0, 0, 0, .7))) task_date_box.bind(pos=lambda i, v: self.update_border_size(i, i.size, (0, 0, 0, .7))) task_box.add_widget(task_id_box) task_box.add_widget(task_status_box) task_box.add_widget(task_name_box) task_box.add_widget(task_date_box) self.tasks_box.add_widget(task_box) def update_size(self, instance, value, color): instance.canvas.before.clear() with instance.canvas.before: Color(*color) # 使用局部坐标(0,0)代替instance.pos Rectangle(size=value, pos=(0, 0)) def update_border_size(self, instance, value, color): instance.canvas.before.clear() with instance.canvas.before: Color(*color) # 使用局部坐标(0,0)绘制矩形边框 Line(rectangle=(0, 0, value[0], value[1]))
关键修改点
- 坐标系修正:将
Rectangle和Line的pos参数改为(0,0),基于控件自身的局部坐标系绘制,避免滚动时位置偏移。 - 补充事件绑定:给每个需要绘制背景/边框的Label同时绑定
pos事件,确保ScrollView滚动导致控件位置变化时,能重新绘制背景和边框。 - 清理冗余代码:移除
update_size和update_border_size中重复的instance.canvas.before.clear()调用,只保留一次clear操作。
内容的提问来源于stack exchange,提问作者Adil Akothiat
相关产品推荐
相关产品推荐

