KivyのScrollViewの中にTabbedPanelがあるとTabbedPanelからのスワイプでスクロールが効かない、に対処したの巻
はじめに
ええと、タイトルそのママです。。。
KivyでScrollViewの内側にTabbedPanelを使っていると、Tabの部分を起点にしてスワイプしてもスクロールさせてくれません。
なんかスクロールしてる時に引っかかりがあるなーと思ったらそういうことでした。
下の動画がその例で、
ScrollViewの中にLabel(青色)を縦に並べて、Label3とLabel4の間にTabbedPanelを挟んであるのですが、tab(Tab1およびTab2と表示している部分)を起点にスワイプでスクロールしようとしてもうんともすんとも言いません。
と、いうわけで、ScrollViewの内側にあるTabbedPanelのtabを起点にスワイプしたときもスクロールできるようにしますよ、という話の備忘録です。
実施環境
Python 3.9.12
Kivy 2.1.0
macOS Monterey 12.6.1
やり方
元のコードに以下の項目を追加したり置換したりします。最終的な全体のコードはコードの項にて。
・追加で必要なモジュール群をimportする
from kivy.clock import Clock
from functools import partial
from kivy.metrics import dp
from kivy.uix.widget import Widget
・ScrollViewクラスを継承したScrollViewParentを作成し、on_scroll_stop関数をオーバーライドする。
このときのon_scroll_stop関数を
def on_scroll_stop(self, touch, check_children=True):
super().on_scroll_stop(touch, check_children=False)
とする。
=> check_children=Falseとして子ScrollView内起点でのスクロールを効かせる
・ScrollViewクラスを継承したScrollViewChildrenを作成し、on_scroll_move関数をオーバーライドする。このときのon_scroll_move関数を
def on_scroll_move(self, touch):
super().on_scroll_move(touch)
touch.ud['sv.handled']['y'] = False
とする。
=> 親widgetにもy軸方向のイベントが伝わるようにする
・TabbedPanelクラスを継承したTabbedPanelRを作成する。_update_tabs関数の中にScrollViewがあるので、この部分をScrollViewChildrenに置換して関数をまるっとオーバーライドする。
少し長いのでコードはコードの項をご参照ください。
・kvファイル中の親側のScrollViewクラスをScrollViewParentクラスに置換する
・kvファイル中のTabbedPanelクラスをTabbedPanelRクラスに置換する
コード
main.pyとmain.kvを以下に示します。
追記・置換が必要な部分は
・import additional moduls 部分
・additional part from here からadditional part endまでの部分
・modified code lineのコメントの部分
追加しています。
main.py
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem
#import additional modules
from kivy.clock import Clock
from functools import partial
from kivy.metrics import dp
from kivy.uix.widget import Widget
class Otameshi(BoxLayout):
pass
#####################################
##### additional part from here #####
#####################################
#該当のwidget内の操作でスクロールするようにScrollViewクラスをオーバーライド
#適用したい親子関係のScrollViewクラスにこれらを使用する
class ScrollViewParent(ScrollView):
def on_scroll_stop(self, touch, check_children=True):
#check_children=Falseとして子ScrollView内起点でのスクロールを効かせる
super().on_scroll_stop(touch, check_children=False)
class ScrollViewChild(ScrollView):
# 親widgetにもy軸方向のイベントが伝わるようにする
def on_scroll_move(self, touch):
super().on_scroll_move(touch)
touch.ud['sv.handled']['y'] = False
#TabbedPanel内のScrollViewクラスをScrollViewChildに置換(1か所;modified code lineとコメントしている行)
class TabbedPanelR(TabbedPanel):
def _update_tabs(self, *l):
self_content = self.content
if not self_content:
return
# cache variables for faster access
tab_pos = self.tab_pos
tab_layout = self._tab_layout
tab_layout.clear_widgets()
scrl_v = ScrollViewChild(size_hint=(None, 1), always_overscroll=False) # modified code line #
tabs = self._tab_strip
parent = tabs.parent
if parent:
parent.remove_widget(tabs)
scrl_v.add_widget(tabs)
scrl_v.pos = (0, 0)
self_update_scrollview = self._update_scrollview
# update scrlv width when tab width changes depends on tab_pos
if self._partial_update_scrollview is not None:
tabs.unbind(width=self._partial_update_scrollview)
self._partial_update_scrollview = partial(
self_update_scrollview, scrl_v)
tabs.bind(width=self._partial_update_scrollview)
# remove all widgets from the tab_strip
super(TabbedPanel, self).clear_widgets()
tab_height = self.tab_height
widget_list = []
tab_list = []
pos_letter = tab_pos[0]
if pos_letter == 'b' or pos_letter == 't':
# bottom or top positions
# one col containing the tab_strip and the content
self.cols = 1
self.rows = 2
# tab_layout contains the scrollview containing tabs and two blank
# dummy widgets for spacing
tab_layout.rows = 1
tab_layout.cols = 3
tab_layout.size_hint = (1, None)
tab_layout.height = (tab_height + tab_layout.padding[1] +
tab_layout.padding[3] + dp(2))
self_update_scrollview(scrl_v)
if pos_letter == 'b':
# bottom
if tab_pos == 'bottom_mid':
tab_list = (Widget(), scrl_v, Widget())
widget_list = (self_content, tab_layout)
else:
if tab_pos == 'bottom_left':
tab_list = (scrl_v, Widget(), Widget())
elif tab_pos == 'bottom_right':
# add two dummy widgets
tab_list = (Widget(), Widget(), scrl_v)
widget_list = (self_content, tab_layout)
else:
# top
if tab_pos == 'top_mid':
tab_list = (Widget(), scrl_v, Widget())
elif tab_pos == 'top_left':
tab_list = (scrl_v, Widget(), Widget())
elif tab_pos == 'top_right':
tab_list = (Widget(), Widget(), scrl_v)
widget_list = (tab_layout, self_content)
elif pos_letter == 'l' or pos_letter == 'r':
# left or right positions
# one row containing the tab_strip and the content
self.cols = 2
self.rows = 1
# tab_layout contains two blank dummy widgets for spacing
# "vertically" and the scatter containing scrollview
# containing tabs
tab_layout.rows = 3
tab_layout.cols = 1
tab_layout.size_hint = (None, 1)
tab_layout.width = tab_height
scrl_v.height = tab_height
self_update_scrollview(scrl_v)
# rotate the scatter for vertical positions
rotation = 90 if tab_pos[0] == 'l' else -90
sctr = Scatter(do_translation=False,
rotation=rotation,
do_rotation=False,
do_scale=False,
size_hint=(None, None),
auto_bring_to_front=False,
size=scrl_v.size)
sctr.add_widget(scrl_v)
lentab_pos = len(tab_pos)
# Update scatter's top when its pos changes.
# Needed for repositioning scatter to the correct place after its
# added to the parent. Use clock_schedule_once to ensure top is
# calculated after the parent's pos on canvas has been calculated.
# This is needed for when tab_pos changes to correctly position
# scatter. Without clock.schedule_once the positions would look
# fine but touch won't translate to the correct position
if tab_pos[lentab_pos - 4:] == '_top':
# on positions 'left_top' and 'right_top'
sctr.bind(pos=partial(self._update_top, sctr, 'top', None))
tab_list = (sctr, )
elif tab_pos[lentab_pos - 4:] == '_mid':
# calculate top of scatter
sctr.bind(pos=partial(self._update_top, sctr, 'mid',
scrl_v.width))
tab_list = (Widget(), sctr, Widget())
elif tab_pos[lentab_pos - 7:] == '_bottom':
tab_list = (Widget(), Widget(), sctr)
if pos_letter == 'l':
widget_list = (tab_layout, self_content)
else:
widget_list = (self_content, tab_layout)
# add widgets to tab_layout
add = tab_layout.add_widget
for widg in tab_list:
add(widg)
# add widgets to self
add = self.add_widget
for widg in widget_list:
add(widg)
#####################################
##### additional part end #####
#####################################
class MainApp(App):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
main.kv
<Otameshi>
ScrollViewParent: #modified code line
do_scroll_x: False
do_scroll_y: True
size:self.size
BoxLayout:
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(5)
Label:
text: 'Label1'
Label:
text: 'Label2'
Label:
text: 'Label3'
TabbedPanelR: #modified code line
padding: dp(2),dp(2)
spacing: dp(5)
do_default_tab:False
size_hint_y: None
height:dp(215)
tab_height: dp(100)
tab_width: self.width/3
TabbedPanelItem:
text: 'Tab1'
Button:
text:'Tab1 Item'
TabbedPanelItem:
text: 'Tab2'
Button:
text:'Tab2 Item'
Label:
text: 'Label4'
Label:
text: 'Label5'
Label:
text: 'Label6'
Label:
text: 'Label7'
Label:
text: 'Label8'
<Label>
size_hint_y: None
font_size: sp(40)
text_size: None, None
size: 100, 200
padding: 10, 10
canvas.before:
Color:
rgba:(48/255,84/255,150/255,1)
Rectangle:
size: self.size
pos: self.pos
結果
で、実行してみると、、、
これで、ScrollViewの内側にあるTabbedPanelのtabを起点にスワイプしたときもスクロールできるようになりました。
めでたしめでたし。
おしまい。
参考にしたサイトなど
https://stackoverflow.com/questions/64470608/python-kivy-nested-scrollviews
https://kivy.org/doc/stable/api-kivy.uix.scrollview.html
https://github.com/kivy/kivy/blob/master/kivy/uix/scrollview.py
https://github.com/kivy/kivy/blob/master/kivy/uix/tabbedpanel.py
・Kivy 2.1.0のライセンス
MIT License
Copyright (c) 2010-2022 Kivy Team and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
本記事上のサンプルコードについては(新しいことほぼないけど)、
上記のMITライセンス条文にCopyright ©︎ 2022 buを付加したものとさせてください。
ライセンス遵守下でご自由にお使いください。
ちょっと広告です
https://business.xserver.ne.jp/
https://www.xdomain.ne.jp/
★LOLIPOP★
.tokyo
MuuMuu Domain!