[Kivy]Androidアプリでの画像ファイル選択

プログラミング

ギャラリーから選択する

Kivyを使ってAndroidアプリを作成していて画像を選択する機能をいれたのですが、実装に苦労をしたので方法をメモ。

とりあえず結論としてギャラリーを呼びだしてそこから選択する形で実装しました。方法のサンプルは以下のような感じです。

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy import platform
from kivy.clock import Clock

from kivy.core.window import Window

currentActivity = None
CLS_Activity = None
CLS_Intent = None
ImagesMedia = None

REQUEST_GALLERY = 1
MediaStore_Images_Media_DATA = '_data'

class FileSelect(BoxLayout):
    def open_ImageSelect(self, *args):
        if platform == 'android': 
            self.android_select_image()

    def select_path(self, filepath):
        if filepath != '':
            self.image1.source = filepath
            self.image1.reload()

    def android_select_image(self):
        if platform == 'android':
            from android import activity
             # イメージギャラリー
            intent = CLS_Intent(CLS_Intent.ACTION_PICK,ImagesMedia.EXTERNAL_CONTENT_URI)
            currentActivity.startActivityForResult(intent, REQUEST_GALLERY)
            activity.bind(on_activity_result=self.on_activity_result)

    def on_activity_result(self, request_code, result_code, intent):
        try:
            if request_code == REQUEST_GALLERY:
                
                if result_code == CLS_Activity.RESULT_CANCELED:
                    #未選択
                    Clock.schedule_once(lambda dt: self.select_path(''), 0)
                    return

                if result_code != CLS_Activity.RESULT_OK:
                    raise NotImplementedError('Unknown result_code "{}"'.format(result_code))

                selectedImage = intent.getData()  # Uri
                filePathColumn = [MediaStore_Images_Media_DATA] # String[]

                cursor = currentActivity.getContentResolver().query(selectedImage, filePathColumn, None, None, None) # Cursor
                cursor.moveToFirst()

                columnIndex = cursor.getColumnIndex(filePathColumn[0]) # int

                selectedPicturePath = cursor.getString(columnIndex) # String
                Clock.schedule_once(lambda dt: self.select_path(selectedPicturePath), 0)
                cursor.close()

        finally:
            from android import activity
            activity.unbind(on_activity_result=self.on_activity_result)

class TestFileSelectApp(App):

    def build(self):
        if platform == 'android':
            Window.bind(on_keyboard=self.key_input)

            from jnius import autoclass, cast
            global currentActivity
            global CLS_Activity
            global CLS_Intent
            global ImagesMedia

            PythonActivity = autoclass('org.kivy.android.PythonActivity')
            currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
            CLS_Activity = autoclass('android.app.Activity')
            CLS_Intent = autoclass('android.content.Intent')
            ImagesMedia = autoclass('android.provider.MediaStore$Images$Media')

            from android.permissions import request_permissions, Permission
            request_permissions([Permission.READ_EXTERNAL_STORAGE, Permission.MANAGE_DOCUMENTS])

        return FileSelect()
    
    def key_input(self, window, key, scancode, codepoint, modifier):
        if key == 27:
            return True
        else:
            return False

if __name__ == '__main__':
    TestFileSelectApp().run()

確認用のKVファイル

<FileSelect>:
    image1:image1
    BoxLayout:
        orientation: 'vertical'
        Image:
            id: image1
            source: ''
        Button:
            text: 'image select'
            size_hint_y: 0.1
            on_press: root.open_ImageSelect()

簡単にサンプルの説明をすると、実装のメインとなる関数は下記2つ。

・android_select_image:ギャラリーを呼び出しています。

・on_activity_result:ギャラリーから戻るときの処理。パスを取得しています。

REQUEST_GALLERYは定数にしていますが数値は任意です。どこから戻ってきたのかを判別するもので、呼び出すときに渡した値と同じ値で返ってきます。

あと必要なのが、Android API にアクセスするため Pyjnius を使っています。サンプルでは build内で分岐させていますが頭のグローバル変数宣言時に書いてもいいと思います。

from jnius import autoclass, cast

PythonActivity = autoclass('org.kivy.android.PythonActivity')
currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
Activity = autoclass('android.app.Activity')
Intent = autoclass('android.content.Intent')
ImagesMedia = autoclass('android.provider.MediaStore$Images$Media')

※サンプルで変数名についてる”CLS_”は、ラップしたJavaクラスだと分かりやすくするため適当につけただけで深い意味はないです。

あとは適当で、open_ImageSelect は呼び出しているだけの関数です。

select_path の関数で選択した画像のパスを使っています。サンプルでは表示しているだけですが、選択画像のパスを使ってやりたいことを実装します。呼び出し元と別クラスにしてファイル選択処理を実装する場合は、コールバック関数として渡す形になるかと思います。

以上、私のたどり着いたAndroidアプリでの画像選択方法でした。

(参考)

Error getting path image from Download folder Android
I use this method to get the file path from uri: public String getPath(Uri uri) { Cursor cursor = getContentResolver().query(uri, null, null, null, null); ...

試した方法履歴

上記の方法に至るまでに紆余曲折、いろいろなファイル選択方法を試したのでそれも残しておきたいと思います。

①tkinterのfiledialog

基本的にPC(Windows)をメインで使って開発しています。KivyというよりPythonを触りはじめてからそのまま使っている方法。

【Python/tkinter】ファイルを開くダイアログボックスの表示
ファイルを開くダイアログボックスを表示するには、tkinter.filedialogモジュールのaskopenfilename()関数を使います。 以下にシンプルなサンプルプログラムを示します。 from tkinter import fi

サンプルコード(省略版)

from tkinter import filedialog

~

    def open_ImageSelect(self, *args):
        filepath = filedialog.askopenfilename(
            title = "画像ファイルを開く",
            filetypes = [("Image file", ".bmp .png .jpg .tif") ], # ファイルフィルタ
            initialdir = "./" # 自分自身のディレクトリ
            )

PCでのデバッグまでは問題なかったのですが、いざAndroidで実機確認をしようとしたとき、tkinterがAndroidに対応していないことを知りました・・・

②KivyのFileChooser

Android や iOS など各OSで動作することを謳っているKivy 。Kivy自体が持っているのファイル選択を使えば Android にも対応しているはず。

ということで次に試したものがこちら。

FileChooser — Kivy 2.3.0 documentation

サンプルコード(省略版)

from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, ObjectProperty
from kivy.uix.popup import Popup
~

    def open_FileChooser(self):
        content = FileChoosePopup(load=self.load, cancel=self.popup_close, image_path="./")
        self.popup = Popup(title="Load file", content=content,size_hint=(0.9, 0.9))
        self.popup.open()

    def load(self, path, filename):
        filepath = os.path.join(path, filename[0])
        ~
        self.popup_close()

    def popup_close(self):
        self.popup.dismiss()

class FileChoosePopup(BoxLayout):
    load = ObjectProperty(None)
    cancel = ObjectProperty(None)
    image_path = StringProperty(None)

KVファイル

<FileChoosePopup>:
    BoxLayout:
        orientation: "vertical"
        FileChooser:
            id: fc
            path: root.image_path
            filters: ['*.jpg','*.jpeg','*.png']
            FileChooserIconLayout
        BoxLayout:
            size_hint: (1, 0.1)
            pos_hint: {'center_x': 0.5, 'center_y': 0.5}
            spacing: 20
            Button:
                text: "Cancel"
                on_release: root.cancel()
            Button:
                text: "Load"
                on_release: root.load(fc.path, fc.selection)
                id: ldbtn
                disabled: True if fc.selection==[] else False

Android でも動いたのですが、アイコン表示でもフォルダとファイルの区別くらいしかできず、実現したい画像ファイルを選択する機能としては再検討が必要でした。

(以下、つづきは後日追記予定)

File Manager — KivyMD documentation
Documentation: example using startActivityForResult with bind(on_activity_result=) · Issue #388 · kivy/python-for-android
I'd like to contribute some documentation. This code opens the Android Photo Gallery and gets the filepath of the selected image. It took me a few hours to figu...

コメント

タイトルとURLをコピーしました