どうもこんばんわ。
パイセンの犯人捕まったそうですね。全然関係ない枠で知った。
(テレビにパイセン映し出されたの普通に面白すぎる)
ニコ生でパイセンの放送何回か見たことあるけど、
パイセンでもちゃんと警察動いててなんか凄いなーって(ひどい言い方だよ)
そんなパイセンを忘れないようにここに書いておくことにします。 → すまん前科が多すぎて忘れられそうにない。いつ狙われてもおかしくないような人だったし、、
2018 年ってもう 6 年も前になるの・・・えぇ
懐かしすぎる、
ニコ生年齢あんけ in パイセンの枠
2018 年くらいらしい
2020 年にニコ生戻ってきて?やってたそうだけど、生前どこでやってたかはわからん。
FC2
とかもBAN
とかなんとか。
本題
それとこれとは関係ないのですが、
(決してパイセンがドラッグやって捕まったからドラッグアンドドロップに繋がったわけではない)
Jetpack Compose
でテキストとかファイルの、ドラッグアンドドロップ機能がつけられるようになったみたいなので、
試してみることにします。
ドラッグアンドドロップ自体はAndroid 7
くらいからありますが、
Android 14 でパワーアップしたのでそれも紹介。あんまり盛り上がってなくて悲しい。
環境
なまえ | あたい |
---|
Android Studio | Android Studio Jellyfish 2023.3.1 Patch 1 |
Jetpack Compose | 2024.05.00 |
minSdk | 24 (ドラッグアンドドロップが Android 7 以降?) |
ドキュメント
テキストをドラッグアンドドロップで貼り付けるとかは、そんなに難しくない。
画像とか、バイナリデータをやり取りしたい場合は一気に面倒になる。
テキストをやり取りしてみる
まずは簡単な、テキストをやり取りしてみることにします。
共通レイアウト
送信と受信が両方試せるように、ドラッグアンドドロップの開始、終了をそれぞれ置きました。
処理はこのあと書きます
送信側
ClipData
にドラッグアンドドロップで送りたいデータを詰め込みます。
あとは、ドラッグアンドドロップしたいBox()
とかのコンポーネントのModifier
へ、dragAndDropSource { }
することで、長押し時に移動できるようになります。
ClipData.newPlainText
の第1引数、"Text"
の部分、ドキュメントにはユーザーへ表示する値とかなんとか書いてありますが、
私もそんなUI
見たこと無いので、おそらく開発側が自由に決めていい値のはずです。
受信側
まずはドラッグアンドドロップの受信コールバックを用意します。
ドラッグアンドドロップの操作が開始した、領域の中に入った、出た、ドラッグアンドドロップが終了した。等を知ることが出来ます。
ドラッグアンドドロップで投げ込まれたときのコールバックは必須で、残りの開始とか終了とかは自由に。
今回はUI
側に反映させたいので、コールバックで受け取ることにします。
次に、ドラッグアンドドロップを受信したいコンポーネントのModifier
で、dragAndDropTarget
を呼び出して、受け取ることを示します。
引数ですが、受け取れるデータの種類(MIME-Type
とか見れる)と、さっき作ったコールバックです。
最後に、ドラッグアンドドロップで投げ込まれたデータのパースをします。
適当に、最初のClipData
を取り出して、テキストとして表示するようにしてみます。
ClipData
の詳細(MIME-Type
とかは)ClipDescription
に入っています。
マルチプラットフォームを意識しているのか、toAndroidDragEvent()
で取り出す必要があります。
使ってみる
テキストボックスに適当に文字を入れて、「長押し!」を押すとドラッグアンドドロップが始まって、
それと同時に、受信先のコンポーネントの背景色が変化して、受信先で指を離すと中身が表示されるはずです。どうだろ???
また、アプリを超えても利用できることがこれで分かるはず。
おんなじアプリを2つ作って、アプリを超えてドラッグアンドドロップしてみましたが、これもちゃんと動きます。
もちろん、ドラッグアンドドロップに対応したアプリへの送信、受信も出来ます。
Android 14 要素どこ????
https://9to5google.com/2023/05/19/android-14-drag-and-drop/
iOS
にはすでにあったそうですが、Android
にも来ました。
ドラッグアンドドロップでアプリを超えたい場合、マルチウィンドウ(画面分割)する必要がなくなりました。
ドラッグアンドドロップ中でも、その指を離さなければ、別の指でホーム画面に戻って別アプリを起動したり、アプリを切り替えたり出来るようになりました。
そしてドラッグアンドドロップ中の指を離せばそのアプリに貼り付けられます。
画面分割するほど画面が大きくない場合に便利そう。
もちろんコード上でなにかする必要はありません。
画像もやり取りしたい
画像を入れる場合、ClipData
へ画像は入れられず、バイナリを共有するための仕組みを使い、Uri
を発行してもらい、そのUri
をClipData
へ入れる必要があります。
画像のファイルパスを入れれば良いとか、そういう話ではないのでかなり面倒くさい。
大雑把にこんな感じ。
Android
のIntent
のサイズ上限が出来たあたり(私は知らない)で、このFileProvider
の知見が結構あるので助かる。。。
FileProvider
https://developer.android.com/reference/androidx/core/content/FileProvider
アプリの固有ストレージ内のファイル(Context#getExternalFileDir()
内のファイルとか)をUri
を経由して外部へ公開できるやつ。
ContentProvider
をいい感じに実装してくれたものになります。ので、頑張ればContentProvider
でも同じことが出来るはず??(何もわからない)
データ送信側
FileProvider
が存在することをAndroidManifest.xml
に書く
- 画像とかのバイナリデータを、
Context#getExternalFileDir()
とかContext#getFilesDir()
に保存する
getExternalFileDir
は/sdcard/Android/data/{アプリケーションID}
- こっちは、パソコンに繋いだときにエクスプローラーからアクセスできちゃいます
Android Studio
のDevice Explorer
機能でも
- あと
Android
標準ファイラー(com.android.documentsui
)でも見れます
Files by Google
のことではありません
getFilesDir
は/data/data/{アプリケーションID}
- こっちはパソコンに繋いでも見れません
- デバッグビルド中であれば見れるかも?
- それ以外の場合は
root
を取らない限り見れません
- そのファイルパスを
FileProvider
へ登録する
- その
Uri
をClipData
に入れる
- ドラッグアンドドロップする
データ受信側
ClipData
からUri
を取り出します
ActivityCompat.requestDragAndDropPermissions
を呼び出します
- これで
Uri
が使えるようになります
- 後は画像表示で使えばおけ。
Coil
とかGlide
にUri
を渡せば表示できるのでは無いでしょうか!?!?
- 今回はライブラリ入れるまでもないので、自分で
BitmapFactory
でUri
からBitmap
を作ります...
- 使えるなら画像読み込みライブラリ(
Coil
、Glide
を使うべきです)。面倒事を全部やってくれるので他のことに集中できます。
画像のドラッグアンドドロップを作る
ちなみに、画像とか言っていますが別に画像じゃなくても任意のバイナリをやり取りできるはず。
画像を送る側
送る側はFileProvider
の用意が必要なので面倒。
FileProvider を作る
まずはres/xml
の中に、file_path.xml
を作ります。
多分名前は何でもいいんですけど、ドキュメント通りに行こうと思います。
中身です。
<external-files-path
はContext#getExternalFileDir()
の中にあるファイルを共有するためですね。
Context#getFilesDir()
の場合は<files-path
にする必要があります。
https://developer.android.com/reference/androidx/core/content/FileProvider
次に、FileProvider
を継承したクラスを作ります。
コンストラクタの引数はさっき作ったxml
です。
最後に、AndroidManifest
にFileProvider
を登録してFileProvider 編
は終了。
画像の用意と送信側のレイアウトを作る
レイアウトを作ります。
あと、画像を用意するのが面倒なので、自身のアプリのアイコンをBitmap
で取って、ファイルに保存することにしようと思います。
自身のアイコン画像の取得はこんな感じです。
MainScreen()
に設置するのも忘れないでね。
どうでしょう?
Uri を取得する
次はBitmap
をgetExternalFileDir
に保存して、Uri
を取得します。
先述のとおり、getExternalFileDir
は、見ようと思えば見れるフォルダなので、もしまずいようならgetFilesDir
を選んでください。(xml
も直してね)
Bitmap
の保存先をgetExternalFileDir
の中に作ります。
/images
フォルダを先に作ってますが、これはfile_paths.xml
でpath=""
をimages
にしたからですね。
getUriForFile
でUri
が出来ます。エラーの場合は例外が投げられます。
画像のドラッグアンドドロップをする
テキストと同じ用にdragAndDropSource
を呼び出して、ドラッグアンドドロップで掴めるようにします。
テキストとは違い、newUri
を使って、Uri
を入れたClipData
を作ります。MIME-Type
とかはUri
を使ってMediaStore
に問い合わせて自動でセットしてくれるらしい。
それから、flags
ですが、Uri
に読み取り権限を付与するためのView.DRAG_FLAG_GLOBAL_URI_READ
フラグを立てておく必要があります。
FileProvider
のドキュメントにはIntent
の場合しか書かれてませんが、ClipData
の場合も権限を付与しないと、ドラッグアンドドロップ先で読み取りできません。
他にも書き込み権限とかあります。View
のドキュメント参照。
View
のドキュメントクソ重いので開くときは注意→ https://developer.android.com/reference/android/view/View#DRAG_FLAG_GLOBAL_URI_READ
これで、送信側は完成のはずです。
Google Keep
とかにドラッグアンドドロップ出来るはず!!!!!できた!?!?!?!?
画像を受け取る側
こっちはいくらか簡単です。
受け取る側くらいは考えてもいいんじゃないでしょうか?
mimeTypes()
のチェックは、画像用に直す必要があります。これは画像以外のバイナリ(動画とか)の場合もそうですが。
今回は画像だけなので、MIME-Type
がimage/
で始まっているかを見ています。
Uri
を受け取った後、ActivityCompat.requestDragAndDropPermissions
を呼び出す必要があります。
これを呼び出さないと、Uri
を使って画像へアクセスしようとしても、ブロックされます。Uri
が用済みになったら、release()
してあげましょう。
requestDragAndDropPermissions
した後はUri
を使ってアクセス出来るようになるのでcontext.contentResolver.openInputStream
でデータを読み出して、
Bitmap
にしています。先述のとおり、Glide
やCoil
のライブラリが使えるなら使うべきです。今回はこのためだけにわざわざ入れないですが。。。
これをMainScreen()
に置いて完成!
どうだろ???
画像も受け取れるアプリが出来ましたか???
そーすこーど
https://github.com/takusan23/JetpackComposeFileDragAndDrop
参考にしました
ありざいす