kivy.metrics調査

# kivy.metrics

kivy.metricsは長さの単位を変換する機能を提供します。例えば以下のコードでは

```python
from kivy.factory import Factory

widget = Factory.Widget(width=100)
```

幅が100pixelのWidgetが作られますが、以下のコードでは

```python
from kivy.factory import Factory
from kivy.metrics import inch

widget = Factory.Widget(width=inch(1))
```

幅1inchのWidgetが作られます。この`inch()`がinchからpixelへの単位変換をしているわけです。

```python
from kivy.metrics import inch

print(inch(1))
```

```text
96.0
```

このような物は`inch`の他にも

- `mm` milli meter(ミリメートル)
- `cm` centi meter(センチメートル)
- `pt` 1pt == 約0.35mm だそうです
- `dp`
- `sp`

など豊富に用意されてはいますが、[公式](https://kivy.org/doc/master/api-kivy.metrics.html)が薦めているのは`dp`と`sp`です。

### dp

Density-independent Pixels。直訳すると「density(密度)に依らないpixels」になると思います。これは基本pixel数で指定するのと同じ感覚で使えますが、画面の密度次第で融通を利かせてくれる単位です。

### sp

Scale-independent Pixels。これは`dp`にユーザー側の設定値を反映させた単位で、公式では`font_size`の指定に薦めています。

### 実践

`dp`と`sp`に関しては上の説明だけだと分かりにくいと思うので実際にコードを動かしてみます。

```python
# (KivyLauncherでも動かす為に長いコードになってます)

import textwrap
from kivy.app import App
from kivy.lang import Builder
from kivy.metrics import sp, dp
from kivy.logger import Logger


Logger.info('Application: dp(100) == ' + str(dp(100)))
Logger.info('Application: sp(100) == ' + str(sp(100)))


class TestApp(App):

    def build(self):
        return Builder.load_string(textwrap.dedent('''
            BoxLayout:
                orientation: 'vertical'
                Label:
                    font_size: 100
                    text: '100'
                Label:
                    font_size: '100dp'
                    text: '100dp'
                Label:
                    font_size: '100sp'
                    text: '100sp'
            '''))

    def on_pasue(self):
        return True

TestApp().run()
```

![14_00.png](https://qiita-image-store.s3.amazonaws.com/0/144673/8df0576c-ba73-66b2-e5e6-0c2b2fef3091.png)

```
[INFO   ] [Application ] dp(100) == 100.0
[INFO   ] [Application ] sp(100) == 100.0
```

え〜三つLabelの`font_size`に違いは見られませんね。実はDesktop環境では既定ではこれら三つの単位(`pixel`, `dp`, `sp`)に差はありません。次にこれをAndroidの[KivyLauncher](https://play.google.com/store/apps/details?id=org.kivy.pygame)上で動かしたらどうなるのか見てみます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/144673/84a42a1d-5b6f-d142-dc4b-82c9fa5d56f4.png" width="50%">  

```
[INFO   ] [Application ] dp(100) == 200.0
[INFO   ] [Application ] sp(100) == 200.0
```

`dp`と`sp`が`pixel`の倍の大きさになりました。ここでさらにOSのフォントサイズの設定値を弄ると

<img src="https://qiita-image-store.s3.amazonaws.com/0/144673/5917634d-0744-07fb-3841-8ad595698d5a.png" width="50%"> 

「極大」だと


<img src="https://qiita-image-store.s3.amazonaws.com/0/144673/9032edaa-3268-c545-ebca-f8e942ef9f50.png" width="50%">  

```
[INFO   ] [Application ] dp(100) == 200.0
[INFO   ] [Application ] sp(100) == 260.0
```

になり、「小」だと

<img src="https://qiita-image-store.s3.amazonaws.com/0/144673/e4862193-8ff3-0515-b44c-e532facbdc11.png" width="50%">  

```
[INFO   ] [Application ] dp(100) == 200.0
[INFO   ] [Application ] sp(100) == 170.0
```

となりました。このように`sp`はユーザー側の設定値(フォントサイズ)を反映します。だから公式は`font_size`の指定に`sp`を薦めているわけですね。

### spとdpを積極的に使うべき?

上記のように`dp`と`sp`を用いることで多少画面の密度を考慮した大きさの指定が可能なことが分かりました。ただ実際これらが使いやすいかというとそんなことは無く、例えば幅が`100dp`と言われてもそれが画面の何割くらいを占めるのかが分からない限りLayoutはしづらいですし、何割くらいになるかは実行環境により異なります。結局の所 **割合** が重要であり、それを容易に指定できるhint系propertyには敵いません。またhint系propertyが使えない場面でも`self.parent.width * 0.3`というように割合で長さを求める事は可能なので`dp`を使う必要はありません。

これは`cm`,`mm`,`pt`,`inch`にも同じ事が言えます。画面全体の物理的な大きさが分からない状態でこれらの単位を使っても意味がありません。仮に分かったとしても結局それを元に割合を求めざるを得ないので`self.parent.width * 0.3`といった式を書くのとなんら変わりありません。というわけでこれらの単位も使いづらいと言えます。

私の考えを纏めると、自分でしか使わないアプリやちょっとしたTestコードなら`dp` `sp`を使っても問題ないと思いますが、不特定多数の人に配布する事を考えているなら割合で精密に計算すべきだと思います。ただ`font_size`に限って言うと非割合による指定はありだと思っていて、理由は`font_size`の厳密な調整がそもそも難しいからです。私は[FlexibleLabel](https://www.youtube.com/watch?v=2T9fL3WWCDE)という`font_size`を自動調節するLabelを作った事はあるのですが、まだ満足のいく性能には至っていません。


### NumericPropertyには特定の形式の文字列を代入できる

上に出てきたTestコードには`font_size: '100dp'`と`font_size: '100sp'`という行があります。`font_size`は`NumericProperty`なので、この行では`NumericProperty`に文字列を与えていることになります。そう`NumericProperty`はこのような`'<数値><単位>'`という形式の文字列を受け入れられるようにできています。

```python
button = Button(width='5in', height='50mm', x='20pt', y='10cm')
```

そして与えた文字列は即座にpixel単位の数値に変換されているため、値を取得する時は常に数値です

```python
print(button.width)
print(button.height)
print(button.x)
print(button.y)
```

```
480.0
188.97637939453125
26.66666603088379
377.9527587890625
```

inchの場合だけ`'<数値>inch'`ではなく`'<数値>in'`となるのが注意点ですね。

### Kv言語内では各種関数がimport済み

上に出てきたTestコード内の`font_size: '100dp'`と`font_size: '100sp'`は`font_size: dp(100)`と`font_size: sp(100)`と書く事も可能です。Kv言語内では`kivy.metrics`の持つ関数達(`mm()`, `cm()`, `pt()`, `inch()`, `sp()`, `dp()`)がimport済みだからです。変換結果を別の計算に利用したい時はこれらが使えるかもしれません。