我有一个带有webview
的Flutter Web应用程序,可以从服务器加载我的PHP项目。在我的PHP项目中,我有一个注册表,要求用户使用相机捕获照片,然后将其上传到MySQL DB。问题是,当我单击上传文件按钮以使用相机时,它什么也没做。但是在浏览器中,文件选择器正在运行,但是在webview
的Android上,它什么也没做。
我试过了,效果不好。
<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:
答案 0 :(得分:0)
我认为这是使用“ webview_flutter”的替代解决方案:https://github.com/flutter/flutter/issues/27924#issuecomment-647197754
必须编辑“ FlutterWebView.java”和“ AndroidManifest”。
答案 1 :(得分:0)
@Bruno 的链接答案对我不起作用。
所以我找到了很多解决方案并将它们一起使用,最后它对我有用。
webview_flutter: ^2.0.8
(此解决方案也适用于较低版本)
先决条件 :- 在实施此解决方案相机和存储权限之前应实施权限处理
转到您的 Flutter 的外部库
导航到 webview_flutter 插件项目文件夹
导航到安卓文件夹
转到 src>main
打开清单文件,它应该是空的,只有包名,然后复制粘贴
<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>
如果主文件夹下没有res目录,则创建该目录
创建 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>
创建xml文件夹,然后在里面创建provider_paths.xml,复制粘贴
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="safetyapp_images" path="images" />
</paths>
转到 main>java>io>flutter>plugins>webviewflutter
创建名为 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";
}
创建 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);
}
}
}
创建这个类 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;
}
}
}
应该有一个名为 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;
} }
创建一个类 RequestCameraPermissionActivity.java
并将此代码粘贴到那里
导入android.Manifest; 导入 android.app.Activity; 导入 android.content.Intent; 导入 android.os.Bundle; 导入 androidx.annotation.NonNull; 导入 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);
}
}
}
答案 2 :(得分:0)