如何修改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()
关键改动说明
- 自定义ScrollableBoard类:
- 重写
on_touch_down:优先判断触摸位置是否在Cell上,若在则将事件传递给Cell,确保拖拽操作优先响应;否则交给ScrollView处理滚动。 - 重写
on_touch_move:检测到有卡片正在拖拽时,阻止ScrollView处理滚动,避免拖拽时误触发滚动。
- 重写
- 调整GridLayout属性:
- 设置
size_hint_y: None和height: self.minimum_height,让GridLayout高度自适应内容,触发ScrollView的滚动功能。
- 设置
- 增加网格数量:修改
on_start中的循环次数,让GridLayout超出屏幕范围,验证滚动功能。
内容的提问来源于stack exchange,提问作者lukassliacky
相关产品推荐
相关产品推荐

