You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kivy ScrollView中Canvas边框位置不更新,仅最后一个任务显示边框

问题解决:Kivy ScrollView中任务边框仅跟随最后一个任务的修复

错误原因

  1. 坐标系误用:绘制边框/背景时使用了控件的全局位置instance.pos,但Kivy控件的Canvas基于自身局部坐标系(控件左下角为(0,0)),用全局坐标会导致滚动时边框位置偏移,只有最后创建的任务边框位置看似正确。
  2. 事件绑定不全:仅绑定了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]))

关键修改点

  1. 坐标系修正:将Rectangle和Line的pos参数改为(0,0),基于控件自身的局部坐标系绘制,避免滚动时位置偏移。
  2. 补充事件绑定:给每个需要绘制背景/边框的Label同时绑定pos事件,确保ScrollView滚动导致控件位置变化时,能重新绘制背景和边框。
  3. 清理冗余代码:移除update_size和update_border_size中重复的instance.canvas.before.clear()调用,只保留一次clear操作。

内容的提问来源于stack exchange,提问作者Adil Akothiat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 19:45:55