PartⅠでは PyGame を取り上げました。今回は、スマホのアプリ作成に使える Kivy を取り上げます。kivyは pythonの中でも特殊な形態をもつフレームワークで、Gは使いこなすのに四苦八苦しています。先ずは、example-tutorialsにある notes (メモ帳)のコードを学びましょう。notes (メモ帳)の最終コードは "https://github.com/kivy/kivy/tree/master/examples/tutorials/notes/final" にあります。
以下、この notes (メモ帳)を Noteと呼びます。Noteの初期画面は以下のようになります。この画面をメモ一覧画面とよびます。
[+]ボタンを押すと次の画面に切り替わります。この画面をメモ入力画面とよびます。
メモ入力画面でNew Noteと表示されている箇所にメモのタイトルを記入し、下の空欄にメモの内容を記入します。[x]ボタンを押せば記入した内容が消去されます。[<]ボタンを押せば、下にメモのタイトルが表示されたメモ一覧画面に切り替わります。[+]ボタンを押せばメモを追加できます。
なお、以下に Noteの main.pyと note.kvを掲載しておきます。
''' Notes ===== Simple application for reading/writing notes. ''' __version__ = '1.0' import json from os.path import join, exists from kivy.app import App from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition from kivy.properties import ListProperty, StringProperty, \ NumericProperty, BooleanProperty, AliasProperty from kivy.uix.boxlayout import BoxLayout from kivy.uix.floatlayout import FloatLayout from kivy.clock import Clock class MutableTextInput(FloatLayout): text = StringProperty() multiline = BooleanProperty(True) def __init__(self, **kwargs): super(MutableTextInput, self).__init__(**kwargs) Clock.schedule_once(self.prepare, 0) def prepare(self, *args): self.w_textinput = self.ids.w_textinput.__self__ self.w_label = self.ids.w_label.__self__ self.view() def on_touch_down(self, touch): if self.collide_point(*touch.pos) and touch.is_double_tap: self.edit() return super(MutableTextInput, self).on_touch_down(touch) def edit(self): self.clear_widgets() self.add_widget(self.w_textinput) self.w_textinput.focus = True def view(self): self.clear_widgets() if not self.text: self.w_label.text = "Double tap/click to edit" self.add_widget(self.w_label) def check_focus_and_view(self, textinput): if not textinput.focus: self.text = textinput.text self.view() class NoteView(Screen): note_index = NumericProperty() note_title = StringProperty() note_content = StringProperty() class NoteListItem(BoxLayout): note_content = StringProperty() note_title = StringProperty() note_index = NumericProperty() class Notes(Screen): data = ListProperty() def _get_data_for_widgets(self): return [{ 'note_index': index, 'note_content': item['content'], 'note_title': item['title']} for index, item in enumerate(self.data)] data_for_widgets = AliasProperty(_get_data_for_widgets, bind=['data']) class NoteApp(App): def build(self): self.notes = Notes(name='notes') self.load_notes() self.transition = SlideTransition(duration=.35) root = ScreenManager(transition=self.transition) root.add_widget(self.notes) return root def load_notes(self): if not exists(self.notes_fn): return with open(self.notes_fn) as fd: data = json.load(fd) # fd = 'C:\\Users\\user\\AppData\\Roaming\\note\\notes.json' self.notes.data = data # print(self.notes.data) ''' [{'title': 'a', 'content': '0001'}, {'title': 'b', 'content': '0002'}, {'title': 'c', 'content': '0003'}, {'title': 'd', 'content': '0004'}] ''' def save_notes(self): with open(self.notes_fn, 'w') as fd: json.dump(self.notes.data, fd) def del_note(self, note_index): del self.notes.data[note_index] self.save_notes() self.refresh_notes() self.go_notes() def edit_note(self, note_index): note = self.notes.data[note_index] name = 'note{}'.format(note_index) if self.root.has_screen(name): self.root.remove_widget(self.root.get_screen(name)) view = NoteView( name=name, note_index=note_index, note_title=note.get('title'), note_content=note.get('content')) self.root.add_widget(view) self.transition.direction = 'left' self.root.current = view.name def add_note(self): self.notes.data.append({'title': 'New note', 'content': ''}) note_index = len(self.notes.data) - 1 self.edit_note(note_index) def set_note_content(self, note_index, note_content): self.notes.data[note_index]['content'] = note_content data = self.notes.data self.notes.data = [] self.notes.data = data self.save_notes() self.refresh_notes() def set_note_title(self, note_index, note_title): self.notes.data[note_index]['title'] = note_title self.save_notes() self.refresh_notes() def refresh_notes(self): data = self.notes.data self.notes.data = [] self.notes.data = data def go_notes(self): self.transition.direction = 'right' self.root.current = 'notes' @property def notes_fn(self): return join(self.user_data_dir, 'notes.json') if __name__ == '__main__': NoteApp().run()
#:kivy 1.7.1 #:import Factory kivy.factory.Factory <Screen >: canvas: Color: rgb: .2, .2, .2 Rectangle: size: self.size <MutableLabelTextInput@MutableTextInput >: Label: id: w_label pos: root.pos text: root.text TextInput: id: w_textinput pos: root.pos text: root.text multiline: root.multiline on_focus: root.check_focus_and_view(self) <MutableRstDocumentTextInput@MutableTextInput >: RstDocument: id: w_label pos: root.pos text: root.text TextInput: id: w_textinput pos: root.pos text: root.text multiline: root.multiline on_focus: root.check_focus_and_view(self) <NoteView >: on_note_content: app.set_note_content(self.note_index, self.note_content) on_note_title: app.set_note_title(self.note_index, self.note_title) BoxLayout: orientation: 'vertical' BoxLayout: orientation: 'horizontal' size_hint_y: None height: '48dp' padding: '5dp' canvas: Color: rgb: .3, .3, .3 Rectangle: pos: self.pos size: self.size Button: text: '<' size_hint_x: None width: self.height on_release: app.go_notes() TextInput: text: root.note_title font_size: '16sp' multiline: False on_text: root.note_title = self.text Button: text: 'X' size_hint_x: None width: self.height on_release: app.del_note(root.note_index) TextInput: text: root.note_content on_text: root.note_content = self.text <NoteListItem >: height: '48sp' size_hint_y: None canvas: Color: rgb: .3, .3, .3 Rectangle: pos: self.pos size: self.width, 1 BoxLayout: padding: '5dp' Label: text: root.note_title Button: text: '>' size_hint_x: None width: self.height on_release: app.edit_note(root.note_index) <Notes >: BoxLayout: orientation: 'vertical' BoxLayout: orientation: 'horizontal' size_hint_y: None height: '48dp' padding: '5dp' canvas: Color: rgb: .3, .3, .3 Rectangle: pos: self.pos size: self.size Image: source: 'data/icon.png' mipmap: True size_hint_x: None width: self.height Label: text: 'Notes' font_size: '16sp' Button: text: '+' size_hint_x: None width: self.height on_release: app.add_note() RecycleView: data: root.data_for_widgets viewclass: 'NoteListItem' RecycleBoxLayout: default_size: None, dp(56) default_size_hint: 1, None size_hint_y: None height: self.minimum_height orientation: 'vertical' spacing: dp(2)
本サイトでは、この Noteのコードを参考にしながら Noteに準じたアプリを作成していきます。Kivyの文法に慣れるために、先ずはメモ内容を記入するメモ記入画面を作成しましょう。
pythonは既にインストール済なので、kivy(2.0.0)をインストールします。pipで簡単にインストールできるはずですが、もしできなかったら
"https://creepfablic.site/2021/05/02/python-kivy-windows-mac-install/"をご覧ください。
無事にインストールが完了したら、テスト画面を表示させましょう。
kivyでは、pythonコード(main.py)と kvコード(*.kv)の二つを用います。pythonコードだけで作成することもできますが、Gは二つのファイルに分けて作成することにします。
pythonコード(main.py)です。
from kivy.app import App class MemoApp(App): pass if __name__ == '__main__': MemoApp().run()
kivy.appから Appをインポートしています。この文は、kivyを使う上で必須となります。
Appを継承してクラス名(MemoApp)を定義します。今は何もしないので passとだけ記載します。
if文以下は呪文です。
kvコード(memo.kv)です。2行だけです。
Label: text: "Hello World"
"Hello World"と表示するラベルを配置させるコードです。
kvファイルには、表示するウィジェット(ここでは Labal)と、そのプロパティ(ここでは text)が書かれます。またファイル名は、pythonファイルのクラス名(MemoApp)から Appを除いた名称を小文字で記します。任意のファイル名を使う場合は、pyファイルに Builder.load_file('ファイル名.kv')と指定すれば OKです。
この二つのファイルを同じフォルダに配置し、コマンドプロンプトで python main.pyと打てば、中央に "Hello World"と書かれたラベルをもつ画像が表示されます。