kivyMDチュートリアル其の参什捌 Components - Selection篇

ハロー、Qiita!いかがお過ごしでしょうか。

今週も相変わらず、KivyMDの触れ込みをやっております。お時間ありましたら
お付き合いください。

ニュース的には、特にとりとめもないのですが、変わったことでいうと衆議院
解散くらいでしょうか。まさかの政治ネタが2週間連続という。投票結果がどう
なるかということをデータ分析で予想するというのも面白そうですね。私は、
知識がおぼつかず見ているだけになりそうですが...

ということで今週も元気にやっていきましょう。今週はSelection篇となります。

## Selection

マテリアルデザインのリンクは見ておいて頂けたらと思います。

マニュアルの最初の説明は以下のようになっています。

> Selection refers to how users indicate specific items they intend to take action on.

ここも個人の考え方をいれないよう、依頼をしてみます。

> 選択とは、ユーザーが行動を起こすための具体的な項目を示すことです。

選択 = Selection

となっている以外はどうでしょうか。んー、このまま説明されても少し抽象的かなと
思われるかもしれません。あとは、英語の部分は英語のままに翻訳するというのは
難しそうですね。ある程度その知識を知らないと、英語のエキスパートの方でも
翻訳を間違えそう...

ということでこの説明がなんなのか分かるためにも先へ進みましょう。すぐ下の
キャプチャを見るとなんとなく分かる気もしますが。

### Entering selection mode

selection modeへ入れるということでしょうか。ここで、Selectionコンポーネント
はselection modeというのがあるのが分かります。実装の方は後に出てくるので一旦
そこは飛ばします。

で、また説明がありますね。

> To select an item and enter selection mode, long press the item:

なにやら、selection modeにするにはアイテムを長押しする必要があるらしいです。
キャプチャはそのまま長押しをして、selection modeを入れていますね。

### Exiting selection mode

今度はselection modeを出る、すなわちselection modeをoffにするということで
しょうか。こちらも説明があります。

> To exit selection mode, tap each selected item until they’re all deselected:

モードを出る、オフにするときにはもう一度アイテムをタップすればよいということ
でしょうか。これもキャプチャはそのままのことを指しています。

### Larger selections

こちらは以下のように、まだ機能がないよとありますね。

:::note
This feature is missing yet.
:::

### Events

説明というかUsageみたいなコードでは2つのイベントがあるよと示されています。

```python
def on_selected(self, instance_selection_list, instance_selection_item):
    '''Called when a list item is selected.'''

def on_unselected(self, instance_selection_list, instance_selection_item):
    '''Called when a list item is unselected.'''
```

on_(un)selectedメソッドがそれぞれ、Entering/Exiting selection mode
となるということでしょうか。これも後のサンプルコードで出てくるでしょう。第2
引数にselection_listのインスタンス、第3引数にselection_itemのインスタ
ンスがあるんだろうなとしか現状わかりません。

### Example with TwoLineAvatarListItem

ということで、まずは最初のサンプルコードになります。こちらは全文を掲載する
こととします。

```python:xxxviii/selection_with_listitem.py
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.utils import get_color_from_hex

from kivymd.app import MDApp
from kivymd.uix.list import TwoLineAvatarListItem

KV = '''
<MyItem>
    text: "Two-line item with avatar"
    secondary_text: "Secondary text here"
    _no_ripple_effect: True

    ImageLeftWidget:
        source: "data/logo/kivy-icon-256.png"


MDBoxLayout:
    orientation: "vertical"

    MDToolbar:
        id: toolbar
        title: "Inbox"
        left_action_items: [["menu"]]
        right_action_items: [["magnify"], ["dots-vertical"]]
        md_bg_color: 0, 0, 0, 1

    MDBoxLayout:
        padding: "24dp", "8dp", 0, "8dp"
        adaptive_size: True

        MDLabel:
            text: "Today"
            adaptive_size: True

    ScrollView:

        MDSelectionList:
            id: selection_list
            spacing: "12dp"
            overlay_color: app.overlay_color[:-1] + [.2]
            icon_bg_color: app.overlay_color
            on_selected: app.on_selected(*args)
            on_unselected: app.on_unselected(*args)
            on_selected_mode: app.set_selection_mode(*args)
'''


class MyItem(TwoLineAvatarListItem):
    pass


class Example(MDApp):
    overlay_color = get_color_from_hex("#6042e4")

    def build(self):
        return Builder.load_string(KV)

    def on_start(self):
        for i in range(10):
            self.root.ids.selection_list.add_widget(MyItem())

    def set_selection_mode(self, instance_selection_list, mode):
        if mode:
            md_bg_color = self.overlay_color
            left_action_items = [
                [
                    "close",
                    lambda x: self.root.ids.selection_list.unselected_all(),
                ]
            ]
            right_action_items = [["trash-can"], ["dots-vertical"]]
        else:
            md_bg_color = (0, 0, 0, 1)
            left_action_items = [["menu"]]
            right_action_items = [["magnify"], ["dots-vertical"]]
            self.root.ids.toolbar.title = "Inbox"

        Animation(md_bg_color=md_bg_color, d=0.2).start(self.root.ids.toolbar)
        self.root.ids.toolbar.left_action_items = left_action_items
        self.root.ids.toolbar.right_action_items = right_action_items

    def on_selected(self, instance_selection_list, instance_selection_item):
        self.root.ids.toolbar.title = str(
            len(instance_selection_list.get_selected_list_items())
        )

    def on_unselected(self, instance_selection_list, instance_selection_item):
        if instance_selection_list.get_selected_list_items():
            self.root.ids.toolbar.title = str(
                len(instance_selection_list.get_selected_list_items())
            )


Example().run()
```

少し長いので、今日は久しぶりに小分けにして触れ込みたいと思います。

#### import文

さっそくこちらに入っていきます。

```python
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.utils import get_color_from_hex

from kivymd.app import MDApp
from kivymd.uix.list import TwoLineAvatarListItem
```

目新しいのはAnimationとget_color_from_hexオブジェクトではないでしょうか。
ただし、AnimationはBehaviorsのところになるので、ここでは詳細は端折ります。

ちなみに、get_color_from_hexについてはクラス側にて登場します。

#### kv側

打って変わり、kv側に入ります。

```python
KV = '''
<MyItem>
    text: "Two-line item with avatar"
    secondary_text: "Secondary text here"
    _no_ripple_effect: True

    ImageLeftWidget:
        source: "data/logo/kivy-icon-256.png"


MDBoxLayout:
    orientation: "vertical"

    MDToolbar:
        id: toolbar
        title: "Inbox"
        left_action_items: [["menu"]]
        right_action_items: [["magnify"], ["dots-vertical"]]
        md_bg_color: 0, 0, 0, 1

    MDBoxLayout:
        padding: "24dp", "8dp", 0, "8dp"
        adaptive_size: True

        MDLabel:
            text: "Today"
            adaptive_size: True

    ScrollView:

        MDSelectionList:
            id: selection_list
            spacing: "12dp"
            overlay_color: app.overlay_color[:-1] + [.2]
            icon_bg_color: app.overlay_color
            on_selected: app.on_selected(*args)
            on_unselected: app.on_unselected(*args)
            on_selected_mode: app.set_selection_mode(*args)
'''
```

こちらに関して言えば、特に目新しいところというとMDSelectionListとなる
でしょうか。それ以外に関しては、以下ページもしくはマニュアルの方をご覧に
おなり下さい。あとは、kv側を見てマニュアルともしくは動かしてみて、ここが
該当するんだね〜と照らし合わせるのも面白いかと。

https://qiita.com/virty/items/3871b85bc67179d67b30

https://qiita.com/virty/items/8475f11f314b76af21e1

https://qiita.com/virty/items/3c726e2a7faa694fb54d

MDSelectionListはというと、今回の大目玉となりますね。これだけ見ると
特に変わった形はしていないと思われます。overlayはselection modeに
入ったときに変わる色となります。ですが、overlay_colorプロパティの値
である計算式は少し不明というのが現状です。また分かり次第、お知らせする
かもしれません。

on_(un)selectedプロパティに関しては先述のEventのところでありましたね。
プロパティ自体は記載がありませんでしたが、実体(メソッド)が書かれていました。
値にその実体となるコールバックメソッドを指定しています。on_selected_mode
もおなじようにコールバックメソッドを置いています。

あとは細かいところですが、on_selectedプロパティのコールバックメソッドで
指定するメソッドで多変数引数をこのように指定することも必見でしょうか。

#### クラス側

残りはクラス側となります。こちらも抜粋します。

```python
class MyItem(TwoLineAvatarListItem):
    pass


class Example(MDApp):
    overlay_color = get_color_from_hex("#6042e4")

    def build(self):
        return Builder.load_string(KV)

    def on_start(self):
        for i in range(10):
            self.root.ids.selection_list.add_widget(MyItem())

    def set_selection_mode(self, instance_selection_list, mode):
        if mode:
            md_bg_color = self.overlay_color
            left_action_items = [
                [
                    "close",
                    lambda x: self.root.ids.selection_list.unselected_all(),
                ]
            ]
            right_action_items = [["trash-can"], ["dots-vertical"]]
        else:
            md_bg_color = (0, 0, 0, 1)
            left_action_items = [["menu"]]
            right_action_items = [["magnify"], ["dots-vertical"]]
            self.root.ids.toolbar.title = "Inbox"

        Animation(md_bg_color=md_bg_color, d=0.2).start(self.root.ids.toolbar)
        self.root.ids.toolbar.left_action_items = left_action_items
        self.root.ids.toolbar.right_action_items = right_action_items

    def on_selected(self, instance_selection_list, instance_selection_item):
        self.root.ids.toolbar.title = str(
            len(instance_selection_list.get_selected_list_items())
        )

    def on_unselected(self, instance_selection_list, instance_selection_item):
        if instance_selection_list.get_selected_list_items():
            self.root.ids.toolbar.title = str(
                len(instance_selection_list.get_selected_list_items())
            )


Example().run()
```

MyItemクラスはどのクラスを継承させるかということで定義されています。
本節で名前が記載のある通り、TwoLineAvatarListItemが継承されています。

そして残るは、Exampleクラスになりますがこちらはメソッドごとに触れ込んで
いきたいと思います。よくあるbuildメソッドについては端折ります。

あ、あと忘れていましたが、overlay_colorは選択状態になったときの色を
指定しています。ここでは紫っぽい色を指定しています。

##### on_start

on_startメソッドになります。これも一旦抜粋してみます。

```python
    def on_start(self):
        for i in range(10):
            self.root.ids.selection_list.add_widget(MyItem())
```

ここに関しては、kv・クラス側で記載しているMyItemをオブジェクト生成して
います。for文である通り、10個のリストアイテムが得られます。

##### set_selection_mode

同様、こちらも抜粋してみます。

```python
    def set_selection_mode(self, instance_selection_list, mode):
        if mode:
            md_bg_color = self.overlay_color
            left_action_items = [
                [
                    "close",
                    lambda x: self.root.ids.selection_list.unselected_all(),
                ]
            ]
            right_action_items = [["trash-can"], ["dots-vertical"]]
        else:
            md_bg_color = (0, 0, 0, 1)
            left_action_items = [["menu"]]
            right_action_items = [["magnify"], ["dots-vertical"]]
            self.root.ids.toolbar.title = "Inbox"

        Animation(md_bg_color=md_bg_color, d=0.2).start(self.root.ids.toolbar)
        self.root.ids.toolbar.left_action_items = left_action_items
        self.root.ids.toolbar.right_action_items = right_action_items
```

まず、if-elseであるのが、modeで条件を定めていることと名前からもある通り
selection modeの設定となりますね。まずselection modeであれば、over-
lay_colorを背景(md_bg_colorプロパティに代入)に設定してleft/right_
action_itemsにアイコンなどを差し替えます。left/right_action_items
プロパティがなんなのか分からない方はもう1度Toolbar篇をご覧ください。
\# elseについては初期画面のときの設定になりますね

それらが終わると、アニメーションをして、left/right_action_itemsそれ
ぞれに設定したものを設定しておきます。

あと、少し漏れているところというと、言わずもがなですがcloseアイコンのとき
は全て選択状態をクリアするようなコールバックメソッドを指定しています。

##### on_selected/on_unselected

これらはほとんど同じようなので、まとめておきます。で、同じように抜粋をして
みます。

```python
    def on_(un)selected(self, instance_selection_list, instance_selection_item):
        if instance_selection_list.get_selected_list_items(): # unselectedのときだけ
            self.root.ids.toolbar.title = str(
                len(instance_selection_list.get_selected_list_items())
            )

```

設定する内容としてはほとんど同じようなものです。どちらも、選択状態のとき、
ツールバーのタイトルにリストアイテムの長さを逐一取得するということになる
かと思われます。ただし、on_unselectedメソッドのときは、instance_se-
lection_listのget_selected_list_itemsメソッドが有効のときにその設
定をしているという事がわかります。

これをしないとどうなるか結果の方で見てみましょう。

### 結果

はい、というわけで少し触れ込みが長くなりましたが、一旦この辺で動作を見る
ことにします。と言ってもマニュアルで大方動作も見れますが。。

まずは、正常なところから。

![178.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/e6143a8d-b965-d87c-1492-1157e4c05bbb.gif)

特に問題がないようですね。ほぼマニュアルと同じ挙動です。

で、あとはon_unselectedメソッドに関してのところで、ifの条件をなくすと
どうなるか試してみたところ動作的には問題がなさそうです。まぁこれもprint
デバッグしてわかったことですが、get_selected_list_items()は選択状態
のリストアイテムオブジェクトを取得しています。

なのでこれがあり次第ということなのですが、これはmodeとかを条件にできない
のか...と15秒くらい考えにふけていました。ですが、仕様に関しては我々が口
出しできません。これはこれでと思うしかありません。
\# ご指摘はじゃんじゃんお寄せ頂ければ嬉しいです!

### Example with FitImage

というわけで、一区切りはついたのですが、こちらもあるのでさくさく進んでいき
たいと思います。

コードも触れ込みたいところではあるのですが、これといってとりとめもありません。
差分はちらちらある状況ではあるんですけどね。これも理由がありまして、なんと、
コンポーネントにFitImageが加わりました！:tada::tada::tada:

喜ばしいことは間違いありません、が、なんだかなぁ、、んー、これは言葉に出せない
ことですね。。

ともあり、FitImageに関してはまた別の機会ということにします。完全にこれはkivy
側の方かと思っていました。

ということなので、メインの差分をここで触れておきます。それが何なのかということ
になるのですが、progress_round_colorになりますね。これは選択モードへ入るとき
にグルグルする円マークみたいなものがありました。まさしく、名前の通りですね。

とりあえず、コードについてはほとんどとりとめもなくなったので端折ります。

### 結果

はい、ということで本日最後の動作確認となります。さっそく見ていきましょう。

![179.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/e77e9340-d4f0-70da-3183-4ba6d1e99c93.gif)

最近顔馴染みのところから画像を出力してみました。引用元としては以下となります。

https://kumamine.blogspot.com/2019/12/blog-post_27.html

これもマニュアルと比較すると問題ないようすですね。途中消そうとしているけど...
少しその後、色あたりを見ていて触ったりしているのでGitHubのコードは変わって
いるかもしれませんが、ご了承頂ければと思います。まぁ変化点がどこにあるのか
みることもよいかもしれませんね。

### API - kivymd.uix.selection.selection

ということで、まとめに入る前にAPIの方をおさらいしておきます。

#### class kivymd.uix.selection.selection.MDSelectionList(**kwargs)

kv・クラス側両方に出てきたウィジェットですね。

##### Events

`on_selected`

> Called when a list item is selected.

`on_unselected`

> Called when a list item is unselected.

イベントとしては2つありましたね。使い方は両方とも同じようなものです。

`selected_mode`

> List item selection mode. If True when clicking on a list item, it will be selected.

> selected_mode is an BooleanProperty and defaults to False.

今度はプロパティになります。このプロパティはon_selected_modeプロパティの
コールバックメソッドの引数で自動的に呼ばれるものでした。あれっ、名前違うくね?
となった方はするどい。鋼の錬金術師だとにらみつけられる立ち位置にいそうです。
\# キャラの名前わすれた

そうです、引数としてはmodeと呼ばれていました。ただし、これは引数なので、
プロパティ名としてはこれで合っている可能性はありますね。ただ、どちらが正しい
かと言われるとんーとなる次第です。。もしかするとプロパティの設定がいらないもの
なのかも。

`icon`

> Name of the icon with which the selected list item will be marked.

> icon is an StringProperty and defaults to ‘check’.

`icon_pos`

> The position of the icon that will mark the selected list item.

> icon_pos is an ListProperty and defaults to [].

`icon_bg_color`

> Background color of the icon that will mark the selected list item.

> icon_bg_color is an ColorProperty and defaults to [1, 1, 1, 1].

`icon_check_color`

> Color of the icon that will mark the selected list item.

> icon_check_color is an ColorProperty and defaults to [1, 1, 1, 1].

これらはアイコン関連のプロパティになりますね。使用していないものもありますが、
アイコン指定もできるんだーとなったのでまとめて記載しています。

`overlay_color`

> The overlay color of the selected list item..

> overlay_color is an ColorProperty and defaults to [0, 0, 0, 0.2]].

選択状態のときの色指定ですね。overlayとあるので上塗りしているということ
でしょうか。

`progress_round_size`

> Size of the spinner for switching of selected_mode mode.

> progress_round_size is an NumericProperty and defaults to dp(46).

`progress_round_color`

> Color of the spinner for switching of selected_mode mode.

> progress_round_color is an NumericProperty and defaults to None.

グルグルするものですね。どうやらサイズも変更できるみたいです。

`get_selected(self)`

> Returns True if at least one item in the list is checked.

なにやら、選択状態かどうか調べられるメソッドがありそうですね。ただし、
少し試しただけではうまくいきませんでした。引数でなにがしかを指定する
必要があるようです。

`get_selected_list_items(self)`

> Returns a list of marked objects:

> [<kivymd.uix.selection.SelectionItem object>, …]

こちらが使われていたもので、上と同じようなものになります。ただし、こちら
は選択したオブジェクトをリストで返すようになっています。

`unselected_all(self)`

`selected_all(self)`

`on_selected(self, *args)`

> Called when a list item is selected.

`on_unselected(self, *args)`

> Called when a list item is unselected.

こちらも重要なものです。selected_allメソッドなんかは全選択する際に
使われそうですね。

## まとめ

はい、ということで今日はこれにて終わりたいと思います！早めに切り上げようかと
思ってましたが。。

さていかがだったでしょうか。保持しているアイテムの管理なんかはうってつけのウィ
ジェットになるかと思われます。ちょっと自分のスマホなんかを見ると、Googleフォト
なんかは同じSelectionコンポーネントではなかろうかと見込んでいます。

来週は、個人的には密かに待望のSnackbar篇となります。これを使えるとなるとまた
アプリの幅が広がりそうですね。ということで今週はこの辺りで。

それでは、ごきげんよう。

## 参照

> Components » Selection
> https://kivymd.readthedocs.io/en/latest/components/selection/

> DeepL 翻訳ツール
> https://www.deepl.com/ja/translator