Android 的 WebChromeClient 默认为文件选择器提供 Intent。此默认选择器意图提供的内容因 Android 操作系统版本而异。在 Android 6 和 7 上,当您选择图库时,有一个打开相机的选项,但在更高版本的 Android 操作系统上,没有图库,也没有可用的相机选项。
根据提供给WebChromeClient 的 OnShowFileChooser 方法的 FileChooserParams 上的 Android 文档,CreateIntent() 方法:
创建将启动文件选择器以进行文件选择的意图。 Intent 支持从设备上可用的简单文件源中选择文件。某些高级来源(例如,实时媒体捕获)可能不受支持,希望支持这些来源或更高级文件操作的应用程序应构建自己的 Intent。
因此,尽管您的应用需要其他答案中提到的权限(读取和写入外部存储以及相机权限),但如果您想提供相机作为选项,您必须自己构建它。
对于 Xamarin.Forms,您可以利用 Xamarin.Essentials.MediaPicker API 来避免直接处理 Android Intent、设置 ContentProvider 等。
这是一个可以添加到自定义 Xamarin.Forms WebViewRenderer 的解决方案(使用来自 MediaPicker docs 的代码):
[assembly: ExportRenderer(typeof(WebView), typeof(MyWebViewRenderer))]
namespace YourAppNameSpace.Droid
{
public class MyWebViewRenderer: WebViewRenderer
{
public MyWebViewRenderer(Context context) : base(context) { }
protected override FormsWebChromeClient GetFormsWebChromeClient()
{
return new CameraFormsWebChromeClient();
}
}
}
还有 CameraFormsWebChromeClient 类:
public class CameraFormsWebChromeClient : FormsWebChromeClient
{
string _photoPath;
public override bool OnShowFileChooser(Android.Webkit.WebView webView, Android.Webkit.IValueCallback filePathCallback, FileChooserParams fileChooserParams)
{
AlertDialog.Builder alertDialog = new AlertDialog.Builder(MainActivity.Instance);
alertDialog.SetTitle("Take picture or choose a file");
alertDialog.SetNeutralButton("Take picture", async (sender, alertArgs) =>
{
try
{
var photo = await MediaPicker.CapturePhotoAsync();
var uri = await LoadPhotoAsync(photo);
filePathCallback.OnReceiveValue(uri);
}
catch (System.Exception ex)
{
System.Console.WriteLine($"CapturePhotoAsync THREW: {ex.Message}");
}
});
alertDialog.SetNegativeButton("Choose picture", async (sender, alertArgs) =>
{
try
{
var photo = await MediaPicker.PickPhotoAsync();
var uri = await LoadPhotoAsync(photo);
filePathCallback.OnReceiveValue(uri);
}
catch (System.Exception ex)
{
System.Console.WriteLine($"PickPhotoAsync THREW: {ex.Message}");
}
});
alertDialog.SetPositiveButton("Cancel", (sender, alertArgs) =>
{
filePathCallback.OnReceiveValue(null);
});
Dialog dialog = alertDialog.Create();
dialog.Show();
return true;
}
async Task<Android.Net.Uri[]> LoadPhotoAsync(FileResult photo)
{
// cancelled
if (photo == null)
{
_photoPath = null;
return null;
}
// save the file into local storage
var newFile = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
using (var stream = await photo.OpenReadAsync())
using (var newStream = System.IO.File.OpenWrite(newFile))
await stream.CopyToAsync(newStream);
_photoPath = newFile;
Android.Net.Uri uri = Android.Net.Uri.FromFile(new Java.IO.File(_photoPath));
return new Android.Net.Uri[] { uri };
}
}
我只是使用原生 Android 对话框进行选择或拍照选择。当然,您可以创建自己的 UI。主要的是您必须从 OnShowFileChooser 方法返回“true”,让 WebChromeClient 知道您将调用 filePathCallback 并提供结果。如果你返回false,你会得到一个本地异常,因为 WebChromeClient 被告知这个方法不会提供结果,所以它提供了它自己的空结果,我们得到一个“重复的 showFileChooser 结果”错误。您还需要保存拍摄的图片,然后将 Android.Net.Uri[] 提供给 filePathCallback.OnReceiveValue 方法。并且如果OnShowFileChooser返回true必须调用这个回调,所以如果用户取消,需要调用filePathCallback.OnReceiveValue(null);