Kivy ScrollView和 ScreenManager:如何处理滚动超出部分的数值并在主屏幕触发事件
首先,编程对我来说是爱好,而不是职业。下面的示例代码在一个字典中创建了一些人工数据,我希望在ScrollView容器被过度滚动时逐键访问这些数据。我已经连续好几天在尝试寻找解决方案…… 一直在研究装饰器,并在网上查找类似的问题但没有结果。我在Linux上运行Python 3.14.1,配套Kivy 2.3.1。
示例main.kv
#: import Effect main.Effect
ManagerWidget:
StartScreen:
Page2Screen:
<StartScreen>:
name: 'startscreen'
BoxLayout:
orientation: 'vertical'
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
size_hint_y: .1
Button:
id: btn1
text: 'Simulation of desired "prev_key" function via overscroll event'
on_release: root.prev_key ( )
Button:
id: btn2
text: 'Simulation of desired "next_key" function via overscroll event'
on_release: root.next_key ( )
ScrollView:
id: scroller
effect_cls: Effect
size_hint: 1, 1
do_scroll_x: False
GridLayout:
id: container
cols: 5
spacing: 2
size_hint_y: None
height: self.minimum_height
BoxLayout:
size_hint_y: .1
Button:
text: 'Go to Page 2'
on_release: root.manager.current = 'page2'
<Page2Screen>:
name: 'page2'
BoxLayout:
padding: 5
spacing: 5
orientation: 'vertical'
Label:
text: 'Placeholder 1'
Label:
text: 'Placeholder 2'
Label:
text: 'Placeholder 3'
BoxLayout:
size_hint_y: .3
Button:
text: 'Back to main screen'
on_release:
root.manager.current = 'startscreen'
以及main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.effects.dampedscroll import DampedScrollEffect
from kivy.metrics import dp
from kivy.clock import Clock
from time import time
class CallControl:
def __init__ ( self, max_call_interval ):
self._max_call_interval = max_call_interval
self._last_call = time ( )
def __call__ ( self, function ):
def wrapped ( *args, **kwargs ):
now = time ( )
if now - self._last_call > self._max_call_interval:
self._last_call = now
function ( *args, **kwargs )
return wrapped
class Effect ( DampedScrollEffect ):
def __init__ ( self, *args, **kwargs ):
super ( Effect, self ).__init__ ( **kwargs )
def on_overscroll ( self, *args ):
self.args = args
if self.overscroll < -100:
self.scrollup ( )
elif self.overscroll > 100:
self.scrolldown ( )
@CallControl ( max_call_interval = 1 )
def scrollup ( self ):
print ( 'How to update the Scrollview widgets? (i.e. the prev_key method in StartScreen )' )
# StartScreen.prev_key ( self.overscroll ) ???
@CallControl ( max_call_interval = 1 )
def scrolldown ( self ):
print ( 'How to update the Scrollview widgets? (i.e. the next_key method in StartScreen )' )
# StartScreen.next_key ( self.overscroll ) ???
class StartScreen ( Screen ):
def __init__ ( self, **kwargs ):
super( StartScreen, self).__init__( **kwargs )
# Create some artificial data for this sample code to fill the GridLayout container
self.num = '11'
self.data = { }
self.keys = [ ]
lines = [ ]
for k in range ( 10, 15 ):
for v in range ( 25 ):
lines.append ( 'ONE,Key %d,%d,yxz,,white,gray,yellow,red,green,\n' % ( k, k+v ) )
self.data[ str ( k ) ] = lines
self.keys.append ( str ( k ) )
lines = [ ]
self.colour_mapping = { 'white' : ( 1,1,1,1, ),
'gray' : ( 0,0,0,.2 ),
'green' : ( 0,1,0,.3 ),
'red' : ( 1,0,0,.3 ),
'yellow' : ( 1,1,0,.3 ) }
Clock.schedule_once ( self.finish_init, 0 )
def finish_init ( self, dt ):
self.update_container ( )
# @classmethod ? But how to call self.update_container then? And the keys list is empty anyway... hmpf...
def prev_key ( self, *args ):
idx = self.keys.index ( self.num )
if idx in ( 0, len ( self.keys ) ):
print ( 'no more keys in the dict...' )
self.num = self.keys[ idx ]
else:
self.num = self.keys[ idx - 1 ]
self.update_container ( )
def next_key ( self, *args ):
idx = self.keys.index ( self.num )
try:
self.num = self.keys[ idx + 1 ]
except IndexError:
print ( 'no more keys in the dict...' )
self.num = self.keys[ idx ]
self.update_container ( )
def update_container ( self ):
hgt_row = 35
fnt_size = 15
self.ids.container.clear_widgets ( )
for line in self.data.get ( self.num ):
d1, d2, d3, d4, empty, bg1,bg2,bg3,bg4,bg5, nl = line.split( ',' )[ : ]
colour1 = self.colour_mapping.get ( bg1 )
colour2 = self.colour_mapping.get ( bg2 )
colour3 = self.colour_mapping.get ( bg3 )
colour4 = self.colour_mapping.get ( bg4 )
colour5 = self.colour_mapping.get ( bg5 )
in_c1 = TextInput ( height = hgt_row,\
text = d1,\
font_size = dp ( fnt_size ),\
size_hint = ( .1, None ),\
background_color = colour1 )
in_c2 = TextInput ( height = hgt_row,\
text = d2,\
font_size = dp ( fnt_size ),\
size_hint = ( .12, None ),\
background_color = colour2 )
in_c3 = TextInput ( height = hgt_row,\
text = d3,\
size_hint = ( .07, None ),\
background_color = colour3 )
in_c4 = TextInput ( height = hgt_row,\
size_hint = ( .09, None ),\
text = d4,\
background_color = colour4 )
in_c5 = TextInput ( height = hgt_row,\
size_hint = ( .7, None ),\
hint_text = empty,\
background_color = colour5 )
self.ids.container.add_widget ( in_c1 )
self.ids.container.add_widget ( in_c2 )
self.ids.container.add_widget ( in_c3 )
self.ids.container.add_widget ( in_c4 )
self.ids.container.add_widget ( in_c5 )
class Page2Screen ( Screen ):
pass
class ManagerWidget ( ScreenManager ):
pass
class MainApp ( App ):
pass
if __name__ == '__main__':
MainApp( ).run( )
我到底缺少什么?有什么建议吗?
解决方案
Effect 直接调用 StartScreen.prev_key() 的位置是错误的。在你的示例中,Effect 是由 ScrollView.effect_cls 生成的,因此它不知道自己属于哪个 StartScreen 实例,而 prev_key / next_key 无论如何都是实例方法。
最简单的修复办法是:不要在效果类内部调用屏幕。把屏幕绑定到 ScrollView 的 effect_y 实例上,并在那里做出响应。
from time import time
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.effects.dampedscroll import DampedScrollEffect
from kivy.metrics import dp
from kivy.clock import Clock
class Effect(DampedScrollEffect):
pass
class StartScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._last_overscroll_call = 0
self.num = '11'
self.data = {}
self.keys = []
lines = []
for k in range(10, 15):
for v in range(25):
lines.append(f'ONE,Key {k},{k+v},yxz,,white,gray,yellow,red,green,\n')
self.data[str(k)] = lines
self.keys.append(str(k))
lines = []
self.colour_mapping = {
'white': (1, 1, 1, 1),
'gray': (0, 0, 0, .2),
'green': (0, 1, 0, .3),
'red': (1, 0, 0, .3),
'yellow': (1, 1, 0, .3),
}
Clock.schedule_once(self.finish_init, 0)
def finish_init(self, dt):
self.update_container()
self.ids.scroller.effect_y.bind(overscroll=self.on_overscroll_y)
def on_overscroll_y(self, effect, value):
now = time()
if now - self._last_overscroll_call < 1:
return
if value < -100:
self._last_overscroll_call = now
self.prev_key()
elif value > 100:
self._last_overscroll_call = now
self.next_key()
def prev_key(self, *args):
idx = self.keys.index(self.num)
if idx == 0:
print('no more keys in the dict...')
else:
self.num = self.keys[idx - 1]
self.update_container()
def next_key(self, *args):
idx = self.keys.index(self.num)
if idx >= len(self.keys) - 1:
print('no more keys in the dict...')
else:
self.num = self.keys[idx + 1]
self.update_container()
def update_container(self):
hgt_row = 35
fnt_size = 15
self.ids.container.clear_widgets()
for line in self.data[self.num]:
d1, d2, d3, d4, empty, bg1, bg2, bg3, bg4, bg5, nl = line.split(',')[:]
colours = [self.colour_mapping[c] for c in (bg1, bg2, bg3, bg4, bg5)]
vals = [d1, d2, d3, d4, empty]
hints = [(.1, None), (.12, None), (.07, None), (.09, None), (.7, None)]
for i, (txt, colour, hint) in enumerate(zip(vals, colours, hints)):
w = TextInput(
height=hgt_row,
text='' if i == 4 else txt,
hint_text=txt if i == 4 else '',
font_size=dp(fnt_size),
size_hint=hint,
background_color=colour
)
self.ids.container.add_widget(w)
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。