kivyMDチュートリアル其の参什玖 Components - Snackbar篇

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

はい、ということで今週も相変わらず開いております。少し肌寒いというかめちゃ寒
でしたが、体調は崩してはいませんでしょうか。この業界は体資本、というかビジネス
マンであれば全てと言っても過言ではないくらい体を使います。体調は万全にしておき
たいですね。

今週のニュースはというと、特にとりとめもなかったかと思います。私の調査不足という
ことはなしにしてください。。

で、今週のKivyMDのテーマは、Snackbarとなります。下の方で伝えたいメッセージを
ピローンと出すやつですね。ではさっそく元気でやっていきます。えいえい、えいー。

## Snackbar

毎回のことですが、マテリアルデザインのリンクは飛ばします。KivyMDとかでは仕様を
守って実装されているのでそれほど気にする必要はありませんが、結構見落としがちな
ことなどを書かれています。例えばボタンはテキストと同じにしないとか、このボタン
のスタイルは使っちゃだめよとか。気になる方は一度ご覧いただけたらと。

マニュアルの方でもこのように概要が書かれています。

> Snackbars provide brief messages about app processes at the bottom of the screen.

このあたりのことは、のちに出てくる結果の方を見ると分かりやすいかと思われます。
てか、マニュアル見るとあーーとなりそうですが。あとは結構Androidの方とかだと
頻繁にこのメッセージ方式はよく見るかと思われます。

### Usage

ということで、さっそく使い方について触れていきたいと思います。まずは、コード
から。

```python:xxxix/snackbar.py
from kivy.lang import Builder

from kivymd.app import MDApp

KV = '''
#:import Snackbar kivymd.uix.snackbar.Snackbar

Screen:

    MDRaisedButton:
        text: "Create simple snackbar"
        on_release: Snackbar(text="This is a snackbar!").open()
        pos_hint: {"center_x": .5, "center_y": .5}
'''


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


Test().run()
```

Usageなのか分からないですが、方法としてはなんてことないと思います。今回の
使い方としては、MDRaisedButtonのon_releaseプロパティのコールバック
メソッドで、いきなりSnackbarインスタンスを生成する形で実現しています。
Snackbarは他コンポーネントに依存して、なにかしらのアクションをすること
によってファイアーされます。

これ以降ではメソッド名を書いておいて、実体はクラス側にて書くということで
実現をしています。どちらを選べばいいかということはないだろうと思いますが、
シンプルなsnackbar(1テキストをはめ込むくらい?)であればUsageの方を、
それ以外であればこれから使う方法を使用すればよいのかなぁと思っちゃいます。

1つ注意したいことは、今回はkv側でSnackbarインスタンスを生成しているので
import文を上記のように指定する必要があります。でないと、普通にエラーを
吐いて終了してしまいます。

あとは、MDRaiseButtonってなに?という方は以下かもしくはマニュアルの方を
参照頂ければと思います。

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

### 結果

はい、ということで結果の方を見てみましょう。論より証拠をということを徹底
していきたいと思います。

![180.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/3d8d86ea-2393-fad7-561a-cce07dcf7de1.gif)

はい、なんてことはないですね。マニュアルから変わっていることがあったり、
問題があったりということはありません。

### Custom Snackbar(Usage with snackbar_x, ... Using a button with custom color)

飛ばしすぎじゃね?と思われるかもしれませんが、そういうことではありません。
最下部のCustom Snackbarでは途中のsnackbar_xなどを使うということを
含んでいるので合体してみました。ちゃんと途中のものは大方触れています。

ただし、Custom usageについては使い方がCustom Snackbarと同じである
ことと、AnimationだったりClockオブジェクトを使ったりと少々これまでに
触れてきてないので省略はしています。また触れるかどうかはこの先の自分の
気持ち次第ではあろうかと思われます。

ということなので、こちらも一旦コードの方に入ります。ほとんどマニュアルと
同じですが、途中のCustom Usage以外のものだったりとかは入れ込んでいます。

```python:xxxix/custom_snackbar.py
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.properties import StringProperty, NumericProperty

from kivymd.app import MDApp
from kivymd.uix.button import MDFlatButton
from kivymd.uix.snackbar import BaseSnackbar

KV = '''
<CustomSnackbar>

    MDIconButton:
        pos_hint: {'center_y': .5}
        icon: root.icon
        opposite_colors: True

    MDLabel:
        id: text_bar
        size_hint_y: None
        height: self.texture_size[1]
        text: root.text
        font_size: root.font_size
        theme_text_color: 'Custom'
        text_color: get_color_from_hex('ffffff')
        shorten: True
        shorten_from: 'right'
        pos_hint: {'center_y': .5}


Screen:

    MDRaisedButton:
        text: "SHOW"
        pos_hint: {"center_x": .5, "center_y": .45}
        on_press: app.show()
'''


class CustomSnackbar(BaseSnackbar):
    text = StringProperty(None)
    icon = StringProperty(None)
    font_size = NumericProperty("15sp")


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

    def show(self):
        snackbar = CustomSnackbar(
            text="[color=#ddbb34]This is a snackbar![/color]",
            icon="information",
            # Usage with snackbar_x, snackbar_y
            snackbar_x="10dp",
            snackbar_y="10dp",
            # Usage with button
            buttons=[MDFlatButton(text="ACTION", text_color=(1, 0, 0, 1))],
            # Using a button with custom color
            bg_color=(0, 0.5, 0.5, 1)
        )
        # Control width
        snackbar.size_hint_x = (
            Window.width - (snackbar.snackbar_x * 2)
        ) / Window.width
        #print("window.width={0}, snackbar.snackbar_x={1}".format(Window.width, snackbar.snackbar_x))
        snackbar.open()


Test().run()
```

ベースとしては、Usageの通りなのですがここも結構触れるところはあるので、
ここからはimport文、kv・クラス側という風に分けていきたいと思います。

#### import文

まずimportとしては以下のパッケージ・オブジェクトを必要とします。

```python
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.properties import StringProperty, NumericProperty

from kivymd.app import MDApp
from kivymd.uix.button import MDFlatButton
from kivymd.uix.snackbar import BaseSnackbar
```

ここで新しく必要なものとしてはWindowや*Property、MDFlatButtonとBase-
Snackbarオブジェクトになります。Windowはsnackbarのサイズを決めるために、
*Propertyはテキストやアイコンなどを決めるために必要となります。また、MDFl-
atButtonはクラス側で使うために、BaseSnackbarはsnackbarをカスタムするため
に必要となります。

#### kv側

続いて、kv側となります。ここはUsageからの差分を抜粋しておきます。

```python
<CustomSnackbar>

    MDIconButton:
        pos_hint: {'center_y': .5}
        icon: root.icon
        opposite_colors: True

    MDLabel:
        id: text_bar
        size_hint_y: None
        height: self.texture_size[1]
        text: root.text
        font_size: root.font_size
        theme_text_color: 'Custom'
        text_color: get_color_from_hex('ffffff')
        shorten: True
        shorten_from: 'right'
        pos_hint: {'center_y': .5}


Screen:

    MDRaisedButton:
(略)
        on_press: app.show()
```

肝となる部分は、CustomSnackbarレイアウトになりますでしょうか。ここは単に
MDIconButtonとMDLabelを配置しているだけになります。ここも紛らわしいですが、
Snackbarの範囲内であるということは留意しておかなければいけません。Snackbar
を出現するためのボタンはMDRaisedButtonで関係がありません。

また、MDLabelについてよく分からないという方は以下のリンクもしくはマニュアルの
方をご覧頂けたらと思います。

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

あとは、カスタムレイアウトから離れて、MDRaisedButtonの方を見てみます。Usage
の方では、on_releaseプロパティでしたが今度はon_pressプロパティに変更されて
います。また、値にインスタンスを生成する指定方法でしたが、今度はクラス側のメソ
ッドにて実体を書くようにしています。

#### クラス側

最後にクラス側を触れてみます。ここも必要箇所を抜粋しておきます。

```python
class CustomSnackbar(BaseSnackbar):
    text = StringProperty(None)
    icon = StringProperty(None)
    font_size = NumericProperty("15sp")


class Test(MDApp):
(略)

    def show(self):
        snackbar = CustomSnackbar(
            text="[color=#ddbb34]This is a snackbar![/color]",
            icon="information",
            # Usage with snackbar_x, snackbar_y
            snackbar_x="10dp",
            snackbar_y="10dp",
            # Usage with button
            buttons=[MDFlatButton(text="ACTION", text_color=(1, 0, 0, 1))],
            # Using a button with custom color
            bg_color=(0, 0.5, 0.5, 1)
        )
        # Control width
        snackbar.size_hint_x = (
            Window.width - (snackbar.snackbar_x * 2)
        ) / Window.width
        #print("window.width={0}, snackbar.snackbar_x={1}".format(Window.width, snackbar.snackbar_x))
        snackbar.open()
```

もうほとんどですね。必要箇所ばかりで抜粋というレベルではありません。

まずはkv側のカスタムレイアウトと連携しているCustomSnackbarクラスです。snack-
barをカスタム化しようとするならば、上記のようにBaseSnackbarを継承しなければ
いけません。あとはテキストとかアイコンを指定できるよう、各々プロパティを置いて
います。

今度はメインアプリのクラス(Test)を見てみます。kv側でのMDRaisedButtonでon_pre-
ssプロパティがありましたが、その値で指定していたメソッドがこのshowメソッドです。
中身については、上記の通りとなりますのでマニュアルと照らし合わせてみると理解度が
向上します。まぁ見なくても分かるよと言われそうですが。。

ちょっと細かいところですが、個人的興味でWindow.widthとsnackbar_xは何が入って
いるんだろうかと思い、見てみた形跡がprint文になります。このコメントアウトを外すと、
以下のように出力されると思われます。

```
window.width=800, snackbar.snackbar_x=10.0
```

あとは漏れているところとして、size_hint_xの値の決め方ですね。これは計算式として
は何をやっているのだろうと気になるでしょうが、単にsnackbar_xの位置の区間(Window
から左端のsnackbarとの距離)を2倍にしてWindow.widthを引いています。このままだと、
size_hint_xが何も変わらないことになるので(1以上は1とみなすため)、分母をWindow-
.widthにして割合を出すようにします。こうすることで、計算した区間が後方区間(画面
右端とsnackbarとの距離)が一致することになり左右対称となります。

### 結果

はい、ということでこちらも触れ込みは以上となります。さっそく実行した様子を見て
みましょう。

![181.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/e913f958-310b-33e5-b225-7666a423d4a8.gif)

問題ありませんね、と言いたいところなのですが、ところどころ反映がされていない
様子です。プロパティとしてはtext、ウィジェットとしては中に埋め込まれていMD-
FlatButtonとなります。これらは色指定をしているのにも関わらず、期待とは異なる
こととなっています。このあたりは次バージョンにて改善されていることを祈るのみです。

## API - kivymd.uix.snackbar.snackbar

まとめに入る前に、使用したAPIについておさらいをしておきます。

### class kivymd.uix.snackbar.snackbar.BaseSnackbar(**kwargs)

カスタムレイアウトをしたときに指定した継承元クラスでしたね。マニュアルでは
概要が以下のように記されています。

> Abstract base class for all Snackbars. This class handles sizing, 
> positioning, shape and events for Snackbars

> All Snackbars will be made off of this BaseSnackbar.

> BaseSnackbar will always try to fill the remainder of the screen with your Snackbar.

> To make your Snackbar dynamic and symetric with snackbar_x.

> Set size_hint_x like below:
> size_hint_xの指定の仕方

BaseSnackbarは抽象クラスとなるんですね。この時点でこれをインスタンス化する
ことは不可能ということがわかります。また、snackbar_xの仕様も記載がありますね。

#### Events

`on_open`

> Called when a dialog is opened.

`on_dismiss`

`When the front layer rises.`

Snackbarをオープンするときに指定するイベントがopen()なはずですが、記載とは
異なりますね。。記載が古いのか、はたまたシノニムなのかまたは全然違うのか...
こればっかりは現状わかっていません。。

#### property

今度はプロパティに入ります。タイトルは全然そんなものはマニュアルにありませんが、
分かりやすくこう名前付けをしました。

`bg_color`

> Snackbar background.

> bg_color is a ColorProperty and defaults to None.

背景色になりますね。これはちゃんと有効化されていました。

`buttons`

> Snackbar buttons.

> buttons is a ListProperty and defaults to ‘[]’

これは一部有効化されていましたね。ボタンは配置されていましたが、色選択とかは
有効化されていませんでした。今後に期待したいところです。

`snackbar_x`

> The snackbar x position in the screen

> snackbar_x is a NumericProperty and defaults to 0dp.

`snackbar_y`

> The snackbar x position in the screen

> snackbar_y is a NumericProperty and defaults to 0dp.

snackbarの位置でしたね。デフォルトは画面左下でスペースがない仕様となります。
これはアプリの設計でどうするかということになりそうです。

`dismiss(self, *args)`

> Dismiss the snackbar.

`open(self)`

> Show the snackbar.

`on_open(self, *args)`

> Called when a dialog is opened.

`on_dismiss(self, *args)`

> Called when the dialog is closed.

APIの最初の方で現状分かっていないと言っていましたが、ちゃんと書いてあり
ましたね。マニュアルは大事ですね。おそらく開発者側が使用するのが、open
だとかdismissなのでしょうか。on_*とかとどう違うのだろう。。

### class kivymd.uix.snackbar.snackbar.Snackbar(**kwargs)

マニュアルでは以下のように、BaseSnackbarを継承しているぜとありますね。

> Snackbar inherits all its functionality from BaseSnackbar

`text`

> The text that will appear in the snackbar.

> text is a StringProperty and defaults to ‘’.

`font_size`

> The font size of the text that will appear in the snackbar.

> font_size is a NumericProperty and defaults to ’15sp’.

可変に値が決められるように、textとfont_sizeプロパティはこちら側にあり
ましたね。しかし、カスタム化するときにはこれらがないので、クラス側にて
それぞれのプロパティをおく必要があるというのは合点がいくところで、意識
しておきたいところですね。

## まとめ

さて、いかがだったでしょうか。

アプリを彩るというと変ですが、リアクティブなアプリにしようとすると、必須
のコンポーネントのような気がしてなりません。今までもところどころ出てきま
したが、ようやく触れこめました。

若干ですが、期待通りに動いていないところもあり、使用するには限定する必要
もあります。次バージョンではちゃんと動いているといいですね。

ということで今週はここまでとします。次回はというとTabs篇になります。これ
も量が多いので、前・後編としようかなぁと思いますがあくまで予定ということで。

では、今日はこの辺にしようと思います。

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

## 参照

>  Components » Snackbar
> https://kivymd.readthedocs.io/en/latest/components/snackbar/