(kivy)Recycleviewを使用した表におけるToggleButtonの挙動について
- 評価
- クリップ 0
- VIEW 3,248
やりたいこと
kivyを使用したGUIにおいて、スクロールできる表を作成しています。
表のうち、いずれかのセルをクリックするとそのセルを含む行が選択された状態になり、再度クリックすることで画面遷移をしたいです。
現状
Recycleviewを使用して表を作成し、表のセルをToggleButtonで表現しました。
ToggleButtonが押されたら、その行に含まれるToggleButtonの状態を変化されるように作っております。
困っていること
Recycleviewで作成した表にToggleButtonを埋め込み、ボタンを押下したところ、意図しないボタンまで押下された状態になりました。
更に表を何回かスクロールしたところ、押下したボタンの状態が勝手に変化していることを確認しました。
これはkivy側の仕様なのでしょうか?
from random import sample
from string import ascii_lowercase
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
kv = """
<Row@BoxLayout>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
value: ''
ToggleButton:
text: root.value
<Test>:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
rv: rv
orientation: 'vertical'
GridLayout:
cols: 3
rows: 2
size_hint_y: None
height: dp(108)
padding: dp(8)
spacing: dp(16)
Button:
text: 'Populate list'
on_press: root.populate()
Button:
text: 'Sort list'
on_press: root.sort()
Button:
text: 'Clear list'
on_press: root.clear()
BoxLayout:
spacing: dp(8)
Button:
text: 'Insert new item'
on_press: root.insert(new_item_input.text)
TextInput:
id: new_item_input
size_hint_x: 0.6
hint_text: 'value'
padding: dp(10), dp(10), 0, 0
BoxLayout:
spacing: dp(8)
Button:
text: 'Update first item'
on_press: root.update(update_item_input.text)
TextInput:
id: update_item_input
size_hint_x: 0.6
hint_text: 'new value'
padding: dp(10), dp(10), 0, 0
Button:
text: 'Remove first item'
on_press: root.remove()
RecycleView:
id: rv
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
"""
Builder.load_string(kv)
class Test(BoxLayout):
def populate(self):
self.rv.data = [{'value': ''.join(sample(ascii_lowercase, 6))}
for x in range(50)]
def sort(self):
self.rv.data = sorted(self.rv.data, key=lambda x: x['value'])
def clear(self):
self.rv.data = []
def insert(self, value):
self.rv.data.insert(0, {'value': value or 'default value'})
def update(self, value):
if self.rv.data:
self.rv.data[0]['value'] = value or 'default new value'
self.rv.refresh_from_data()
def remove(self):
if self.rv.data:
self.rv.data.pop(0)
class TestApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TestApp().run()
kivyのサンプルプログラムより抜粋・一部変更あり
備考
python: 3.4.2
kivy: 1.10.0
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+1
自分なりに調べた結果、いくつか面白い事が分かりました。
検証コード
以下は上の検証コードを実行して、
valueにAを入れて「Insert new item」を押す。
valueにABを入れて「Insert new item」を押す。
同じ要領で ABC, ABCD, ABCDE と追加していった時の標準出力です。
----insert----
Rowの順番 []
add_widget: 377 () {}
----insert----
Rowの順番 [('377', 'A')]
remove_widget: 377 () {}
add_widget: 377 () {}
add_widget: 395 () {}
----insert----
Rowの順番 [('395', 'A'), ('377', 'AB')] # A
remove_widget: 395 () {}
remove_widget: 377 () {}
add_widget: 395 () {}
add_widget: 377 () {}
add_widget: 413 () {}
----insert----
Rowの順番 [('413', 'A'), ('377', 'AB'), ('395', 'ABC')] # B
remove_widget: 413 () {}
remove_widget: 377 () {}
remove_widget: 395 () {}
add_widget: 413 () {}
add_widget: 377 () {}
add_widget: 395 () {}
add_widget: 431 () {}
----insert----
Rowの順番 [('431', 'A'), ('395', 'AB'), ('377', 'ABC'), ('413', 'ABCD')] # C
remove_widget: 431 () {}
remove_widget: 395 () {}
remove_widget: 377 () {}
remove_widget: 413 () {}
add_widget: 431 () {}
add_widget: 395 () {}
add_widget: 377 () {}
add_widget: 413 () {}
add_widget: 449 () {}
出力中にある数字はWidget(Row)のuid(固有のID)です。
一つ分かったのは新しいRowが追加されるたびに、それまでにあったRowの順番が反転してることです。例えば#Aでは 395,377 の順なのが #Bでは新しく追加された413の後に 377,395 の順に(つまり逆順に)並んでいます。#Bから#Cにかけても同じです。
二つ目は各Rowに割り当てられているvalueが固定されてないことで、#Bでは377に'AB'が割り当てられているのに#Cでは'ABC'が割り当てられています。
内部でのRowの順番の変化とRowに割り当てるvalueの変化がToggleButtonのOn-Offが切り替わった様に見える原因だと思います。
更に表を何回かスクロールしたところ、押下したボタンの状態が勝手に変化していることを確認しました。
これも出力をみるとスクロール中に激しくadd_widget()とremove_widget()が呼び出されてました。
ここからは個人的な予想で検証はしていないんですが、RecycleViewは巨大な量のデータを扱えることをうりにしているので、その為の処理負荷対策なんじゃないかと思います。例えばスクロールによって'X'というvalueを表示する必要が無くなり、代わりに'Y'というvalueを表示する必要が生じた時、RecycleViewは’X'の表示に使っていたRowを’Y'の表示に使い回してRowのインスタンスの数を節約しているんじゃないかと。
スクロールの部分はともかくinsertによってvalueの割り当て直しが起きているのは事実なので、valueとそれに対応するRowは固定されない前提で扱わないといけないと言えると思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
確かにそんな感じだったと記憶しています。
on_press
om_release
どちらにしても触ったところで判定を返すようになってるはずです。
まだ試してませんが、on_touch_系を使うとより厳格な指示にできるかもしれません。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.22%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2017/11/16 09:41
納得と理解ができましたので、ベストアンサーとさせていただきます。