kivyMDチュートリアル其の伍 Themes - Icon Definitions篇

さぁ、今週もやってまいりました、kivyMDのお時間です。
少しサンプルコードのkivyの難易度が上がってきて、あたふたしておりますがそんなことは
お構いなくで、元気にやって参りましょう。慌てて、今は再勉強中です。
本日はIconのことをやっていきます。

## Icon Definitions

冒頭は「大将、いつものやつで！」と言わんばかりのリンクからになります。

> See also

> Material Design Icons
> https://materialdesignicons.com/

リンクの[View the Contributors]を辿ると、こんなにあるの？と思えるくらいアイコンが
羅列されています。内訳はというと、Googleとマテリアルデザインのコミュニティ？とで構成
され何千にものぼるほどです。コミュニティは赤枠で囲まれていますね(先週とかあったっけ?赤枠
はコミュニティ発ですみたいなもの)。マニュアルの続きではこういった説明もあります。

> List of icons from materialdesignicons.com. These expanded material design icons
> are maintained by Austin Andrews (Templarian on Github).

> materialdesignicons.comからのアイコンのリスト。 これらの拡張マテリアルデザインアイコンは、
> Austin Andrews（GithubのTemplarian）によって管理されています。

何食わぬ顔で依頼していますが、上記の通りとなっています。Templarianってなんだろう。。
まぁ、なんにせよAustin Andrewsさんによってこれらのアイコンが使えるので感謝を捧げましょう。
Thank you Austin Andrews!

とイキってお礼を伝えたところで、今日の本題に入りたいと思います。
安心してください、コード動いてますよ。

### To preview the icons and their names, you can use the following application:

とまぁ、サンプルコードが載っけてあるのでさっそくこちらを見ていきましょう。

```python:v/icon.py
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.screenmanager import Screen

from kivymd.icon_definitions import md_icons
from kivymd.app import MDApp
from kivymd.uix.list import OneLineIconListItem


Builder.load_string(
    '''
#:import images_path kivymd.images_path


<CustomOneLineIconListItem>:

    IconLeftWidget:
        icon: root.icon


<PreviousMDIcons>:

    MDBoxLayout:
        orientation: 'vertical'
        spacing: dp(10)
        padding: dp(20)

        MDBoxLayout:
            adaptive_height: True

            MDIconButton:
                icon: 'magnify'

            MDTextField:
                id: search_field
                hint_text: 'Search icon'
                on_text: root.set_list_md_icons(self.text, True)

        RecycleView:
            id: rv
            key_viewclass: 'viewclass'
            key_size: 'height'

            RecycleBoxLayout:
                padding: dp(10)
                default_size: None, dp(48)
                default_size_hint: 1, None
                size_hint_y: None
                height: self.minimum_height
                orientation: 'vertical'
'''
)


class CustomOneLineIconListItem(OneLineIconListItem):
    icon = StringProperty()


class PreviousMDIcons(Screen):

    def set_list_md_icons(self, text="", search=False):
        '''Builds a list of icons for the screen MDIcons.'''

        def add_icon_item(name_icon):
            self.ids.rv.data.append(
                {
                    "viewclass": "CustomOneLineIconListItem",
                    "icon": name_icon,
                    "text": name_icon,
                    "callback": lambda x: x,
                }
            )

        self.ids.rv.data = []
        # print(len(md_icons)) #-> 4996
        #print(type(md_icons)) #-> <class 'dict'>
        for name_icon in md_icons.keys():
            if search:
                if text in name_icon:
                    add_icon_item(name_icon)
            else:
                add_icon_item(name_icon)


class MainApp(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.screen = PreviousMDIcons()

    def build(self):
        return self.screen

    def on_start(self):
        self.screen.set_list_md_icons()


MainApp().run()
```

少々コードが難しくなってきましたが、わかる範囲だけ触っていこうと思います。

#### モジュールのインポート

```python
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.screenmanager import Screen

from kivymd.icon_definitions import md_icons
from kivymd.app import MDApp
from kivymd.uix.list import OneLineIconListItem
```

あまりこれまではインポート文を触ってきませんでしたが、これからは注意していこうと思います。
サンプルコードを動かすには上記が必要となります。今回のiconをいじるには下記の1文が必要に
なりますかね。
`from kivy.uix.screenmanager import Screen`

#### kv側

Builder.load_stringでkvを読み込んでいるところをピックアップします。

```python
Builder.load_string(
    '''
#:import images_path kivymd.images_path


<CustomOneLineIconListItem>:

    IconLeftWidget:
        icon: root.icon


<PreviousMDIcons>:

    MDBoxLayout:

        (略)
        MDBoxLayout:
        (略)

        RecycleView:
        (略)
'''
)
```

少し長いので略せるところは略します。冒頭のimport文はなんなんでしょうね。取り除いても普通に動いて
いたしで。。まぁ、この辺は一旦放っておきましょう。大まかに捉えると、2つのウィジェットが定義されて
います。CustomOneLineIconListItemとPreviousMDIconsになります。CustomOneLineIconListItem
に関してはIconLeftWidgetを内部に持っていて、またその中にはiconを保持しています。このアイコンはroot
とあるので、ここではpython側で宣言されているのだろうと推測しておきます。

もう一方のPreviousMDIconsウイジェットは子ウィジェットのMDBoxLayoutを持ちます。さらにその子ウィ
ジェットは子MDBoxLayoutウィジェットと子RecycleViewウィジェットを持ちます。PreviousMDIconsから
すると孫にあたるのでしょうか。

```python
    MDBoxLayout:
        orientation: 'vertical'
        spacing: dp(10)
        padding: dp(20)
```

さらにPreviousMDIconsのトップにあるMDBoxLayoutですが、この辺りは説明するまでもと
思いましたがどうなっているか自分も知りたいと思ったのでさらっと触ります。まずorientationはマニュアル
参照ですが、アプリでも表示されているように縦並び配置ということですよね。で、あとはspacingとpaddingは
デザインのこと知っている人であれば常識みたいなところですが、自分みたいなドがつく素人は次の試した結果を見た
方が早そうです。

![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/e490f9ae-6085-35cb-a20b-6f48b4335469.png)

それぞれspacingとpaddingを50ほど増やした結果が上キャプチャになります。見てわかりやすいのは
spacingですかね。トップに定義したMDBoxLayoutの2つの子ウィジェットの空白を決めますよということ
になるのですね。んで、paddingはというとそれぞれ子ウィジェットにあるパーツの周辺の長さを決めれますよ
ということになります。何いってるか分からないとかあればデザインの本を買ってみてください。

```python
        padding: dp(20)

        MDBoxLayout:
            adaptive_height: True

            MDIconButton:
                icon: 'magnify'

            MDTextField:
                id: search_field
                hint_text: 'Search icon'
                on_text: root.set_list_md_icons(self.text, True)
```

続けて2つのウィジェットの1つにあたるMDBoxLayoutに触れます。これらが意味するところは検索マークと実際に
検索するときのテキストフィールドのエリアにあたりますね。んで先にadaptive_heightってなんぞよと思いましたが、
こればっかりは先のcomponentsにあるし特に説明がないしでよく分かりませんでした。
\# おそらくコードもないしでcomponents - BoxLayoutは触れないと思います

まぁ、これだけで終わるとやるせないので試しにFalseで動かしてみたところ以下のようになりました。

![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/94c6e5f6-b9dd-22c9-a22a-76e644023092.png)

んー、画面上部に配置しなくなるよーということなのか。奥が深すぎるぞ、kivyMD。。。
後のMDIconButtonとMDTextFieldは見たまんまになりますかね。MDTextFieldでは、またrootと
あるので伏線だなと勘ぐっておきます。あと、id: search_fieldとありますがこの後でこれが出てくる
ことはありません。うん、こればっかりはよくわからないな。

```python
        RecycleView:
            id: rv
            key_viewclass: 'viewclass'
            key_size: 'height'

            RecycleBoxLayout:
                padding: dp(10)
                default_size: None, dp(48)
                default_size_hint: 1, None
                size_hint_y: None
                height: self.minimum_height
                orientation: 'vertical'
```

んで、kvの最後にラスボスとなるRecycleViewがやってきます。すみません、こればっかりは力不足で
自分からは触れることのできない内容となっています。ですが、先行で分かりやすすぎて目から鱗状態に
なるページがあったのでそちらを参照ください。むずい、RecycleView...

> 私文 vs kivy(5-1) RecycleViewの基本
> https://labor.hatenablog.jp/entry/2019/09/25/%E7%A7%81%E6%96%87_vs_kivy%285-1%29_RecycleView%E3%81%AE%E5%9F%BA%E6%9C%AC_1

ぽきたさんによるRecycleViewの説明になります。ありがたや、ありがたや。
改めてここで謝辞を述べさせていただきます。後、勝手な引用をお許しください。

なるほど、もともとListViewというものがあったのですね。んで後継でこいつが生まれたと。
あとは、概念としてMVCがあるというのは面白いなぁと思いました。全部UI側なのにMVCを採用
したとは実に面白い・・・と思うのは私だけでしょうか。
\# 少し憶測入ってるかもだけど

1つ1つ説明もありだけど、レイアウトで被っている内容もあるしでここではkivyに近い内容なので
割愛します。ご了承のほどを。あ、あとid: rvだとかkey_viewclass: 'viewclass'だとかは後で
出てくるのでお楽しみをー！

#### python側

```python
class CustomOneLineIconListItem(OneLineIconListItem):
    icon = StringProperty()
```

んで、ここからはpython側に入っていきますがまずはCustomOneLineIconListItemクラスに
になります。このクラスはOneLineIconListItemを継承していて、1行のリストアイテムを表示
させたいときにこれを継承するのかな。知らないけど。こんなこと言ってますが、components - 
listで触りますのでご心配なくと乞うご期待ということで！

あとは、kv側で定義してあったroot.iconとリンクするところで変数化をしていますね。
伏線回収っと。

```python
class PreviousMDIcons(Screen):

    def set_list_md_icons(self, text="", search=False):
        '''Builds a list of icons for the screen MDIcons.'''

        def add_icon_item(name_icon):
            self.ids.rv.data.append(
                {
                    "viewclass": "CustomOneLineIconListItem",
                    "icon": name_icon,
                    "text": name_icon,
                    "callback": lambda x: x,
                }
            )

        self.ids.rv.data = []
        # print(len(md_icons)) -> 4996
        for name_icon in md_icons.keys():
            if search:
                if text in name_icon:
                    add_icon_item(name_icon)
            else:
                add_icon_item(name_icon)
```

それで少し長いですが、こちらはPreviousMDIconsになります。Previousと謳っている割には、
検索してからの表示も兼ねていて混乱しそうになります。

まずは、後でも出てきますがアプリが初期化され起動されたときにはこのset_list_md_iconsが
呼び出されます。すぐ下の内部メソッドadd_icon_itemはさっそくkv側でidをrvと決めていた
ところ、すなわちrecycleviewにあたりますね。引数にはname_iconを受け取っていて、rvに
データを流し込んでいます。方法はというとrv.data.appendでjson形式のようにデータを入れ
込んでいますね。あとはkv側で決めていたviewclassもここで登場しています。recycleviewは
わりかし、このような形をしているので自分みたいにわけわからんとなっている方は形を覚えて
おきましょう。ときにおまじないは便利なものです。あと、伏線回収っと。

後出しで実はというと、このadd_icon_itemはクラスを初期化したとき(rv.dataを初期化してそのあと
イテレータを回している寸前)は即座に呼び出されません。じゃ、いつ呼ばれんのよと言われそうですが、
もう見えているかもしれませんので説明するまでもないかもですが、このイテレータを回しているときに
初めてadd_icon_itemが呼び出されます。\# 見りゃわかるだろーがと言われそう

んで、一見何をしているか分からないfor文ですが、大まかに言って検索したときとそうでないとき(
検索フィールドに何も文字列を入れていないとき)とでこのif文で処理を分けています。その条件とは
と言われそうですが(被害妄想)、それがsearchになります。このsearchはset_list_md_iconsの
第2引数ですね。デフォルトではfalseになります。もう察しの良い方はわかるかもというか当たり前
かもですが、trueになるときは検索フィールドに値が入ったときですね。このことがいわゆるkv側での
以下の1文になります。
`on_text: root.set_list_md_icons(self.text, True)`

あとは、さらなるその中にあるifですがここまでくるとお手のまえ、そのtextがname_iconに含まれて
いるとそのアイコンが絞り出されるといった挙動になっています。もう、ここまで実装されているとお見事
としか言いようがありません。

```python
class MainApp(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.screen = PreviousMDIcons()

    def build(self):
        return self.screen

    def on_start(self):
        self.screen.set_list_md_icons()


MainApp().run()
```

今更かい、と言われそうですがアプリの起動方法もついでに見ておきましょう。
初期化(init)メソッドでは、screen変数にPreviousMDIconsクラスを生成して入れ込んでいます。
んで、その次のbuildメソッドではscreenを返して、最後にon_startメソッドでscreen経由で
set_list_md_iconsメソッドを呼び出しています。runメソッドは言わずもがな。

触れ込みは以上になりますが、ここでアプリを動かしてみたいと思います。というかすでに動かして
いるけど。無駄にスマホのように形を変えて、キャプチャしたものが以下になります。

![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/cfccfd7a-5bbf-9c32-4a77-7d0a061b6936.png)

![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/71169a2b-ad3c-e296-88ba-d66c5f583b4f.png)

というように、動かしてみるとわかりますがと！に！か！く！量が多い。。コードの方の触れ込みで
言い忘れていましたが、なんとその総数4996。。日常生活で使うものは大抵入ってる？と思える
くらいの量があります。

### API - kivymd.icon_definitions

#### kivymd.icon_definitions.md_icons

コードの方で触れているので、言わずもがなかもですが使い方を一応おさらいしておきます。
importでモジュールを取り込んで、それを使う、以上！です。
あと、コードでは触れてませんが辞書型のオブジェクトになります。

## まとめ

さぁ、今週も無事に終わりましたがいかがだったでしょうか。
自分のTODOというか振り返りとしてはRecycleViewをなんとかしたいなぁと思いました。
他のkotlinとか触っても出てきたりするので、またわかり次第解説とかページ作ろうかな。
日本語のページという括りでは、あまり検索しても出てこないしで。一応以下にRecycleViewの
リンクを備忘録&共有として貼っておきます。
> RecycleView
> https://kivy.org/doc/stable/api-kivy.uix.recycleview.html

あとはもっともっとkivyというかkv側に詳しくならないと。。課題は山積みだぁ。
とまぁ、どうでも良いまとめをしたところで今日はこの辺りで。

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

## 参照

> Themes - Icon Definitions
> https://kivymd.readthedocs.io/en/latest/themes/icon-definitions/

## github

https://github.com/Quiet396/kivymd-app/tree/main/iv