php 有没有一种方法使Flutter的网页使用android相机的文件上传?如何在webview_flutter flutter中打开文件选取器?

neskvpey  于 2023-09-29  发布在  PHP
关注(0)|答案(4)|浏览(145)

我有一个flutter web应用程序,它带有一个webview,可以从服务器加载我的PHP项目。在我的PHP项目中,我有一个注册表单,需要用户使用相机拍摄照片,然后将其上传到MySQL DB。问题是,当我点击上传文件按钮使用相机时,它什么也不做。但在浏览器中,文件选择器是工作,但在我的android从webview它没有做任何事情。
我试过了,它不起作用。

<div class="col-sm-4">
Image 1 <span style="color:red">*</span><input type="file" name="img1" accept"image/*" capture="camera"  required>
</div>

这也是我的进口货。

import 'package:car_renting_app/Animations/FadeAnimation.dart';
import 'package:car_renting_app/onboarding.dart';
import 'package:car_renting_app/popup.dart';
import 'package:car_renting_app/ui/adminwebview.dart';
import 'package:car_renting_app/widgets/animated_botton_bar.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:connectivity/connectivity.dart';

这是我的pubsec。yaml

cupertino_icons: ^0.1.2
webview_flutter:
simple_animations: ^1.1.3
page_transition: ^1.1.4
carousel_pro: ^0.0.13
url_launcher: ^5.1.1
sliding_up_panel: ^0.3.4
font_awesome_flutter: ^8.5.0
flutter_swiper: ^1.1.6
connectivity:
uni_links:
pxy2qtax

pxy2qtax1#

我认为这是使用“webview_flutter”的替代解决方案:https://github.com/flutter/flutter/issues/27924#issuecomment-647197754
必须编辑“FlutterWebView.java“和“AndroidManifest”。

1l5u6lss

1l5u6lss2#

@Bruno的链接回答对我不起作用。所以我找到了很多解决方案,并把它们结合起来使用,最后它对我起作用了。webview_flutter: ^2.0.8(此解决方案也适用于较低版本)

  • 前提条件**:-在实施本方案之前,需要先进行权限处理,摄像头和存储权限 *

1.转到Flutter的外部库
1.导航到webview_flutter插件项目文件夹
1.导航到Android文件夹
1.转到src>main
1.打开manifest文件,它应该是空的,里面只有包名,然后复制粘贴这个

<provider
     android:name="io.flutter.plugins.webviewflutter.GenericFileProvider"
     android:authorities="${applicationId}.generic.provider"
     android:exported="false"
     android:grantUriPermissions="true"
     >
     <meta-data
         android:name="android.support.FILE_PROVIDER_PATHS"
         android:resource="@xml/provider_paths"
         />
 </provider>

 </application>
 <queries>
     <package android:name="com.google.android.apps.photos" />
     <package android:name="com.google.android.apps.docs" />
     <package android:name="com.google.android.documentsui" />
 </queries>

1.如果主文件夹下没有res目录,则创建res目录
1.创建values目录,然后在其中创建strings.xml:复制并粘贴此

<resources>
    <string name="webview_file_chooser_title">Choose a file</string>
    <string name="webview_image_chooser_title">Choose an image</string>
    <string name="webview_video_chooser_title">Choose a video</string>
</resources>

1.创建xml文件夹,然后在其中创建provider_paths. xml,复制并粘贴此

<paths xmlns:android="http://schemas.android.com/apk/res/android">
     <external-files-path name="safetyapp_images" path="images" />
 </paths>

1.转到main>java>io>flutter>plugins>webviewflutter
1.创建名为Constants.java的java类,并复制以下内容
软件包io.flutter.plugins.webviewflutter;

public class Constants {
        static final String ACTION_REQUEST_CAMERA_PERMISSION_FINISHED =
                "action_request_camera_permission_denied";
        static final String ACTION_FILE_CHOOSER_FINISHED = "action_file_chooser_completed";
    
        static final String EXTRA_TITLE = "extra_title";
        static final String EXTRA_ACCEPT_TYPES = "extra_types";
        static final String EXTRA_SHOW_VIDEO_OPTION = "extra_show_video_option";
        static final String EXTRA_SHOW_IMAGE_OPTION = "extra_show_image_option";
        static final String EXTRA_FILE_URIS = "extra_file_uris";
        static final String EXTRA_ALLOW_MULTIPLE_FILES = "extra_allow_multiple_files";
    
    
        static final String WEBVIEW_STORAGE_DIRECTORY = "images";
    }

1.创建FileChooserActivity.java并复制此
软件包io.flutter.plugins.webviewflutter;

import static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ACCEPT_TYPES;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ALLOW_MULTIPLE_FILES;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URIS;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_IMAGE_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_VIDEO_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE;
import static io.flutter.plugins.webviewflutter.Constants.WEBVIEW_STORAGE_DIRECTORY;

import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

public class FileChooserActivity extends Activity {

private static final int FILE_CHOOSER_REQUEST_CODE = 12322;
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");

// List of Uris that point to files where there MIGHT be the output of the capture. At most one of these can be valid
private final ArrayList<Uri> potentialCaptureOutputUris = new ArrayList<>();

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    showFileChooser(
            getIntent().getBooleanExtra(EXTRA_SHOW_IMAGE_OPTION, false),
            getIntent().getBooleanExtra(EXTRA_SHOW_VIDEO_OPTION, false));
}

private void showFileChooser(boolean showImageIntent, boolean showVideoIntent) {
    Intent getContentIntent = createGetContentIntent();
    Intent captureImageIntent =
            showImageIntent ? createCaptureIntent(MediaStore.ACTION_IMAGE_CAPTURE, "jpg") : null;
    Intent captureVideoIntent =
            showVideoIntent ? createCaptureIntent(MediaStore.ACTION_VIDEO_CAPTURE, "mp4") : null;

    if (getContentIntent == null && captureImageIntent == null && captureVideoIntent == null) {
        // cannot open anything: cancel file chooser
        sendBroadcast(new Intent(ACTION_FILE_CHOOSER_FINISHED));
        finish();
    } else {
        ArrayList<Intent> intentList = new ArrayList<>();

        if (getContentIntent != null) {
            intentList.add(getContentIntent);
        }

        if (captureImageIntent != null) {
            intentList.add(captureImageIntent);
        }
        if (captureVideoIntent != null) {
            intentList.add(captureVideoIntent);
        }

        Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
        chooserIntent.putExtra(Intent.EXTRA_TITLE, getIntent().getStringExtra(EXTRA_TITLE));

        chooserIntent.putExtra(Intent.EXTRA_INTENT, intentList.get(0));
        intentList.remove(0);
        if (intentList.size() > 0) {
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentList.toArray(new Intent[0]));
        }

        startActivityForResult(chooserIntent, FILE_CHOOSER_REQUEST_CODE);
    }
}

private Intent createGetContentIntent() {
    Intent filesIntent = new Intent(Intent.ACTION_GET_CONTENT);

    if (getIntent().getBooleanExtra(EXTRA_ALLOW_MULTIPLE_FILES, false)) {
        filesIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
    }

    String[] acceptTypes = getIntent().getStringArrayExtra(EXTRA_ACCEPT_TYPES);

    if (acceptTypes.length == 0 || (acceptTypes.length == 1 && acceptTypes[0].length() == 0)) {
        // empty array or only 1 empty string? -> accept all types
        filesIntent.setType("*/*");
    } else if (acceptTypes.length == 1) {
        filesIntent.setType(acceptTypes[0]);
    } else {
        // acceptTypes.length > 1
        filesIntent.setType("*/*");
        filesIntent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes);
    }

    return (filesIntent.resolveActivity(getPackageManager()) != null) ? filesIntent : null;
}

private Intent createCaptureIntent(String type, String fileFormat) {
    Intent captureIntent = new Intent(type);
    if (captureIntent.resolveActivity(getPackageManager()) == null) {
        return null;
    }

    // Create the File where the output should go
    Uri captureOutputUri = getTempUri(fileFormat);
    potentialCaptureOutputUris.add(captureOutputUri);

    captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureOutputUri);

    return captureIntent;
}
private File getStorageDirectory() {
    File imageDirectory = new File(this.getExternalFilesDir(null), WEBVIEW_STORAGE_DIRECTORY);
    if (!imageDirectory.isDirectory()) {
        imageDirectory.mkdir();
    }
    return imageDirectory;
}

private Uri getTempUri(String format) {
    String fileName = "CAPTURE-" + simpleDateFormat.format(new Date()) + "." + format;
    File file = new File(getStorageDirectory(), fileName);
    return FileProvider.getUriForFile(
            this, getApplicationContext().getPackageName() + ".generic.provider", file);
}

private String getFileNameFromUri(Uri uri) {
    Cursor returnCursor = getContentResolver().query(uri, null, null, null, null);
    assert returnCursor != null;
    int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
    returnCursor.moveToFirst();
    String name = returnCursor.getString(nameIndex);
    returnCursor.close();
    return name;
}

private Uri copyToLocalUri(Uri uri) {
    File destination = new File(getStorageDirectory(), getFileNameFromUri(uri));

    try (InputStream in = getContentResolver().openInputStream(uri);
         OutputStream out = new FileOutputStream(destination)) {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = in.read(buffer)) != -1) {
            out.write(buffer, 0, len);
        }
        return FileProvider.getUriForFile(
                this, getApplicationContext().getPackageName() + ".generic.provider", destination);
    } catch (IOException e) {
        Log.e("WEBVIEW", "Unable to copy selected image", e);
        e.printStackTrace();
        return null;
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == FILE_CHOOSER_REQUEST_CODE) {
        Intent fileChooserFinishedIntent = new Intent(ACTION_FILE_CHOOSER_FINISHED);
        if (resultCode == Activity.RESULT_OK) {
            if (data != null && (data.getDataString() != null || data.getClipData() != null)) {
                if (data.getDataString() != null) {
                    // single result from file browser OR video from camera
                    Uri localUri = copyToLocalUri(data.getData());
                    if (localUri != null) {
                        fileChooserFinishedIntent.putExtra(
                                EXTRA_FILE_URIS, new String[] {localUri.toString()});
                    }
                } else if (data.getClipData() != null) {
                    // multiple results from file browser
                    int uriCount = data.getClipData().getItemCount();
                    String[] uriStrings = new String[uriCount];

                    for (int i = 0; i < uriCount; i++) {
                        Uri localUri = copyToLocalUri(data.getClipData().getItemAt(i).getUri());
                        if (localUri != null) {
                            uriStrings[i] = localUri.toString();
                        }
                    }
                    fileChooserFinishedIntent.putExtra(EXTRA_FILE_URIS, uriStrings);
                }
            } else {
                // image result from camera (videos from the camera are handled above, but this if-branch could handle them too if this varies from device to device)
                for (Uri captureOutputUri : potentialCaptureOutputUris) {
                    try {
                        // just opening an input stream (and closing immediately) to test if the Uri points to a valid file
                        // if it's not a real file, the below catch-clause gets executed and we continue with the next Uri in the loop.
                        getContentResolver().openInputStream(captureOutputUri).close();
                        fileChooserFinishedIntent.putExtra(
                                EXTRA_FILE_URIS, new String[] {captureOutputUri.toString()});
                        // leave the loop, as only one of the potentialCaptureOutputUris is valid and we just found it
                        break;
                    } catch (IOException ignored) {
                    }
                }
            }
        }
        sendBroadcast(fileChooserFinishedIntent);
        finish();
    } else {
        super.onActivityResult(requestCode, resultCode, data);
    }
   }
 }

1.创建这个类FileChooserLauncher.java并复制这个
软件包io.flutter.plugins.webviewflutter;

import static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.ACTION_REQUEST_CAMERA_PERMISSION_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ACCEPT_TYPES;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ALLOW_MULTIPLE_FILES;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URIS;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_IMAGE_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_VIDEO_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE;

import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.webkit.ValueCallback;
import androidx.core.content.ContextCompat;

public class FileChooserLauncher extends BroadcastReceiver {

private Context context;
private String title;
private boolean allowMultipleFiles;
private boolean videoAcceptable;
private boolean imageAcceptable;
private ValueCallback<Uri[]> filePathCallback;
private String[] acceptTypes;

public FileChooserLauncher(
        Context context,
        boolean allowMultipleFiles,
        ValueCallback<Uri[]> filePathCallback,
        String[] acceptTypes) {
    this.context = context;
    this.allowMultipleFiles = allowMultipleFiles;
    this.filePathCallback = filePathCallback;
    this.acceptTypes = acceptTypes;

    if (acceptTypes.length == 0 || (acceptTypes.length == 1 && acceptTypes[0].length() == 0)) {
        // acceptTypes empty -> accept anything
        imageAcceptable = true;
        videoAcceptable = true;
    } else {
        for (String acceptType : acceptTypes) {
            if (acceptType.startsWith("image/")) {
                imageAcceptable = true;
            } else if (acceptType.startsWith("video/")) {
                videoAcceptable = true;
            }
        }
    }

    if (imageAcceptable && !videoAcceptable) {
        title = context.getResources().getString(R.string.webview_image_chooser_title);
    } else if (videoAcceptable && !imageAcceptable) {
        title = context.getResources().getString(R.string.webview_video_chooser_title);
    } else {
        title = context.getResources().getString(R.string.webview_file_chooser_title);
    }
}

private boolean canCameraProduceAcceptableType() {
    return imageAcceptable || videoAcceptable;
}

private boolean hasCameraPermission() {
    return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
            == PackageManager.PERMISSION_GRANTED;
}

public void start() {
    if (!canCameraProduceAcceptableType() || hasCameraPermission()) {
        showFileChooser();
    } else {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED);
        context.registerReceiver(this, intentFilter);

        Intent intent = new Intent(context, RequestCameraPermissionActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
}

private void showFileChooser() {
    IntentFilter intentFilter = new IntentFilter(ACTION_FILE_CHOOSER_FINISHED);
    context.registerReceiver(this, intentFilter);

    Intent intent = new Intent(context, FileChooserActivity.class);
    intent.putExtra(EXTRA_TITLE, title);
    intent.putExtra(EXTRA_ACCEPT_TYPES, acceptTypes);
    intent.putExtra(EXTRA_SHOW_IMAGE_OPTION, imageAcceptable && hasCameraPermission());
    intent.putExtra(EXTRA_SHOW_VIDEO_OPTION, videoAcceptable && hasCameraPermission());
    intent.putExtra(EXTRA_ALLOW_MULTIPLE_FILES, allowMultipleFiles);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

@Override
public void onReceive(Context context, Intent intent) {
    if (intent.getAction().equals(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED)) {
        context.unregisterReceiver(this);
        showFileChooser();
    } else if (intent.getAction().equals(ACTION_FILE_CHOOSER_FINISHED)) {
        String[] uriStrings = intent.getStringArrayExtra(EXTRA_FILE_URIS);
        Uri[] result = null;

        if (uriStrings != null) {
            int uriStringCount = uriStrings.length;
            result = new Uri[uriStringCount];

            for (int i = 0; i < uriStringCount; i++) {
                result[i] = Uri.parse(uriStrings[i]);
            }
        }

        filePathCallback.onReceiveValue(result);
        context.unregisterReceiver(this);
        filePathCallback = null;
    }
  }
 }

1.应该有一个名为FlutterWebView.java的类只要复制这个重写方法并粘贴到里面。

@Override
public boolean onShowFileChooser(
        WebView webView,
        ValueCallback<Uri[]> filePathCallback,
        FileChooserParams fileChooserParams) {
  // info as of 2021-03-08:
  // don't use fileChooserParams.getTitle() as it is (always? on                Mi 9T Pro Android 10 at least) null
  // don't use fileChooserParams.isCaptureEnabled() as it is (always? on Mi 9T Pro Android 10 at least) false, even when the file upload allows images or any file
  final Context context = webView.getContext();
  final boolean allowMultipleFiles = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
          && fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;
  final String[] acceptTypes = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
          ? fileChooserParams.getAcceptTypes() : new String[0];
  new FileChooserLauncher(context, allowMultipleFiles, filePathCallback, acceptTypes)
          .start();
  return true;
} }

1.创建一个类RequestCameraPermissionActivity.java并将此代码粘贴到那里
import android.清单; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable;导入androidx.core.app.ActivityCompat;

public class RequestCameraPermissionActivity extends Activity {

    private static final int CAMERA_PERMISSION_REQUEST_CODE = 12321;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityCompat.requestPermissions(
                this, new String[] {Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
    }

    @Override
    public void onRequestPermissionsResult(
            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
            sendBroadcast(new Intent(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED));
            finish();
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}
wf82jlnq

wf82jlnq3#

这是一个添加onShowFileChooser的PR
https://github.com/flutter/plugins/pull/3225
也许下一个版本将在Android上支持此功能。

efzxgjgh

efzxgjgh4#

在Android上有一种从webview_flutter 4.0.2使用file picker的方法,iOS不需要任何特殊更改。在小部件的initState中,添加以下代码

if (Platform.isAndroid) {
      final androidController = controller.platform as AndroidWebViewController;
      await androidController.setOnShowFileSelector(_androidFilePicker);
    }

_androidFilePicker包含file_picker来选择文件。使用此代码,如果有人按下HTML页面上的上传按钮,则文件拾取器将在Android上打开。如果您需要更多详细信息,可以查看此blog post

相关问题