kivyMDチュートリアル其の弍什壱 Components - List篇

みなさん、おはこんばんは。
いかが、お過ごしでしょうか。

梅雨もいつの間にか開けて(開けてないというかきていない)、
夏らしい季節になってきましたね。

最近、結構大規模なネットワーク障害がありましたね。Fastlyという会社は世に大きく
知られたのではないでしょうか(自分も全然知らなかった)。

でも、安心してください。ネットがなくならない(もっというとQiita自体)以上はこの
チュートリアルはなくなることはありません。なくなればどこかにお引っ越ししないと
ですね。\#あ、まずい、バックアップとらないと

まぁ、そんななかなか起こり得ないこと考えてもしょうがないので、チュートリアルを
充実させることを優先します。ということで、今日も元気にやっていきましょう。今日は
List篇となります。

## List

恒例行事で端折ることはさながら、今日はいつもと事情が異なっていますね。
なんだか説明文がとても長い様子です。

>The class MDList in combination with a BaseListItem like OneLineListItem 
> will create a list that expands as items are added to it, working nicely 
> with Kivy’s ScrollView.

> Due to the variety in sizes and controls in the Material Design spec, 
> this module suffers from a certain level of complexity to keep the widgets compliant, 
> flexible and performant.

> For this KivyMD provides list items that try to cover the most common usecases, 
> when those are insufficient, there’s a base class called BaseListItem which you 
> can use to create your own list items. This documentation will only cover the 
> provided ones, for custom implementations please refer to this module’s source code.

長いですね・・・

最初の段落は、MDListとOneLineListItemを組み合わせることによってListを構成して
いるようです。んで、それらはScrollViewとうまく連携されると。

次の段落は、MaterialDesignと適合するためにKivyMD側では煩わしさを感じながらも複雑性が
必要となるということですかね。理由としてはモジュールの準拠や柔軟性などを維持するためだとか。

最後の段落は、これから出てくる最もよく使われそうな使用例を用意しているが、カスタムリストを
作りたいというわがままな方にBaseListItemというカスタム用の基底クラスを持っているぜという
ことですね。で、使用例はサンプルコードを見てちょということが言われています。

まぁ、分かった方はいるかもですが、これも恒例行事で依頼を出しています。
ちょっとそっちも見たいのだがという方に依頼内容を以下にまとめておきます。
\# どっちかでいいんじゃねというロジハラはなしの方向で

> MDListクラスとOneLineListItemのようなBaseListItemを組み合わせることで、
> アイテムが追加されると拡大するリストが作成され、KivyのScrollViewとうまく連携します。

> Material Design仕様ではサイズやコントロールが多様であるため、このモジュールでは、
> ウィジェットの準拠性、柔軟性、パフォーマンスを維持するために、ある程度の複雑さが必要となります。

> このため、KivyMDは最も一般的な使用例をカバーしようとするリストアイテムを提供していますが、
> それらが不十分な場合は、独自のリストアイテムを作成するために使用できるBaseListItemと
> 呼ばれる基本クラスがあります。このドキュメントでは、提供されているもののみをカバーしており、
> カスタム実装については、このモジュールのソースコードを参照してください。

※ 依頼内容も内容で正確な期待結果とはならないのであくまで参考程度にしておいてもらえれば

ちょっと、まだ長いですが説明は続きます。

> KivyMD provides the following list items classes for use:

KivyMD側が用意しているのは以下だぜという紹介があります。

### Text only ListItems

* OneLineListItem
* TwoLineListItem
* ThreeLineListItem

### ListItems with widget containers

なにやら、まだ喋らせろということを言われていますね。これでは、まるで話が長い上司
ではないですか。おっと、失礼。今のことはなかったことに・・・

> These widgets will take other widgets that inherit from ILeftBody, 
> ILeftBodyTouch, IRightBody or IRightBodyTouch and put them in 
> their corresponding container.

> As the name implies, ILeftBody and IRightBody will signal that 
> the widget goes into the left or right container, respectively.

> ILeftBodyTouch and IRightBodyTouch do the same thing, except 
> these widgets will also receive touch events that occur within 
> their surfaces.

> KivyMD provides base classes such as ImageLeftWidget, ImageRightWidget, 
> IconRightWidget, IconLeftWidget, based on the above classes.

なにやら、また基底クラスがあるようですね。ILeftBody、ILeftBodyTouch、IRightBody、
IRightBodyTouchやImageLeftWidget、ImageRightWidget、IconRightWidget、Icon-
LeftWidgetがあるようです。詳しい内容は依頼結果を以下に載せておきますので適宜参照して
もらえれば。

> これらのウィジェットは、ILeftBody、ILeftBodyTouch、IRightBody、IRightBodyTouchを
> 継承した他のウィジェットを、それぞれ対応するコンテナに入れます。

> その名の通り、ILeftBodyとIRightBodyはそれぞれ、ウィジェットが左または右のコンテナに入ることを
> 知らせます。

> ILeftBodyTouchとIRightBodyTouchは同じことをしますが、これらのウィジェットは表面内で発生する
> タッチイベントも受け取ります。

> KivyMDでは、これらのクラスをベースにImageLeftWidget、ImageRightWidget、IconRightWidget、
> IconLeftWidgetなどのベースクラスを用意しています。

次は、具体的なリストの種類で左にコンテンツを仕込むのは
これらだよーということを言われています。

> Allows the use of items with custom widgets on the left.

* OneLineAvatarListItem
* TwoLineAvatarListItem
* ThreeLineAvatarListItem
* OneLineIconListItem
* TwoLineIconListItem
* ThreeLineIconListItem

次も同様ですね。左右どちらでもコンテンツを仕込めるようです。

> It allows the use of elements with custom widgets on the left and the right.

* OneLineAvatarIconListItem
* TwoLineAvatarIconListItem
* ThreeLineAvatarIconListItem

ではこれから実際に、これらも含めサンプルコードで確認してみましょう。

### Usage

まずは、簡単なリストの使い方ですね。テキストのみのリストアイテムが載っけられています。

```python:xxi/onelinelistitem.py
from kivy.lang import Builder

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

KV = '''
ScrollView:

    MDList:
        id: container
'''


class Test(MDApp):
    def build(self):
        return Builder.load_string(KV)

    def on_start(self):
        for i in range(20):
            self.root.ids.container.add_widget(
                OneLineListItem(text=f"Single-line item {i}")
            )

Test().run()
```

このあと実際に実行してみる結果と同じコード、つまりマニュアルと同等です。
まぁ、いたってシンプルですね。ピックアップすると以下のようになるでしょうか。

```python

(略)

from kivymd.uix.list import OneLineListItem

KV = '''
ScrollView:

    MDList:
        id: container
'''

(略)

        for i in range(20):
            self.root.ids.container.add_widget(
                OneLineListItem(text=f"Single-line item {i}")
            )

```

Testクラスのon_startメソッドで必要になるので、インポート文は必要ということは
当然で、まず注目ポイントはkv側ですね。冒頭の説明文通り、ScrollView・MDListが
持ち合わさっています。これはもうListパターンと名付けてもよいくらいのものですね。

あとは、idを定義して(ここではcontainer)クラス側でOneLineListItemオブジェクトを
生成してcontainerに流し込んでもよし、MDList配下に直置きしてもよしという感じでしょうか。
まぁこれは設計次第ですが、個人的には設定内容とかで決め打ちしているのであれば直置き、そう
ではなく何十・何百というコンテンツを仕込むのであればクラス側で生成する方法をという感じで
しょうか。

まぁ、なんにせよよく分からんという方は上記パターン化して覚えておきましょう。

### 実行結果

本日もコード例が多いので、結果をコードごとに貼り付けておきます。

![102.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/202451cf-94c0-9ffb-1fb4-4a136948e11f.png)

![103.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/c571d146-3a80-e9fa-b778-1fb391b48ab3.png)

確認ヨシ！という感じでしょうか。すみません、面白みがほぼ0に近いもので・・・

### Events of List

こちら側には、先程言っていた直置きするサンプルコードが載せられていますね。
サンプルをそのままずっと載せるのは面白みが0なので、少し改変をします。

先に少し説明をしておきますと、ListItemの種類を全て載っけたコードにしてみました。
OneLineListItemはさっき出てきてたんですけどね・・・まぁ、でも直置きパターンは
初登場ということで再度載っけておきます。

```python:xxi/listitem_iroiro.py
from kivy.lang import Builder

from kivymd.app import MDApp

KV = '''
ScrollView:

    MDList:
        
        OneLineListItem:
            text: "Single-line item"

        TwoLineListItem:
            text: "Two-line item"
            secondary_text: "Secondary text here"

        ThreeLineListItem:
            text: "Three-line item"
            secondary_text: "This is a multi-line label where you can"
            tertiary_text: "fit more text than usual"

(略)

        OneLineAvatarIconListItem:
            on_release: print("Click 2!")

            IconLeftWidget:
                icon: "gitlab"
'''


class MainApp(MDApp):
    def build(self):
        return Builder.load_string(KV)


MainApp().run()
```

こちらは、一部コードを省略しています。安心してください、マニュアルで記載のパーツは
全て揃ってますよ(いらない報告)。詳しい使い方は公式マニュアル、もしくはGitHubでの
コードを参照ください。

こちらもListパターンの通りで特にとりとめもありませんが、最後のOneLineAvatarIcon-
ListItemでコールバックを用意しているくらいですかね。サンプルでは全てのアイテムに
コールバックを定義されていましたが、ここでは動作確認のために最後だけ定義してみました。
当然っちゃ当然ですが、直置きの場合はimport文が必要ありません。

### 実行結果

ではさっそく、結果をみる方が何百倍も早いので見ることにしましょう。

![104.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/d4094313-6aaf-8edc-ee87-d99801110472.png)

![105.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/8b19ffe0-cc99-1db8-45d7-a4b9392feeb2.png)

問題ありませんね。
随分とアプリっぽくなってきました。

![106.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/cf3eeced-667e-dfee-74d9-81abc23c9560.png)

なお、ThreeLineAvatarIconListItemについては両側のアイコンがオプション扱い
なのかどうか見るために試しに右側アイコンを除いてみました。問題なく取り除かれて
いますね。

### Custom list item

こちらは冒頭の案内であったカスタムリストの作成方法になります。こちらもコードを見た
方が早いと思うので、さっそくコードに入っていきます。コードはマニュアルのままで、要所
要所で区切っていきたいと思います。

#### import文

```python:xxi/custom_list_item.py
from kivy.lang import Builder
from kivy.properties import StringProperty

from kivymd.app import MDApp
from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem
from kivymd.uix.selectioncontrol import MDCheckbox
from kivymd.icon_definitions import md_icons
```

まぁ、これはわざわざ取り上げるものでもないですが、しつこいくらい触っていきます。
はい、予想されたとは思いますが、これはkv以外で定義や宣言をしたときに必要になる
ものですね。StringPropertyやIRightBodyTouchとOneLineAvatarIconListItem、
MDCheckboxやmd_iconsを使用するために列記しておきます。

#### kv側

```python
KV = '''
<ListItemWithCheckbox>:

    IconLeftWidget:
        icon: root.icon

    RightCheckbox:


BoxLayout:

    ScrollView:

        MDList:
            id: scroll
'''
```

まずはカスタムレイアウト(ListItemWithCheckbox)とルートウィジェット(Box-
Layout以下)に分かれていますね。カスタムレイアウトでは、IconLeftWidgetと
RightCheckboxに分かれているようです。これらは何をもとに構成されているので
しょうかね。実はクラス側にヒントが隠されています。

ルートウィジェットに関しては、触れるまでもなさそうですかね。新しくBoxLayoutが
加わったことと、その配下にListパターンが追加されているだけです。


#### ListItemWithCheckbox側

```python
class ListItemWithCheckbox(OneLineAvatarIconListItem):
    '''Custom list item.'''

    icon = StringProperty("android")
```

なんとListItemWithCheckboxではOneLineAvatarIconListItemを継承している
のでしたね。それってなんだっけという方は今すぐマニュアルを振り返ってください。
まぁそれも面倒だという方は、左右にアイコンが出ているものだということを目を凝ら
しておいてください。で、あとはiconプロパティを生成させてデフォルトをandroid
アイコンにしています。

#### RightCheckbox

```python
class RightCheckbox(IRightBodyTouch, MDCheckbox):
    '''Custom right container.'''
```

こちらは単なるクラス定義だけですが、IRightBodyTouchとMDCheckboxをMixin
させているところがポイントですね。冒頭にあったIRightBodyTouchはこうやって
継承させてから、使用することができるようになります。

#### MainApp側

```python
class MainApp(MDApp):
    def build(self):
        return Builder.load_string(KV)

    def on_start(self):
        icons = list(md_icons.keys())
        for i in range(30):
            self.root.ids.scroll.add_widget(
                ListItemWithCheckbox(text=f"Item {i}", icon=icons[i])
            )
```

ここも、取り留めもありません。ただし、アイコンの使い方で、ここからここを使いたいの
だけどなぁということを思ってたらこういう使い方をした方がいいかもしれません。これから
私のほうでも重宝するかも。

```python
icons = list(md_icons.keys())
```

あとはMDListに流し込むのは同じことをしているだけですね。

### 実行結果

はい、同様に結果を見てみましょう。

![107.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/b67342f1-3ea1-19e9-bc36-704251fdc067.png)

こちらも問題ないようすですね。ちゃんとチェックボックスも見えています。

### もしもIconRightWidgetを何かのLayoutにしたいとなったら

すみません、いいキャプションがなかったので適当に自分で作ってみました。

はい、タイトルの通りですが、1つだけ左右どちらかにアイコンを載せるだけでは何か
足りない！という超わがままな方向けのやり方ということでしょうか。まぁ、こういう
言い方だと語弊がありますが、色々アイコンとかで複数配置されているのをよく見かけ
ますよね。いいねボタン、よくないねボタンを並べて配置したとか。

ということで、挙動を見てみましょうか。まずはコードから。

```python:xxi/
from kivy.lang import Builder

from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.list import IRightBodyTouch

KV = '''
OneLineAvatarIconListItem:
    text: "cup cup cup!"
    on_size:
        self.ids._right_container.width = container.width
        self.ids._right_container.x = container.width

    IconLeftWidget:
        icon: "cup"

    Container:
        id: container

        MDIconButton:
            icon: "thumb-up"

        MDIconButton:
            icon: "thumb-down"
'''


class Container(IRightBodyTouch, MDBoxLayout):
    adaptive_width = True


class MainApp(MDApp):
    def build(self):
        return Builder.load_string(KV)


MainApp().run()
```

変わったところでいうと、まずは順番前後しますがContainerウィジェットになりますかね。
以前では、IconRightWidgetだったのに今回はContainerウィジェットに切り替わって
います。Containerクラスである通り、MDBoxLayoutを継承しているので、MDIconButt-
onを2つ並べて配置出来ているわけになります。

on_sizeプロパティに関しては、これはなんなんですかねぇ。取り除いてはダメな気がプン
プンします。_right_container.widthと.xは何が違うんだろう・・・とりあえずここは
このままにしておきます。

### 実行結果

さてどうなっているか確認しましょう。

![108.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/c4f5dfd9-b8c3-18bc-ea27-32cb2c384d38.png)

cogアイコンが見えなかったから、こちらもカスタムしてみたということがあるのですが
挙動的には問題なさそうですね。コードの方ではとくに触れてませんでした、すみません。
私の環境だけであればいいのですが、cogアイコンはなぜか表示されませんでした。

んで、テキストとContainerウィジェットの中のアイコンも変更して見ました。さっき
いいねボタンとか言ってたしで。どうでもいいですが、テキスト文字列と言い、いいね
ボタンと言いちょっとおバカなリストアイテムですね。IQがちょっとよろしくなさそう。

![109.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/13df41e1-58f8-c291-7c21-96fff9f0e4bd.png)

で、試しにon_sizeプロパティを取り除くと、こうなりました。さらにおバカになり
ましたね。あとは、アイコンを3つ以上に増やすといったこともしましたが、2つだけ
しか正確に表示されませんでしたね。3つ目から途中で見切れました。

うーん、これはon_sizeプロパティでは中央にあるtextプロパティの領域を調整したい
くらいですがねぇ・・・このままだとアイコンは2つだけしか表示されない。

### Behavior

すみません、ここは力尽きました。マニュアル通りで見たい方はご自分で試されてくだ
さい。ただただ、ここはアイコンがタッチされてもタッチされた振る舞いがなくなるよ
ということだけしか言ってないと思うので。

## API - kivymd.uix.list

最後に使用したAPIについて触れておきます。ほとんど、引用するのみになりそうですが。

### class kivymd.uix.list.MDList(**kwargs)

> ListItem container. Best used in conjunction with a kivy.uix.ScrollView.

> When adding (or removing) a widget, it will resize itself to fit its children, 
> plus top and bottom paddings as described by the MD spec.

### class kivymd.uix.list.ILeftBodyTouch

> Same as ILeftBody, but allows the widget to receive touch events 
> instead of triggering the ListItem’s ripple effect.

コードとかでは出てきませんでしたが、念のため。

### class kivymd.uix.list.IRightBodyTouch

> Same as IRightBody, but allows the widget to receive touch events 
> instead of triggering the ListItem’s ripple effect

### class kivymd.uix.list.OneLineListItem(**kwargs)

> A one line list item.

すみません、他のリストアイテムについては種類が多すぎるので省略します。

### class kivymd.uix.list.OneLineRightIconListItem(**kwargs)

> Overrides add_widget in a ListItem to include support for 
> I*Body widgets when the appropiate containers are present.

こういうのもあるのですね。取り留めておきます。

## まとめ

いやー、今日も長かった！

という所感はどうでも良い話ですが、いかがだったでしょうか。

マニュアルの冒頭では、熱いお気持ち表明はされていましたが強い意気込みだとか思い
というのはひしひしと伝わってきましたね。結構苦労してるんだぜ...というぼやきみた
いなものも伝わってきたりして・・・

まぁ、なんにせよコントリビューターのおかげでこれらの機能が使えることは事実になり
ます。ありがたく使わさせてもらいましょう。コンテンツを充実させるためには不可欠の
素材となります。

ということで今週はここまでということで！来週はMenu篇となります。
あれっ、MDSwiperが先なのでは？と思われた方はその通りなのですが、色々事情が
あってですね・・・まぁでもそれは来週話すとします。

では一旦ここにて、さようなら〜。

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

## 参照

> Components » List
> https://kivymd.readthedocs.io/en/latest/components/list/

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