kivyMDチュートリアル其の参什伍 Components - MDSwiper篇

秋風が立ちはじめ、しのぎやすい頃となりました。
さて、キーターの方はいかがお過ごしでしょうか。

とまぁ、手慣れない時候の挨拶を取り入れてみましたが、今週も元気にやっていきたいと
思います。参照のところでも載せておきますが、以下から抜粋してみました。月初めに
取り入れていくかもしれませんし、取り入れないかもしれません。

https://www.midori-japan.co.jp/letter/letter-manners/jikouaisatsu/4322

めっきりと過ごしやすくなったことは嬉しいことですね。でも、天気は相変わらず
グズついたり、台風が近づいたり体調の変化は油断なりません。というかさっき
調べてみると、いつの間にか台風が温帯低気圧に変わったのですね。台風さん、
お疲れ様です。

他に変わったことで言うと、Qiitaが今年の9/16時点で10周年を迎えるそうですね。
おめでとうございます！！！:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:

https://qiita.com/qiita10th#event

いやー、本当にQiitaさんには感謝してもしきれません。なんせ自分は10年と言わず
ですが、開発をしてから大方の期間はお世話になりましたね。そのときは、機械学習
とかも色々してたからなぁ、なつかしい。そして、こうして記事を書かせてもらってる
のも全てQiitaさんのおかげだと思ってます。ありざっす。

とまぁ、はい、本当にお世話になっております。これからも益々のご発展・活躍の方を
祈るばかりです。

というわけで、前置きは以上となります。KivyMDに戻りまして、今日はMDSwiper篇と
なります。※FileManager飛ばしてない?と思った方はするどいです、まとめで触れますね

## MDSwiper

今日はマテリアルデザインのリンクがないので、タグも貼り付けていません。
※ ちなみに10周年記念タグも邪魔になるだろうと思いつけていませんでした

あると思ったのになぁ、まぁいいや。

特に私からは言うまでもないですが、簡単な概要としてはスワイプをすることで画像を
切り替える、画像ギャラリーということになるでしょうか。これはWebサイトなんかでも
よくあるものですよね。画像コンテンツを表示させたり、それを切り替えさせたりなんか
して。

以前では、似たような画像表示コンポーネントであるImageList篇をやりましたね。
ImageListは画像を一覧表示させることが特徴ですが、MDSwiperに関しては、全て
画像を表示させるのではなく、自分からスワイプして画像を逐一表示させることが特徴
となります。今回のコンポーネントはアプリ提供者が画像を用意するときに使われやす
いですかね。

というわけで色々うだうだ言ってもどうにもならないので、もういきなりUsageに入り
ます。

### Usage

使用方法としては以下のようになると記載があります。

```python
MDSwiper:

    MDSwiperItem:

    MDSwiperItem:

    MDSwiperItem:
```

この形は何度も見たことがあるかと思われます。というか関連コンポーネントである
ImageListの実現方法とクリソツ(死語)です。ほぼ同じ。

### Example

で、その後にサンプルコードがあります。まずはそれを見てみましょう。

```python:xxxv/mdswiper_example.py
from kivymd.app import MDApp
from kivy.lang.builder import Builder

kv = '''
<MySwiper@MDSwiperItem>

    FitImage:
        source: "好きな場所に/好きな画像を表示してみてね.(jpg|png|etc..)"
        radius: [20,]

MDScreen:

    MDToolbar:
        id: toolbar
        title: "MDSwiper"
        elevation: 10
        pos_hint: {"top": 1}

    MDSwiper:
        size_hint_y: None
        height: root.height - toolbar.height - dp(40)
        y: root.height - self.height - toolbar.height - dp(20)

        MySwiper:

        MySwiper:

        MySwiper:

        MySwiper:

        MySwiper:
'''


class Main(MDApp):
    def build(self):
        return Builder.load_string(kv)

Main().run()
```

シンプルと言えば、シンプルな構成になりますね。先に留意事項を言っておくと、マニュ
アルからそれほど変更はといったことになります。

さらに言えば、今回はほぼほぼkv側の触れ込みとなるのでimportとクラス側は端折ります。

#### kv側

MDToolbarについては、以前にまとめているページがあるのでそちらをご参照ください。
えー、いやだよという方はマニュアルを見た方が早い可能性もあります。

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

ということで、今日のメインディッシュでもあるMDSwiperの部分をフォーカスしてみます。

```python
<MySwiper@MDSwiperItem>

    FitImage:
        source: "好きな場所に/好きな画像を表示してみてね.(jpg|png|etc..)"
        radius: [20,]

MDScreen:

(略)

    MDSwiper:
        size_hint_y: None
        height: root.height - toolbar.height - dp(40)
        y: root.height - self.height - toolbar.height - dp(20)

        MySwiper:

        MySwiper:

        MySwiper:

        MySwiper:

        MySwiper:
```

まず、マニュアルにない部分だとMySwiperがカスタムで定義されています。継承している
のはもちろん、MDSwiper関連クラスでもあるMDSwiperItemとなりますね。これはUsage
にもある通りですが、MDSwiperはカスタムされたものを管理するManager的な役割をして
います。

で、MySwiperは何を持っているかというと、FitImageウィジェットとなります。その中
には画像の場所(sourceプロパティで指定)と角丸の大きさ(radiusプロパティで指定)を
持ち合わせています。

それを持って、今度はMDScreen側で配置をするために、先ほどのMDSwiperとMySwiperを
親子関係のように指定します。MDSwiperはsize_hint_yやheight、yプロパティを持ち合わ
せています。size_hint_yプロパティについてはLayout篇で紹介していますので、もし良かっ
たらそちらの方をご参照ください。でも詳細はkivyの公式マニュアルの方が詳しいです。

https://qiita.com/virty/items/d5fa0a529912e6b0beb0

heightプロパティはyプロパティよりかは分かり易いでしょうか。ルートウィジェットから
ツールバーと40dpほど引いたことが高さの領域となっています。yプロパティは正直断定できる
ほどよく分かっていません。おそらく左上のy座標の計算となっているのでしょうが、self.
heightはどの高さなのでしょうかね。少しよく分かってないので、実験して確かめることと
します。

### 結果

というわけで、まずは先ほど載っけておいたコードの実行結果を見てみましょう。
あ、というかその前にどんな画像かを言っていなかったですね。まぁ、すぐに分かること
ですが今回もあの方を呼んでいますよ。はい、あのお方です。

![157.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/caa52591-ad71-ec11-1c8f-6b7129461e5a.gif)

はい、薄々分かっていた方もいるかもしれませんが、このお方でした。茜さやさんですね。
ImageListも登場頂いた方でしたが、もう画像となるとこのお方しかありません。OpenCV
にLena、KivyMDにはSayaさんしかありません(独断しかありません)。正直なことで言うと
、見た瞬間に「最高かよ...」となったのはまぎれもなく事実です(気持ちが悪い)。

ということであまり突っ込んでしまうと、規約違反でBANされるとやっかいなことになるかも
しれないので、この辺りでやめておきます。お次は、先ほどyプロパティでよく分からないとこ
ろだと言っていたself.heightになります。思い切ってこいつを削除してみましょう。変更点
は以下のようになります。

```diff
-        y: root.height - self.height - toolbar.height - dp(20)
+        y: root.height - toolbar.height - dp(20)
```

![156.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/1bace8a7-1035-3025-d17b-58d31a492b96.png)

あーね(これも若干古い)、という感じになりました。まぁそのままっちゃそのままですが、
self.heightはMDSwiper自体の高さということになりますでしょうか。そうとなると辻褄
があいそうです。self(MDSwiper).height分下げておくことによって、先ほどの動画キャプ
チャのように位置がちゃんとずらされるようになります。yは左上と先ほど言っていましたが、
左下の座標となるのですね。

次に入る前に、なにやら警告文が書かれていますね。しっかり読み込んでいきましょう。

:::note warn
The width of MDSwiperItem is adjusted automatically. Consider changing that by width_mult.
:::

まずこちらに関してですが、横幅は自動で切り替わるということでしょうか。確かに初期表示
からウィンドウサイズを変更すると、そのサイズによってこのウィジェットの横サイズも自動で
変化したことは確認できました。試しにここは色々と変更してみてもらえればと思います。

:::note warn
The width of MDSwiper is automatically adjusted according to the width of the window.
:::

こちらは、MDSwiperもおなじだよということですね。

また、イベントにも触れ込みはありますが、これは次のところに回します。

### Custom Example

というわけで、Example自体の触れ込みは以上となります。ここからは、MySwiperをさらに
カスタムしてみるとどうなるかということを見てみたいと思います。

サンプルコードもあるのですが、それだけだとつまらないので少し工夫したいと思います。です
が、ベースとなるものは同じなので変更差分だけをここでは記載しておきます。と言ってもほと
んどというか全て追記する形になりますね。全容はGitHubにでも載せておくのでそちらを参照
してもらえればと思います。

```diff:xxxv/mdswiper_custom_example.py
(略)

            MagicButton:
                id: icon
-               icon: "weather-sunny"
+               icon: "thumb-up-outline"
                user_font_size: "56sp"
                opposite_colors: True

            MDLabel:
-               text: "MDLabel"
+               text: "Saya"
(略)  
-               opposite_colors: True
+               # opposite_colors: True
+               # add custom color
+               theme_text_color: "Custom"
+               text_color: 1, 1, 1, 1

MDScreen:

    MDToolbar:

(略)
        elevation: 10
+       right_action_items: [["hand-pointing-left", lambda x: swiper.set_current(swiper.get_current_index() - 1)], ["hand-pointing-right", lambda x: swiper.set_current(swiper.get_current_index() + 1)]]
        pos_hint: {"top": 1}

    MDSwiper:
+       id: swiper
        size_hint_y: None
        on_swipe: self.get_current_item().ids.icon.shake()
+       on_pre_swipe: print("on_pre_swipe")
+       on_overswipe_right: print("on_overswipe_right")
+       on_overswipe_left: print("on_overswipe_left")
+       on_swipe_left: print("on_swipe_left")
+       on_swipe_right: print("on_swipe_right")

(略)

```

とまぁ、変更差分の抽出もれがないか不安になるところですが、一応全ての差分は以下
のようになります。MagicButtonだとかMDLabelなどは以前にも触れていることもあり
ますし、MagicBehaviorなんかはまだ触れていないのでカスタムがどのようになって
いるかは結果を見れば一目瞭然なので割愛します。結構ここも重要度高いですけどね。
\# これまでずっと見てきた方はスラスラ読めるのではないかと

あ、忘れないうちにButton篇とMDLabel篇のリンクを貼り付けておきますね。そんな
時間ないんだけど!という方はマニュアルを見直してもらえれば。

https://qiita.com/virty/items/29d792d5ecf4ad0f86be

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

ということで今日のデザートあたりになりますが、MDToolbarにボタンを表示してみて
スワイプをできるようにright_action_itemsプロパティを導入してみました。これも
なんのことかわからんてなった方は、先述のToolbar篇をご覧いただければ。

これだと、少しぶっきらぼうになりますので、なぜこのボタンを取り入れたか理由を言って
おきますと「How to automatically switch a SwiperItem?」のコードを使って
いるだけになります。よくよく見れば、そのメソッドが使われていることがよく分かるので
はないかと。

あとは、なんてことないですがon_*イベントプロパティを追加してる件ですね。これも
なんてことはないと思います。これも動作を見てもらえれば分かり易いかと。

### 結果

ということで、ロンよりツモ。あ、違う違う。失礼しました。
論より証拠ということで結果の方を見てみましょう。

![158.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/62a29cee-fd92-8b22-6587-351d8cb0dd6b.png)

まずは、どのような表示になるかですね。うーん、ずっと見てられるな。いや、
すみません、寄り道ばっかりしてますね。

で、どのようになったかというと、見てもらえれば分かり易いですがちゃんとツールバー
に少しクセが強いボタンも配置できていますね。これはスワイプできるように意図をして
います。あとは、画像コンテンツのアイコンやら文言やらが変わっていて。

で、残りになりますが、イベントプロパティはどのように挙動がなされるのかということを
見てみたいと思います。これは動画キャプチャで見てみましょう。

![159.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/0e8f61da-d591-a225-3ac9-7422884d7718.gif)

ターミナルと動かしたものをセットで撮ってみました。まずは、on_swipeですが、これは
スワイプしたときにいいねアイコンがプルプル震えていますね。具体的には、MagicButton
ないしはMagicBefaviorを使用しているというわけになるのですが、まだBefaviorはやって
ないので、割愛します。

から、on_pre_swipeに入りますが、これは実際に指(キャプチャとしてはマウス)・ボタン
でスワイプしたときの手前で働くイベントとなります。なので、これはフリックしたときだけ
に動くものではないということに注意が必要そうです。ここはon_swipeとイベントの起こり
具合的には同じですね。

そしてon_overswipe_(right|left)になりますが、これは最後あたりを注目してもらえれ
ば、なお分かり易いと思われます。両橋の最終地点に到達したときにさらにスワイプすると
このイベントが発生します。なのでもうこれ以上ないよとかメッセージを出すときとか、他
に画像を用意したときとかは重宝しそうですね。

on_swipe_(right|left)に関しては何も言うことはなさそうですね。これもキャプチャを
見てもらえると分かり易いですが、on_pre_swipeの後でこれらのイベントがファイアーされ
ます。

あと、ボソッと言っておきますが、左右のボタンを画像ないのに押してしまうと大変なこと
となります。気になる方は修正してみても良いかもしれません(丸投げ)。こういう場合は
コールバックメソッドをクラス側に持っておいた方がいいですね。

## API - kivymd.uix.swiper.swiper

もう疲れ...いや、なんでもありませんよ。そんなこころの声が出ただなんて、あはは。

ということで、見せるものは全てお見せしたので、今日使用したAPIについて復習して
おきます。

### class kivymd.uix.swiper.swiper.MDSwiperItem(**kwargs)

> MDSwiperItem is a BoxLayout but it’s size is adjusted automatically.

実質BoxLayoutにあたりますが、横幅とかは自動計算されていましたね。

### class kivymd.uix.swiper.swiper.MDSwiper(**kwargs)

> ScrollView class. See module documentation for more information.

いつものやつです。好奇心旺盛な方向きの文言ですね。

`width_mult`

> This number is multiplied by items_spacing x2 and then subtracted 
> from the width of window to specify the width of MDSwiperItem. 
> So by decreasing the width_mult the width of MDSwiperItem increases and vice versa.

> width_mult is an NumericProperty and defaults to 3.

警告文のところで出ていたので、掲載してみました。使うとするとなると注意が必要そうですね。

`set_current(self, index)`

> Switch to given MDSwiperItem index.

`get_current_index(self)`

> Returns the current MDSwiperItem index.

今回はこちらを使ってみました。というかマニュアルのままだけど。

## まとめ

いやー、今日も長かった！先週短かったからですかね(知らない)。

というわけでいかがだったでしょうか。開発者がユーザーへ対して見せたいといった
ことがある場合は重宝しそうな感じがありますね。おすすめ機能とかあるとこれ使った
方がいいかもです。

少し課題も残っている状況でしたが、今回は皆さんに丸投げした状況ですw
余力のある方は是非トライしてみてはいかがでしょうか。もしできた!という方は
コメントにて記載いただけると嬉しかったりもします。

ということで、今週はこれまでです。来週は残っているMenu篇となりますね。
来週も来週で多そうだなぁ・・・

ということで今日も見てくださりありがとうございました〜。

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

## 参照

> Components » MDSwiper
> https://kivymd.readthedocs.io/en/latest/components/mdswiper/

> 「一緒に食べないの？」と問いかける美女の写真素材
> https://www.pakutaso.com/20160203053post-7025.html

> 「そろそろ時間だから行こっか」のフリー素材
> https://www.pakutaso.com/20160452095post-7493.html