WebViewの<input type="file" multiple>でファイルを複数選択する [Android]
目次
1. 作るもの
2. 初期設定
3. 画面設計
4. HTMLファイル
5. コーディング
1. 作るもの
WebViewの<input type="file">でカメラ、画像からデータを取得するのファイルを複数選択するバージョンです。
ギャラリーでロングタップをするとファイルを複数選択できます。
選択した画像を表示しています。
2. 初期設定
2-1. APIレベル
今回はWebViewコントロールを扱うのでAndroidの動作可能なAPIレベルは21(Android5.0/Lollipop)にしています。
設定方法はAndroid Studioで左のTreeViewにあるGradle Scriptsのbuild.gradleの「minSdkVersionを21」にします。
2-2. 権限
[AndroidManifest.xml]
ファイルを扱うので次のストレージ権限を追加します。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
3. 画面設計
WebViewを1つ配置します。
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <WebView android:id="@+id/webView" android:layout_width="368dp" android:layout_height="447dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>
4. HTMLファイル
HTMLをリソースに追加するにはWebViewの基本操作をご覧下さい。
[index.html]
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script> // 非同期で画像の読み込み function getAsynchroImageData(file) { return new Promise(function(resolve, reject) { var image = new Image; // イメージが読み込まれた image.onload = function (){ var canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; ctx = canvas.getContext("2d"); ctx.drawImage(image,0,0); resolve(ctx.getImageData(0, 0, image.width, image.height)); } // イメージが読み込めない image.onerror = function (){ reject(Error('この画像は読み込めません。\n' + file.name)); }; image.src = file.data; }); } // 非同期でファイルを読み込む function getAsynchroFile(file) { return new Promise(function(resolve, reject) { var reader = new FileReader(); reader.onload = function (event) { resolve(getAsynchroImageData({'data':reader.result,'name':file.name})); } reader.onerror = function (event) { reject('ファイルの読み込みに失敗しました'); } reader.readAsDataURL(file); }); } function onAddFile(event) { var reader = new FileReader(); var files = event.target.files; for(var i=0;i<files.length;i++){ // ファイル(画像)の読み込み getAsynchroFile(files[i]).then(function(imagedata){ // canvasを生成してImageDataを描画する var canvas = document.createElement('canvas'); canvas.width = imagedata.width; canvas.height = imagedata.height; ctx = canvas.getContext("2d"); ctx.putImageData(imagedata,0,0); document.body.appendChild(canvas); // 読み込み失敗 }).catch(function(err){ alert(err); }); } } </script> </head> <body> <br> <input type="file" id="inputfile" accept="image/*" onchange="onAddFile(event);" multiple><br> <br> </body> </html>
選択されたファイルはPromiseオブジェクトを使用して非同期でデータを取得しています。このPromiseの注意事項としてはAndroid5-7はfinallyを使用できますがAndroid8ではfinallyが使用できません。
catchの次にfinallyを使用したい場合はthenを使用してください。これならばAndroid5,7,8で動作確認済みです。
また、ファイルの複数選択はAndroid側のコードで対応するので<input type="file" multiple>のmultipleはあってもなくてもOKです。
5. コーディング
複数選択できるのはファイルのみです。カメラの写真は1枚だけです。
import android.Manifest; import android.app.Activity; import android.content.ClipData; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebView; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.util.ArrayList; public class MainActivity extends AppCompatActivity { // URIリスト(テンポラリ) private ArrayList<Uri> UriList = new ArrayList<>(); // INIファイルのアクセス用 private SharedPreferences.Editor editor; // カメラ & 画像選択ダイアログ用 public static final int INPUT_FILE_REQUEST_CODE = 1; private ValueCallback<Uri[]> mFilePathCallback; private String mCameraPhotoPath; //////////////////////////////////////////////////////////////////////////////////////////////////// // 汎用メソッド //////////////////////////////////////////////////////////////////////////////////////////////////// // パーミッションダイアログ public boolean CheckPermission(Activity actibity, String permission, int requestCode){ // 権限の確認 if (ActivityCompat.checkSelfPermission(actibity, permission) != PackageManager.PERMISSION_GRANTED) { // 権限の許可を求めるダイアログを表示する ActivityCompat.requestPermissions(actibity, new String[]{permission},requestCode); return false; } return true; } // リソースからファイルを生成する(/data/data/パッケージ名/files/) public boolean setRawResources(Context context , int resourcesID, String fileName){ boolean result = false; // リソースの読み込み InputStream is = context.getResources().openRawResource(resourcesID); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte [] buffer = new byte[1024]; try{ // 1024バイト毎、ファイルを読み込む while(true) { int len = is.read(buffer); if(len < 0) break; baos.write(buffer, 0, len); } }catch (Exception e){ e.printStackTrace(); return result; } // ファイルの生成 File file = new File(context.getFilesDir() + "/" + fileName); FileOutputStream fos = null; try { fos = new FileOutputStream(file); fos.write(baos.toByteArray()); result = true; } catch (Exception e) { e.printStackTrace(); } finally { if(fos != null){ try{ fos.close(); }catch (Exception e){ e.printStackTrace(); } } } return result; } //////////////////////////////////////////////////////////////////////////////////////////////////// // イベント //////////////////////////////////////////////////////////////////////////////////////////////////// @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // *************** // INI設定 // *************** // INIファイルのアクセス用 // ※「カメラ or 画像」の選択画面を表示する度に // ※Picturesフォルダに0バイトのファイルが作成されるので,それを削除する為にです。 SharedPreferences prefs = getSharedPreferences("MyINI", MODE_PRIVATE); editor = prefs.edit(); // INIファイルからURIリストの情報を読み込む int count = prefs.getInt("count", 0); for (int i = 0; i < count; i++) { try { String str = prefs.getString("uri" + i, ""); UriList.add(Uri.parse(str)); } catch (Exception e) { e.printStackTrace(); } } // *************** // 権限設定 // *************** // ストレージの権限確認 CheckPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE, 1000); // *************** // リソース // *************** // リソースからファイルを生成する(/data/data/パッケージ名/files/に作成) setRawResources(MainActivity.this, R.raw.index, "index.html"); // *************** // WebView // *************** final WebView webView = findViewById(R.id.webView); // キャッシュクリア // ※開発時のみ有効にする webView.clearCache(true); // JavaScriptを有効にする webView.getSettings().setJavaScriptEnabled(true); // <input type="file">で「カメラ、画像の選択」を表示する ※複数選択対応版 // ※元ソースはGoogleです。参考URLを参照してください。 // ※そのままでは動作しないので改変しています。 webView.setWebChromeClient(new WebChromeClient() { public boolean onShowFileChooser( WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { if (mFilePathCallback != null) { mFilePathCallback.onReceiveValue(null); } mFilePathCallback = filePathCallback; mCameraPhotoPath = null; Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(MainActivity.this.getPackageManager()) != null) { String filename = Environment.getExternalStorageDirectory() + "/dummy.jpg"; ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.TITLE, filename); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); Uri uri = getContentResolver(). insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); UriList.add(uri); // INIファイルにURIリストの情報を書き込む for (int i = 0; i < UriList.size(); i++) { editor.putString("uri" + i, UriList.get(i).toString()); } editor.putInt("count", UriList.size()); editor.apply(); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri); mCameraPhotoPath = uri.toString(); } Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); contentSelectionIntent.setType("image/*"); contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); Intent[] intentArray; if (takePictureIntent != null) { intentArray = new Intent[]{takePictureIntent}; } else { intentArray = new Intent[0]; } Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); chooserIntent.putExtra(Intent.EXTRA_TITLE, "選択"); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE); return true; } }); // ファイルを読み込む webView.loadUrl("file:///" + MainActivity.this.getFilesDir() + "/index.html"); } // <input type="file">で「カメラ、画像の選択」を表示する ※複数選択対応版 // ※元ソースはGoogleです。参考URLを参照してください。 // ※そのままでは動作しないので改変しています。 @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode != INPUT_FILE_REQUEST_CODE || mFilePathCallback == null) { super.onActivityResult(requestCode, resultCode, data); return; } Uri[] results = null; if (resultCode == Activity.RESULT_OK) { if (data == null) { if (mCameraPhotoPath != null) { // カメラ results = new Uri[]{Uri.parse(mCameraPhotoPath)}; } } else { String dataString = data.getDataString(); if (dataString != null) { // 単一選択 results = new Uri[]{Uri.parse(dataString)}; } else { ClipData clipData = data.getClipData(); if(clipData == null) { // カメラ results = new Uri[]{Uri.parse(mCameraPhotoPath)}; }else{ // 複数選択 results = new Uri[clipData.getItemCount()]; for (int i = 0; i < clipData.getItemCount(); i++) { results[i] = clipData.getItemAt(i).getUri(); } } } } } mFilePathCallback.onReceiveValue(results); mFilePathCallback = null; return; } }
参考URL(Google)
https://gauntface.com/blog/2014/10/17/what-you-need-to-know-about-the-webview-in-l
https://github.com/googlearchive/chromium-webview-samples/blob/master/input-file-example/app/src/main/java/inputfilesample/android/chrome/google/com/inputfilesample/MainFragment.java
関連記事
前の記事: | WebViewで「ファイルのダウンロード」(Blob形式)のデータを取得する [Android] |
次の記事: | Struts1.3.10のHelloWorldとデータベース、Examplesを試してみる |