1: ( Android ) スマホへのインストール後、アイコン登録スクショ

2: ( Android ) アプリが立ち上がりスクショ

3: ( Android ) ストップウォッチ動作中スクショ

4: ( Windows ) ストップウォッチ立ち上げスクショ

5: ( Windows ) ストップウォッチ稼動スクショ

6: ( Windows ) ストップウォッチ稼動中ウィンドウ縦長変形スクショ

7: ( Windows ) ストップウォッチ稼動中ウィンドウ横長変形スクショ

全てのファイルは 1 つのディレクトリに入っていて、構成は以下の通りです (
興味がある方は GitHub からダウンロードできます)。
button_down.png
button_normal.png
red_button_down.png
red_button_normal.png
Roboto-Medium.ttf
Robot-Thin.ttf
moo.ico
clock.kv
main.py
注意:私、kivy を利用するのは今回が初めてです。
python と kivy で、うまくロジック部分と UI 部分を分けられているか、といわれれば「グチャグチャです」と答えざるを得ません。
今回はあくまでも興味本位で、前から気になっていた kivy を使ってみた、というだけですので、「何だこのコーディング」とか「なんだこの実装」などという冷たいご批判は受け付けません。
力技で、とにかくまずは動かしてみよう、ということですので、あまり参考になさらず、「こんな感じなんだ」とご覧ください。
clock.kv
<BackgroundColor@Widget>:
background_color: 1, 1, 1, 1
canvas.before:
Color:
rgba: root.background_color
Rectangle:
size: self.size
pos: self.pos
<BackgroundLayout@BoxLayout+BackgroundColor>:
font_name: 'Roboto'
background_color: 1, 0, 0, 1
<WatchLabel@Label>:
markup: True
font_name: 'Roboto'
on_size: app.on_size()
<WatchButton@Button>:
font_name: 'Roboto'
font_size: 25
bold: True
border: [2] * 4
ClockLayout:
orientation: 'vertical'
time_prop: time
watch_prop: stopwatch
btn_box: button_layout
start_btn: start
reset_btn: reset
WatchLabel:
id: time
text: '[b]00[/b]:00:00'
BackgroundLayout:
id: button_layout
height: 90
orientation: 'horizontal'
padding: 20
spacing: 20
size_hint: (1, None)
WatchButton:
id: start
text: 'Start'
background_normal: 'button_normal.png'
background_down: 'button_down.png'
on_press: app.cb_start()
WatchButton:
id: reset
text: 'Reset'
background_normal: 'red_button_normal.png'
background_down: 'red_button_down.png'
on_press: app.cb_reset()
WatchLabel:
id: stopwatch
text: '00:00.00'
以上が Kivy のレイアウトファイルです。
次に、このレイアウトファイルを裏で操る Python ファイルがこちら ↓
main.py
from time import strftime
import re
from kivy.app import App
from kivy.core.text import LabelBase
from kivy.core.window import Window
from kivy.resources import resource_add_path
from kivy.utils import get_color_from_hex
from kivy.clock import Clock
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
class ClockLayout(BoxLayout):
time_prop = ObjectProperty(None)
watch_prop = ObjectProperty(None)
btn_box = ObjectProperty(None)
start_btn = ObjectProperty(None)
reset_btn = ObjectProperty(None)
class ClockApp(App):
stop_watch = False
total_past = 0.0
on_size_call = False
font_ratio = 0
texture_ratio = 0
stop_watch_pat = re.compile(r'(?:\[size=\d+\])?(?P<size>\d+)(?:\[/size\])?')
def on_start(self):
Clock.schedule_interval(self.update, 0)
self.root.btn_box.background_color = get_color_from_hex('#e6e6e6')
def update(self, nap):
# self.root.ids.time.text = strftime('[b]%H[/b]:%M:%S')
self.root.time_prop.text = strftime('[b]%H[/b]:%M:%S')
if self.stop_watch:
self.total_past += nap
minutes, seconds = divmod(self.total_past, 60)
micro_size = int(self.root.watch_prop.font_size * 0.7)
self.root.watch_prop.text = f"{int(minutes):02}:{int(seconds):02}.[size={micro_size}]{int(seconds * 100 % 100):02}[/size]"
def cb_start(self):
self.root.start_btn.text = 'Start' if self.stop_watch else 'Stop'
self.stop_watch = not self.stop_watch
def cb_reset(self):
self.stop_watch = False
self.total_past = 0.0
self.root.start_btn.text = 'Start'
micro_size = int(self.root.watch_prop.font_size * 0.7)
self.root.watch_prop.text = f"00:00.[size={micro_size}]00[/size]"
def on_size(self):
if self.on_size_call:
label_x, label_y = self.root.time_prop.size
if (label_x / label_y) < self.texture_ratio:
new_ratio = (label_x * 0.8) / self.texture_ratio
else:
new_ratio = label_y * 0.8
self.root.time_prop.font_size = self.font_ratio * new_ratio
self.root.watch_prop.font_size = self.font_ratio * new_ratio
stop_txt_lst = self.root.watch_prop.text.split('.')
micro_size = int(self.root.watch_prop.font_size * 0.6)
m = self.stop_watch_pat.search(stop_txt_lst[1])
self.root.watch_prop.text = f"{stop_txt_lst[0]}.[size={micro_size}]{m.group('size')}[/size]"
box_height = int(self.root.time_prop.size[1] * 0.4)
self.root.btn_box.height = box_height
self.root.btn_box.padding = int(box_height * 0.15)
self.root.btn_box.spacing = int(box_height * 0.15)
else:
self.font_ratio = self.root.time_prop.font_size / self.root.time_prop.texture_size[1]
self.texture_ratio = self.root.time_prop.texture_size[0] / self.root.time_prop.texture_size[1]
self.on_size_call = True
if __name__ == '__main__':
Window.clearcolor = get_color_from_hex('#ff6666')
# resource_add_path(r"C:\Windows\Fonts")
LabelBase.register(
name='Roboto',
fn_regular='Roboto-Thin.ttf',
fn_bold='Roboto-Medium.ttf'
)
ClockApp().run()
今回かなり躓いたのが on_size() の実装です。
Kivy では Label widget のフォントサイズのデフォルト値が 15 に設定されています。
まず、これをどうやって Label widget の大きさに合わせてそれなりの大きさで表示するのか、という基本部分で悩みました (絶対値指定はしたくなかったので)。
また、クロスプラットフォーム ( cross platform ) 上で動作することが魅力的なんですが、
デスクトップ環境下における利用時に顕著な「window 領域の大きさの変化」にフォントサイズ等を如何に対応させるのか、が問題でした。
今回の結論が on_size() でございます。
見ていただければ一目瞭然、ほぼ力技実装でお恥ずかしい限りです。
Android 用 APK の作成は CentOS 上 (何を隠そうこのサイトを動かしているサーバー = conoha VPS です) で行いました。
Buildozer をインストールし、python-for-android が必要としている dependencis を満たし、出来上がった APK をダウンロードし、AndroidStudio のエミュレーターでは動かなかったので実機に USB 経由でインストールし、何とか動作を確認しました。
この APK を Google Play Store で公開できるのかできないのか、よく分かりませんがちょっとやって後日報告したいと思います。