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

如何修改Kivy-Garden Draggable示例实现ScrollView与拖拽放置兼容

问题描述

原Kivy-Garden Draggable示例可正常生成数字/字母卡片并拖拽到4x4网格中,现在需要将网格所在的RelativeLayout替换为ScrollView,实现可滚动的放置区域。但直接替换后出现功能冲突:要么ScrollView可滚动但卡片拖拽放置失效,要么拖拽功能正常但ScrollView无法滚动。

解决方案

核心是处理ScrollView与Draggable组件的触摸事件冲突,通过区分拖拽操作和滚动操作,让两种功能共存。以下是修改后的完整代码:

from kivy.properties import ObjectProperty
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView

from kivy_garden.draggable import KXDroppableBehavior, KXDraggableBehavior

KV_CODE = '''
#:import ascii_uppercase string.ascii_uppercase

<Cell>:
    drag_classes: ['card', ]
    canvas.before:
        Color:
            rgba: .1, .1, .1, 1
        Rectangle:
            pos: self.pos
            size: self.size
<Card>:
    drag_cls: 'card'
    drag_timeout: 0
    font_size: 100
    opacity: .3 if self.is_being_dragged else 1.
    canvas.after:
        Color:
            rgba: 1, 1, 1, 1
        Line:
            rectangle: [*self.pos, *self.size, ]
<Deck>:
    canvas.after:
        Color:
            rgba: 1, 1, 1, 1
        Line:
            rectangle: [*self.pos, *self.size, ]
<ScrollableBoard>:
    GridLayout:
        id: board
        cols: 4
        size_hint_x: 1
        size_hint_y: None
        spacing: 10
        padding: 10
        height: self.minimum_height  # 让GridLayout高度自适应内容,触发滚动

BoxLayout:
    Widget:
        size_hint_x: .1

    # 替换原RelativeLayout为自定义ScrollableBoard
    ScrollableBoard:

    BoxLayout:
        orientation: 'vertical'
        size_hint_x: .2
        padding: '20dp', '40dp'
        spacing: '80dp'
        RelativeLayout:
            Deck:
                board: board
                text: 'numbers'
                font_size: '20sp'
                text_iter: (str(i) for i in range(20))  # 增加数量触发滚动
        Deck:
            board: board
            text: 'letters'
            font_size: '20sp'
            text_iter: iter(ascii_uppercase)
'''


class Cell(KXDroppableBehavior, FloatLayout):
    def accepts_drag(self, touch, ctx, draggable) -> bool:
        return not self.children

    def add_widget(self, widget, *args, **kwargs):
        widget.size_hint = (1, 1, )
        widget.pos_hint = {'x': 0, 'y': 0, }
        return super().add_widget(widget, *args, **kwargs)


class Card(KXDraggableBehavior, Label):
    pass


class Deck(Label):
    text_iter = ObjectProperty()
    board = ObjectProperty()

    def on_touch_down(self, touch):
        if self.collide_point(*touch.opos):
            if (text := next(self.text_iter, None)) is not None:
                card = Card(text=text, size=self._get_cell_size())
                card.start_dragging_from_others_touch(self, touch)
            return True

    def _get_cell_size(self):
        return self.board.children[0].size


class ScrollableBoard(ScrollView):
    def on_touch_down(self, touch):
        # 优先检查触摸是否在可拖拽区域,优先响应拖拽
        touch.push()
        touch.apply_transform_2d(self.to_local)
        for child in self.ids.board.children:
            if isinstance(child, Cell) and child.collide_point(*touch.pos):
                touch.pop()
                if child.dispatch('on_touch_down', touch):
                    return True
        touch.pop()
        return super().on_touch_down(touch)

    def on_touch_move(self, touch):
        # 若有卡片正在拖拽,阻止ScrollView处理滚动
        for card in App.get_running_app().root.walk(restrict=True):
            if isinstance(card, Card) and card.is_being_dragged:
                return False
        return super().on_touch_move(touch)


class SampleApp(App):
    def build(self):
        return Builder.load_string(KV_CODE)

    def on_start(self):
        board = self.root.ids.board
        # 增加网格数量,让ScrollView有滚动空间
        for __ in range(board.cols * 8):
            board.add_widget(Cell())


if __name__ == '__main__':
    SampleApp().run()
关键改动说明
  1. 自定义ScrollableBoard类:
    • 重写on_touch_down:优先判断触摸位置是否在Cell上,若在则将事件传递给Cell,确保拖拽操作优先响应;否则交给ScrollView处理滚动。
    • 重写on_touch_move:检测到有卡片正在拖拽时,阻止ScrollView处理滚动,避免拖拽时误触发滚动。
  2. 调整GridLayout属性:
    • 设置size_hint_y: None和height: self.minimum_height,让GridLayout高度自适应内容,触发ScrollView的滚动功能。
  3. 增加网格数量:修改on_start中的循环次数,让GridLayout超出屏幕范围,验证滚动功能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 01:12:34