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

Kivy RecycleView可拖拽行位置计算bug求助

RecycleView拖拽行功能适配问题

由于BoxLayout行数存在限制,我尝试在RecycleView中实现可拖拽行功能,参考相关示例完成了基础实现,但适配RecycleView时遇到核心问题:元素位置计算逻辑存在bug,例如列表有20条数据但屏幕仅显示10条时会出现异常。我已禁用RecycleView的拖拽滚动,相关实现代码如下:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, ListProperty, NumericProperty
from kivy.uix.recycleview import RecycleView
from kivy.metrics import dp
from kivy.uix.behaviors import DragBehavior

kv = """
<DraggableRow>:
    canvas:
        Color:
            rgba: root.highlight_color
        Rectangle:
            size: self.size
            pos: self.pos
    padding: 20, 0
    Label:
        text: root.song
    Label:
        text: root.album
    Label:
        text: root.artist
    Button:
        size_hint_x: None
        width: 80
        text: 'Delete'
        on_release: root.delete_song()

TestBox:
    orientation: 'vertical'
    TextInput:
        size_hint_y: None
        height: 48
        hint_text: 'Enter Song Name'
        multiline: False
        on_text: root.highlight_song(self.text)
    Label:
        text: 'Song List'
        size_hint_y: .1
    TouchRecycleView:
        id: rv
        viewclass: 'DraggableRow'
        data: root.rv_data_list
        scroll_type: ['bars']
        scroll_distance: 0
        bar_inactive_color: 1, 1, 1, 1
        bar_width: dp(20)
        do_scroll_x: False
        RecycleBoxLayout:
            id: rbl
            key_selection: 'song'
            default_size: None, dp(56)
            default_size_hint: 1, None
            size_hint_y: None
            height: self.minimum_height
            orientation: 'vertical'
"""

class DraggableRow(DragBehavior, BoxLayout):
    song = StringProperty()
    album = StringProperty()
    artist = StringProperty()
    highlight_color = ListProperty()
    index = NumericProperty()
    drag_touch = None

    def __init__(self, **kwargs):
        super(DraggableRow, self).__init__(**kwargs)
        self.test_box = None
        self.original_index = 0
        self.drag_touch = None

    def on_parent(self, instance, value):
        if value is not None:
            self.test_box = instance

    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos) and not self.drag_touch:
            self.drag_touch = touch
            self.original_index = self.parent.children.index(self)
            return super(DraggableRow, self).on_touch_down(touch)
        return super(DraggableRow, self).on_touch_down(touch)

    def on_touch_move(self, touch):
        if touch == self.drag_touch:
            self.y = touch.y - self.height / 2

            # 找到TestBox父容器
            test_box = self.parent
            while not isinstance(test_box, TestBox):
                test_box = test_box.parent

            # 拖拽时更新列表位置
            target_index = round(self.y / self.height)
            target_index = max(0, min(target_index, len(test_box.rv_data_list) - 1))

            if self.original_index < target_index:
                target_index -= 1

            #print(f"original_index: {self.original_index}, target_index: {target_index}, y: {self.y}")

            if target_index != self.original_index:
                test_box.move_element(self.original_index, target_index)
                self.original_index = target_index

    def on_touch_up(self, touch):
        if touch == self.drag_touch:
            test_box = self.parent
            while not isinstance(test_box, TestBox):
                test_box = test_box.parent

            test_box.move_element(self.original_index, self.index)
            self.drag_touch = None
            return True

        return super(DraggableRow, self).on_touch_up(touch)

    def delete_song(self):
        app = App.get_running_app()
        rv = app.root.ids.rv
        del rv.data[self.index]
        for i, _ in enumerate(rv.data):
            rv.data[i]['index'] = i
        rv.refresh_from_data()

class TestBox(BoxLayout):

    elements = [{'song': f'song_{n}',
                 'album': f'album_{n}',
                 'artist': f'artist_{n}',
                 'index': n,
                 'highlight_color': [0, 0, 0, 1]} for n in range(7)]
    rv_data_list = ListProperty(elements)

    def highlight_song(self, song):
        for track in self.rv_data_list:
            track['highlight_color'] = (.8, .8, .8, 1) if song == track['song'] else (0, 0, 0, 1)
        self.ids.rv.refresh_from_data()

    def move_element(self, original_index, target_index):
        # 将元素从original_index移动到target_index
        element = self.rv_data_list.pop(original_index)
        self.rv_data_list.insert(target_index, element)

        #print(f"data: {self.rv_data_list}")

        # 更新所有元素的index值
        for i, element in enumerate(self.rv_data_list):
            element['index'] = i

        # 刷新RecycleView
        self.ids.rv.refresh_from_data()

class TouchRecycleView(RecycleView):
    pass

class RVTestApp(App):
    def build(self):
        return Builder.load_string(kv)

RVTestApp().run()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 17:02:03