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を試してみる |
プチモンテ ※この記事を書いた人
![]() | |
![]() | 💻 ITスキル・経験 サーバー構築からWebアプリケーション開発。IoTをはじめとする電子工作、ロボット、人工知能やスマホ/OSアプリまで分野問わず経験。 画像処理/音声処理/アニメーション、3Dゲーム、会計ソフト、PDF作成/編集、逆アセンブラ、EXE/DLLファイルの書き換えなどのアプリを公開。詳しくは自己紹介へ |
| 🎵 音楽制作 BGMは楽器(音源)さえあれば、何でも制作可能。歌モノは主にロック、バラード、ポップスを制作。歌詞は抒情詩、抒情的な楽曲が多い。楽曲制作は🔰2023年12月中旬 ~ | |









