TOP > カテゴリ > Java・Android >

WebViewの<input type="file" multiple>でファイルを複数選択する [Android]

Android Studioの使い方(目次)

目次

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





関連記事



公開日:2018年06月12日
記事NO:02676