您现在的位置是:首页 > 技术教程 正文

H5获取手机相机或相册图片两种方式-Android通过webview传递多张照片给H5

admin 阅读: 2024-03-28
后台-插件-广告管理-内容页头部广告(手机)

需求目的: 手机机通过webView展示H5网页,在特殊场景下,需要使用相机拍照或者从相册获取照片,上传后台。

完整流程效果: 如下图
H5调用相机相册使用组件库方式流程

一、H5界面样例代码

使用html文件格式,文件直接打开就可以展示布局;一会在andriod webview中直接加载

DOCTYPE html> <html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Documenttitle> head> <div id="app"> <h1>alllalalallalal 默认会被覆盖h1> div> <template id="why"> <div> <h2>{{message}}h2> <h2>{{counter}}h2> <button @click="increment">+1button> <button @click="decrement">-1button> <h1 style="text-align: center;">{{ title }}h1> <div> <h2 style="text-align: center;">android选中照片H5展示h2> <input accept="image/*" capture="camera" ref="imgFile" type="file" multiple @change="previewFiles"> <div id="preview"> <img v-for="imgSrc in imageSources" :src="imgSrc" :key="imgSrc" style="max-width: 100px; max-height: 100px; margin: 10px;"> div> div> div> template> <body> <script src="vue3.2.12global.js">script> <script> Vue.createApp({ template: '#why', data: function () { return { message: "功能开发中,敬请期待!", counter: 100, pictureSelectorContent: "相机选择结果:", imageSources: [] // 存储图片的数据URL } }, // 在你的 Vue 组件中处理 Webview 传递的数据 mounted() { // 设置全局函数,用于接收 WebView 调用 // window.pictureSelectorResult = this.pictureSelectorResult; }, methods: { increment() { this.counter++; console.log("点击了+1"); }, decrement() { this.counter--; consloe.log("点击了-1"); }, startPictureSelector() { window.android.startPictureSelector(); }, previewFiles() { const files = this.$refs.imgFile.files; this.imageSources = []; for (let i = 0; i < files.length; i++) { const file = files[i]; const reader = new FileReader(); reader.onload = (e) => { this.imageSources.push(e.target.result); }; reader.readAsDataURL(file); } }, }, }).mount("#app") script> <style> h1 { font-size: 80px; font-weight: bold; margin-bottom: 20px; } h2 { font-size: 20px; font-weight: bold; color: #C8EFD4; } h3 { font-size: 10px; font-weight: bold; color: #C8EFD4; } button1 { font-size: 60px; padding: 10px 20px; background-color: #007bff; color: #fff; border: none; border-radius: 4px; cursor: pointer; margin-bottom: 20px; margin-top: 20px; text-align: center; /* 将文字水平居中显示 */ display: flex; /* 将按钮设置为flex容器 */ align-items: center; /* 将文字在垂直方向上居中显示 */ } style> body> html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133

上述代码是前端代码,使用vue3框架展示一个基础 加减demo界面(不重要的冗余),以及 一个打开文件的按钮以及展示图片
在这里插入图片描述

其中HTML5提供了元素来实现选取文件的功能,当在WebView表现为调用onShowFileChooser后,回调图片uri列表一一获取并展示

二、Android打开相机以及相册的两种方式

方式一:android 原生方式

实际效果和流程示图
在这里插入图片描述

1.android界面逻辑代码
这边使用的是kotlin语言,使用的fragment界面展示,使用binding加载了布局,以及声明了webview组件,在webview上导入html链接,使本地H5界面展示

class VisitorFragment : Fragment() { private lateinit var binding: FragmentVisitorBinding lateinit var mActivity: Activity private lateinit var mRoot: View companion object { const val TAG = "VisitorFragment" } private var mWebViewUrl: String = "file:///android_asset/vue_android_demo.html" var mAppName = MainApplication.instance.configuration.BASE_APP_LOGIN_IDENTITY var mSystemName = WebViewConstant.DEFAULT_SYSTEM_NAME private var mVisitorAndroidJs: VisitorAndroidJs = VisitorAndroidJs(this) private var mWebView: WebView? = null var mAndroidId: String = WebViewConstant.DEFAULT_DEVICE_SIGN var mApiKey: String = WebViewConstant.DEFAULT_API_KEY //回传H5时使用的对象 private var mUploadCallback: ValueCallback<Array<Uri>>? = null //拍照传递的路径uri private var mImageUri: Uri? = null /** * onCreate方法是Activity生命周期的第一个回调方法 * ,当Activity被创建时被调用。 * 在这个方法中,你可以进行一些初始化的操作,比如设置布局、绑定控件、初始化数据等。 * * @param savedInstanceState If the fragment is being re-created from * a previous saved state, this is the state. */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = FragmentVisitorBinding.inflate(layoutInflater) mRoot = binding.root //记录 val viewModel = ViewModelProvider(requireActivity())[DashboardViewModel::class.java] viewModel.setFragment(this) LogUtils.d(TAG, "onCreate") } /** * onCreateView方法是Fragment生命周期的回调方法, * 当Fragment创建并绘制其用户界面时被调用。 * 在这个方法中,你可以通过返回一个View对象来定义Fragment的用户界面。 * 它常用于加载布局文件、查找和初始化控件等操作。 * * @return */ override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { LogUtils.d(TAG, "onCreateView") initView(mRoot, layoutInflater, null) return mRoot } fun initView(parent: View?, inflater: LayoutInflater?, container: ViewGroup?) { //设置当前fragment mAndroidId = DeviceUtils.getUniqueId(mActivity) initWebView() mWebView?.loadUrl(mWebViewUrl) //弹出展示链接 showToast("${getString(R.string.current_develop_environment)}$mWebViewUrl") initData() } override fun onAttach(context: Context) { super.onAttach(context) mActivity = context as Activity } /** * @param msg 内容 */ fun showToast(msg: String?) { val activity: Activity? = activity activity?.runOnUiThread { Toast.makeText( activity, msg, Toast.LENGTH_SHORT ).show() } } private fun initData() { } override fun onResume() { super.onResume() LogUtils.d(TAG, "onResume") } @SuppressLint("SetJavaScriptEnabled") private fun initWebView() { LogUtils.d(TAG, "initWebView") mWebView = binding.mainWebView mWebView?.requestFocus() mWebView?.isHorizontalScrollBarEnabled = false mWebView?.isVerticalScrollBarEnabled = false val setting = mWebView?.settings setting?.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW; setting?.javaScriptEnabled = true //用于开启或禁用其 DOM(文档对象模型)存储功能,浏览器缓存这些数据 setting?.domStorageEnabled = true //允许访问文件,默认允许 setting?.allowFileAccess = true //设置不,会引起webView重新不急,默认NARROW_COLUMNS setting?.layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS //自动缩放 setting?.setSupportZoom(true) setting?.builtInZoomControls = true //自适应屏幕 setting?.useWideViewPort = true setting?.loadWithOverviewMode = true //支持多窗口 setting?.setSupportMultipleWindows(true) setting?.setAppCacheEnabled(true) setting?.domStorageEnabled = true //定位 setting?.setGeolocationEnabled(true) //优先使用缓存数据,在缓存数据不存在的情况下才去获取网络数据 setting?.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK setting?.savePassword = false //设置js接口 mVisitorAndroidJs.let { mWebView?.addJavascriptInterface(it, "android") } //页面不跳转浏览器 mWebView?.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { LogUtils.d(TAG, "url: $url") view.loadUrl(url) return true } override fun shouldInterceptRequest( view: WebView, request: WebResourceRequest ): WebResourceResponse? { var uri = request.url var path = uri.path LogUtils.d(TAG, "uri: $uri, path: $path") return super.shouldInterceptRequest(view, request) } } //webView官方打开文件选取方法onShowFileChooser,把网页回传文件 mWebView?.webChromeClient = object : WebChromeClient() { //API >=21(android 5.0.1)回调此方法 override fun onShowFileChooser( webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams? ): Boolean { mUploadCallback = filePathCallback //使用拍照或者打开文件 mImageUri = ChoosePhotoFile.takePhoto(mActivity) //设置false会IllegalStateException: Duplicate showFileChooser result return true } } } fun onActivityResultResponse(requestCode: Int, resultCode: Int, intent: Intent?) { LogUtils.d( TAG, "onActivityResultResponse requestCode $requestCode, resultCode:$resultCode" ) // 扫描二维码/条码回传 if (requestCode == ScanCodeUtils.REQUEST_CODE_SCAN && resultCode == Activity.RESULT_OK) { LogUtils.d(TAG, "onActivityResultResponse ${intent?.extras}") if (intent == null) { //弹出展示链接 showToast("扫描结果为空") return } //传递给js,格式是"scanCodeResult('${data.extras}')",其中单引号很重要,可能导致js script error //codedContent是组件中定义的参数名 setEvaluateJavascript("scanCodeResult('${intent.getStringExtra("codedContent")}')") } else if (requestCode == ChoosePhotoFile.REQUEST_CODE) { //拍照,界面跳回后,结果文件的使用 ChoosePhotoFile.takeActivityResult(requestCode, intent, mUploadCallback, mImageUri) } } @Override override fun onDestroy() { //防止更新dialog内存泄漏 super.onDestroy() } /** * 登录成功后跳转 */ open fun loginSuccessJump() { } /** * 给网页传值 * 传递给js,格式是"scanCodeResult('${data.extras}')" * 其中单引号很重要,可能导致js script error */ private fun setEvaluateJavascript(jspMethodAndValue: String) { LogUtils.d( TAG, "setEvaluateJavascript mWebView ${mWebView}, jspMethodAndValue $jspMethodAndValue" ) mWebView?.evaluateJavascript(jspMethodAndValue, ValueCallback<String>() { LogUtils.d(TAG, "给网页传值为: $jspMethodAndValue") }) } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231

android layout布局

<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <WebView android:id="@+id/main_web_view" android:layout_width="match_parent" android:layout_height="match_parent" /> androidx.constraintlayout.widget.ConstraintLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.获取照片的主要思路是两个方法:

  • webView官方打开文件选取方法onShowFileChooser,把网页回传文件
//webView官方打开文件选取方法onShowFileChooser,把网页回传文件 mWebView?.webChromeClient = object : WebChromeClient() { //API >=21(android 5.0.1)回调此方法 override fun onShowFileChooser( webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams? ): Boolean { mUploadCallback = filePathCallback //使用拍照或者打开文件 mImageUri = ChoosePhotoFile.takePhoto(mActivity) //设置false会IllegalStateException: Duplicate showFileChooser result return true } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 拍照或者相册选中后界面跳回后,使用ValueCallback回传
fun onActivityResultResponse(requestCode: Int, resultCode: Int, intent: Intent?) { LogUtils.d( TAG, "onActivityResultResponse requestCode $requestCode, resultCode:$resultCode" ) if (requestCode == ChoosePhotoFile.REQUEST_CODE) { //拍照或者相册选中后界面跳回后,结果文件的使用 ChoosePhotoFile.takeActivityResult(requestCode, intent, mUploadCallback, mImageUri) } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 以上onActivityResultResponse方法需要在actvity onActivityResult方法中使用
    (因为我这里是activity嵌套fragment的,如果直接在activity使用webview就不需我这太麻烦)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) LogUtils.d( TAG, "onActivityResult requestCode11 $requestCode, resultCode:$resultCode" ) //设置当前fragment val fragment = mDashboardViewModel.fragment.value Log.d(TAG, "fragment: $fragment") Log.d(TAG, "fragment.isResumed: ${fragment?.isResumed}") //界面返回时VisitorFragment还没有Resumed if (fragment is VisitorFragment) { val visitorFragment: VisitorFragment = fragment visitorFragment.onActivityResultResponse(requestCode, resultCode, data) } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

3.打开相机和相册的工具类

object ChoosePhotoFile { private const val TAG = "ChoosePhotoFile" const val REQUEST_CODE: Int = 12345 fun takePhoto(activity: Activity): Uri { //相机可以访问的公共位置才能存储,获取时需要读取文件权限 val file: String = Environment.getExternalStorageDirectory() .toString() + File.separator + Environment.DIRECTORY_PICTURES + File.separator val fileName = "Image_${SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())}.jpg" val realFile = File(file, fileName) val imageUri = Uri.fromFile(realFile) LogUtils.d(TAG, "realFile:$realFile, imageUri: $imageUri") // 拍照后获取图片需要文件权限,界面跳转直接拿文件不需要权限 //检查申请读文件权限 CheckPermissionUtils.requestPermissions( activity, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA) ) //调起相机,拍一张照片 val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri) //调起相册,取一张照片 val photoIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) //选择方式,拍照或者相册 val chooserIntent = Intent.createChooser(photoIntent, "Image Chooser") chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf<Parcelable>(captureIntent)) activity.startActivityForResult(chooserIntent, REQUEST_CODE) return imageUri } /** * H5针对从拍照或者相册中选中的图片做处理 * @param imageUri 拍照返回的数据, */ fun takeActivityResult( requestCode: Int, intent: Intent?, filePathCallback: ValueCallback<Array<Uri>>?, imageUri: Uri? ) { if (requestCode == REQUEST_CODE) { //从相册获取,返回的intent if (intent != null && intent.data != null) { var uri: Uri = intent.data as Uri LogUtils.d(TAG, "file uri: $uri") filePathCallback?.onReceiveValue(arrayOf<Uri>(uri)) } else { //从拍照中获取图片,已经返回的imageUri LogUtils.d(TAG, "take photo imageUri: $imageUri") if (imageUri != null) { filePathCallback?.onReceiveValue(arrayOf<Uri>(imageUri)) } else { filePathCallback?.onReceiveValue(null) } } } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
方式二:使用android 组件库是实现-朋友圈获取照片功能

实际效果和流程示图
在这里插入图片描述
1.获取照片的主要思路是两个方法-替换

  • webView官方打开文件选取方法onShowFileChooser,把网页回传文件
//webView官方打开文件选取方法onShowFileChooser,把网页回传文件 mWebView?.webChromeClient = object : WebChromeClient() { //API >=21(android 5.0.1)回调此方法 override fun onShowFileChooser( webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams? ): Boolean { mUploadCallback = filePathCallback //使用拍照或者打开文件 // mImageUri = ChoosePhotoFile.takePhoto(mActivity) //模拟微信朋友圈获取照片模式 LogUtils.d(TAG,"onShowFileChooser") PictureSelectorUtils.startPictureSelector(mActivity) //设置false会IllegalStateException: Duplicate showFileChooser result return true } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 拍照或者相册选中后界面跳回后,使用ValueCallback回传
fun onActivityResultResponse(requestCode: Int, resultCode: Int, intent: Intent?) { LogUtils.d( TAG, "onActivityResultResponse requestCode $requestCode, resultCode:$resultCode" ) if (requestCode == PictureSelectorUtils.REQUEST_PICTURE_SELECTOR) { LogUtils.d(TAG, "onActivityResultResponse REQUEST_PICTURE_SELECTOR") PictureSelectorUtils.takeActivityResult(requestCode, intent, mUploadCallback) } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.打开相机和相册的工具类

object PictureSelectorUtils { const val REQUEST_PICTURE_SELECTOR = 10012 const val TAG = "PictureSelectorUtils" fun startPictureSelector(activity: Activity) { LogUtils.d(TAG, "startPictureSelector") // 拍照后获取图片需要文件权限,界面跳转直接拿文件不需要权限 //检查申请读文件权限 // CheckPermissionUtils.requestPermissions( // activity, // arrayOf( // Manifest.permission.CAMERA, // Manifest.permission.READ_EXTERNAL_STORAGE, // Manifest.permission.WRITE_EXTERNAL_STORAGE // ) // ) //插件里自带了静态权限以及权限校验 PictureSelector.create(activity).openGallery(PictureMimeType.ofImage()) .imageEngine(GlideEngine) // Please refer to the Demo GlideEngine.java .isWeChatStyle(true) // 是否开启微信图片选择风格 .selectionMode(PictureConfig.MULTIPLE).forResult(REQUEST_PICTURE_SELECTOR) } fun getPictures(data: Intent): MutableList<Uri> { val selectList = PictureSelector.obtainMultipleResult(data) LogUtils.d(TAG, "getPicture selectList: $selectList") // 将照片路径转换成 Uri 列表 val imageUris: MutableList<Uri> = ArrayList() if (selectList.isEmpty()) { LogUtils.d(TAG, "getPicture selectList isEmpty") return imageUris } for (imagePath in selectList) { var path = imagePath.path LogUtils.d(TAG, "path: $path") val uri = Uri.parse(path) LogUtils.d(TAG, "uri: $uri") imageUris.add(uri) } LogUtils.d(TAG, "imageUris: ${imageUris.toString()}") return imageUris } /** * H5针对从文件钟选中的图片做处理 */ fun takeActivityResult( requestCode: Int, intent: Intent?, filePathCallback: ValueCallback<Array<Uri>>?, ) { if (requestCode == REQUEST_PICTURE_SELECTOR) { val selectList = PictureSelector.obtainMultipleResult(intent) LogUtils.d(TAG, "getPicture selectList: $selectList") // 将照片路径转换成 Uri 列表 val imageUris: MutableList<Uri> = ArrayList() if (selectList.isEmpty()) { LogUtils.d(TAG, "getPicture selectList isEmpty") filePathCallback?.onReceiveValue(null) return } for (imagePath in selectList) { var path = imagePath.path LogUtils.d(TAG, "path: $path") val uri = Uri.parse(path) LogUtils.d(TAG, "uri: $uri") imageUris.add(uri) } LogUtils.d(TAG, "imageUris: ${imageUris.toString()}") filePathCallback?.onReceiveValue(imageUris.toTypedArray()) } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

其中使用第三方组件库-实现类似朋友圈获取照片的样式,需要引入一下依赖

//照片获取类微信朋友圈 implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0'
  • 1
  • 2

拍照后获取图片需要文件权限,界面跳转直接拿文件不需要权限,且第三方插件库里自带了静态权限以及权限请求

3.我完整的代码——类似朋友圈获取界面逻辑

class VisitorFragment : Fragment() { private lateinit var binding: FragmentVisitorBinding lateinit var mActivity: Activity private lateinit var mRoot: View companion object { const val TAG = "VisitorFragment" } private var mWebViewUrl: String = "file:///android_asset/vue_android_demo.html" var mAppName = MainApplication.instance.configuration.BASE_APP_LOGIN_IDENTITY var mSystemName = WebViewConstant.DEFAULT_SYSTEM_NAME private var mVisitorAndroidJs: VisitorAndroidJs = VisitorAndroidJs(this) private var mWebView: WebView? = null var mAndroidId: String = WebViewConstant.DEFAULT_DEVICE_SIGN var mApiKey: String = WebViewConstant.DEFAULT_API_KEY //回传H5时使用的对象 private var mUploadCallback: ValueCallback<Array<Uri>>? = null //拍照传递的路径uri private var mImageUri: Uri? = null /** * onCreate方法是Activity生命周期的第一个回调方法 * ,当Activity被创建时被调用。 * 在这个方法中,你可以进行一些初始化的操作,比如设置布局、绑定控件、初始化数据等。 * * @param savedInstanceState If the fragment is being re-created from * a previous saved state, this is the state. */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = FragmentVisitorBinding.inflate(layoutInflater) mRoot = binding.root //记录 val viewModel = ViewModelProvider(requireActivity())[DashboardViewModel::class.java] viewModel.setFragment(this) LogUtils.d(TAG, "onCreate") } /** * onCreateView方法是Fragment生命周期的回调方法, * 当Fragment创建并绘制其用户界面时被调用。 * 在这个方法中,你可以通过返回一个View对象来定义Fragment的用户界面。 * 它常用于加载布局文件、查找和初始化控件等操作。 * * @return */ override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { LogUtils.d(TAG, "onCreateView") initView(mRoot, layoutInflater, null) return mRoot } fun initView(parent: View?, inflater: LayoutInflater?, container: ViewGroup?) { //设置当前fragment mAndroidId = DeviceUtils.getUniqueId(mActivity) initWebView() mWebView?.loadUrl(mWebViewUrl) //弹出展示链接 showToast("${getString(R.string.current_develop_environment)}$mWebViewUrl") initData() } override fun onAttach(context: Context) { super.onAttach(context) mActivity = context as Activity } /** * @param msg 内容 */ fun showToast(msg: String?) { val activity: Activity? = activity activity?.runOnUiThread { Toast.makeText( activity, msg, Toast.LENGTH_SHORT ).show() } } private fun initData() { } override fun onResume() { super.onResume() LogUtils.d(TAG, "onResume") } @SuppressLint("SetJavaScriptEnabled") private fun initWebView() { LogUtils.d(TAG, "initWebView") mWebView = binding.mainWebView mWebView?.requestFocus() mWebView?.isHorizontalScrollBarEnabled = false mWebView?.isVerticalScrollBarEnabled = false val setting = mWebView?.settings setting?.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW; setting?.javaScriptEnabled = true //用于开启或禁用其 DOM(文档对象模型)存储功能,浏览器缓存这些数据 setting?.domStorageEnabled = true //允许访问文件,默认允许 setting?.allowFileAccess = true //设置不,会引起webView重新不急,默认NARROW_COLUMNS setting?.layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS //自动缩放 setting?.setSupportZoom(true) setting?.builtInZoomControls = true //自适应屏幕 setting?.useWideViewPort = true setting?.loadWithOverviewMode = true //支持多窗口 setting?.setSupportMultipleWindows(true) setting?.setAppCacheEnabled(true) setting?.domStorageEnabled = true //定位 setting?.setGeolocationEnabled(true) //优先使用缓存数据,在缓存数据不存在的情况下才去获取网络数据 setting?.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK setting?.savePassword = false //设置js接口 mVisitorAndroidJs.let { mWebView?.addJavascriptInterface(it, "android") } //页面不跳转浏览器 mWebView?.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { LogUtils.d(TAG, "url: $url") view.loadUrl(url) return true } override fun shouldInterceptRequest( view: WebView, request: WebResourceRequest ): WebResourceResponse? { var uri = request.url var path = uri.path LogUtils.d(TAG, "uri: $uri, path: $path") return super.shouldInterceptRequest(view, request) } } //webView官方打开文件选取方法onShowFileChooser,把网页回传文件 mWebView?.webChromeClient = object : WebChromeClient() { //API >=21(android 5.0.1)回调此方法 override fun onShowFileChooser( webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams? ): Boolean { mUploadCallback = filePathCallback //使用拍照或者打开文件 // mImageUri = ChoosePhotoFile.takePhoto(mActivity) //模拟微信朋友圈获取照片模式 LogUtils.d(TAG,"onShowFileChooser") PictureSelectorUtils.startPictureSelector(mActivity) //设置false会IllegalStateException: Duplicate showFileChooser result return true } } } fun onActivityResultResponse(requestCode: Int, resultCode: Int, intent: Intent?) { LogUtils.d( TAG, "onActivityResultResponse requestCode $requestCode, resultCode:$resultCode" ) // 扫描二维码/条码回传 if (requestCode == ScanCodeUtils.REQUEST_CODE_SCAN && resultCode == Activity.RESULT_OK) { LogUtils.d(TAG, "onActivityResultResponse ${intent?.extras}") if (intent == null) { //弹出展示链接 showToast("扫描结果为空") return } //传递给js,格式是"scanCodeResult('${data.extras}')",其中单引号很重要,可能导致js script error //codedContent是组件中定义的参数名 setEvaluateJavascript("scanCodeResult('${intent.getStringExtra("codedContent")}')") // } else if (requestCode == ChoosePhotoFile.REQUEST_CODE) { // //拍照,界面跳回后,结果文件的使用 // ChoosePhotoFile.takeActivityResult(requestCode, intent, mUploadCallback, mImageUri) } else if (requestCode == PictureSelectorUtils.REQUEST_PICTURE_SELECTOR) { LogUtils.d(TAG, "onActivityResultResponse REQUEST_PICTURE_SELECTOR") PictureSelectorUtils.takeActivityResult(requestCode, intent, mUploadCallback) } } @Override override fun onDestroy() { //防止更新dialog内存泄漏 super.onDestroy() } /** * 登录成功后跳转 */ open fun loginSuccessJump() { } /** * 给网页传值 * 传递给js,格式是"scanCodeResult('${data.extras}')" * 其中单引号很重要,可能导致js script error */ private fun setEvaluateJavascript(jspMethodAndValue: String) { LogUtils.d( TAG, "setEvaluateJavascript mWebView ${mWebView}, jspMethodAndValue $jspMethodAndValue" ) mWebView?.evaluateJavascript(jspMethodAndValue, ValueCallback<String>() { LogUtils.d(TAG, "给网页传值为: $jspMethodAndValue") }) } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236

三、总结一下

  • H5调用公共获取图片文件方法,
  • 在android手机端,H5主要依赖Webview,
  • 这边在webview声明并重写该方法onShowFileChooser
  • 使用工具类打开相机或相册,可以两种方式安卓原生方式或者利用第三方组件库方式
  • 选中图片,返回uri列表给H5
  • H5收到uri照片列表,并且使用前端方式展示

创造价值,乐哉分享!

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索