app混合开发,选择相册和相机功能
原生:安卓 , IOS
前端:vue
目标:前端 input type=file 实现调用原生相机和图片,选择并返回前台照片
前提:安卓调用和访问权限一添加
原生:
dWebView.setWebChromeClient(new WebChromeClient(){ @Override public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { mFileCallback = filePathCallback; MODE_OPEN_MULTIPLE = fileChooserParams.getMode(); showImageSelectDialog(); return true; } });
onShowFileChooser 获取 input的信息
安卓端弹窗开发
//自主选择相机或者相册dialog private boolean isClick = false; private void showImageSelectDialog() { isClick = false; final Dialog dialog = new Dialog(this, R.style.Dialog); View view = View.inflate(this, R.layout.dialog_custom_layout, null); dialog.setContentView(view); Window window = dialog.getWindow(); window.setGravity(Gravity.BOTTOM); window.setWindowAnimations(R.style.main_menu_animStyle); window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); dialog.show(); //打开相机按钮 dialog.findViewById(R.id.tv_take_photo).setOnClickListener(view1 -> { isClick = true; openCamera(); dialog.dismiss(); }); //打开相册按钮 dialog.findViewById(R.id.tv_take_pic).setOnClickListener(view2 -> { isClick = true; takePhoto(); dialog.dismiss(); }); //取消按钮 dialog.findViewById(R.id.tv_cancel).setOnClickListener(view3 -> dialog.dismiss()); //关闭dialog dialog.setOnDismissListener(dialog1 -> { if ( ! isClick ) { mFileCallback.onReceiveValue(null); } }); }
开发打开相机
//打开相机 private void openCamera() { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File p = Environment.getExternalStorageDirectory(); File dir = new File(FileUtil.getImageTempPath()); if ( ! dir.exists() ) { dir.mkdirs(); } String picName = System.currentTimeMillis() + ".jpg"; String appId = ConfigUtils.getFieldBuildConfig("APPLICATION_ID"); File photoFile = new File(FileUtil.getImageTempPath() + picName); if ( Build.VERSION.SDK_INT >= 24 ) { Uri uri = FileProvider.getUriForFile(context, appId + ".fileprovider", photoFile); intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); } else { intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); } CacheUtils.getInstance().setCameraPhotoPath(this, photoFile.getAbsolutePath()); startActivityForResult(intent, REQUEST_CODE_CAMERA); }
打开相册
//打开相册 private void takePhoto() { //传递读取本地相册意图 Intent intent = new Intent(Intent.ACTION_PICK); intent.setType("image/*"); if(MODE_OPEN_MULTIPLE == 1){ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } // 判断系统中是否有处理该 Intent 的 Activity if (intent.resolveActivity(getPackageManager()) != null) { startActivityForResult(Intent.createChooser(intent, "Select Picture"), REQUEST_CODE_PICTURE); } else { Toast.makeText(MainActivity.this, "未找到图片查看器", Toast.LENGTH_SHORT).show(); } }
处理回调
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { //input控件相关回调设置 if ( resultCode == RESULT_OK ) { switch (requestCode) { case REQUEST_CODE_CAMERA: String path = CacheUtils.getInstance().getCameraPhotoPath(this); File file = new File(path); if ( file.exists() ) { mFileCallback.onReceiveValue(new Uri[]{Uri.fromFile(file)}); } else { mFileCallback.onReceiveValue(null); } mFileCallback = null; break; case REQUEST_CODE_PICTURE: if(data != null){ Uri uri = data.getData(); List<Uri> uriList = new ArrayList<>(); if (uri != null) { //没设置Intent.EXTRA_ALLOW_MULTIPLE,会回调这个 uriList.add(uri); } else if (data.getClipData() != null) { //设置Intent.EXTRA_ALLOW_MULTIPLE,会回调这个 ClipData clipData = data.getClipData(); int count = clipData.getItemCount(); if(count > 0){ for (int i=0; i<count; i++){ Uri imageUri =clipData.getItemAt(i).getUri(); uriList.add(imageUri); } }else { Toast.makeText(context , "获取数据为空", Toast.LENGTH_LONG).show(); } } else { Toast.makeText(context , "获取数据为空", Toast.LENGTH_LONG).show(); } if(CollectionUtil.isNotEmpty(uriList)){ Uri[] uris = uriList.toArray(new Uri[uriList.size()]); mFileCallback.onReceiveValue(uris); } } else { Toast.makeText(context , "获取数据为空", Toast.LENGTH_LONG).show(); } mFileCallback = null; break; default: break; } } if ( mFileCallback != null ) { mFileCallback.onReceiveValue(null); mFileCallback = null; } super.onActivityResult(requestCode, resultCode, data); }
弹窗画面
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/white" android:orientation="vertical"> <TextView android:id="@+id/tv_take_photo" android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:text="拍摄" android:textColor="@android:color/background_dark" android:textSize="16sp" /> <View android:layout_width="match_parent" android:layout_height="0.5dp" android:background="#1a000000" /> <TextView android:id="@+id/tv_take_pic" android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:text="从手机相册选择" android:textColor="@android:color/background_dark" android:textSize="16sp" /> <View android:layout_width="match_parent" android:layout_height="6dp" android:background="#08000000" /> <TextView android:id="@+id/tv_cancel" android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:text="取消" android:textColor="@android:color/background_dark" android:textSize="16sp" /> </LinearLayout>
动画主题
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="Dialog" parent="android:Theme.Dialog"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowNoTitle">true</item> <item name="android:windowIsFloating">true</item> </style> <style name="main_menu_animStyle"> <item name="android:windowEnterAnimation">@anim/dialog_in_anim</item> <item name="android:windowExitAnimation">@anim/dialog_out_anim</item> </style> </resources>
动画
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="500" android:fromXDelta="0" android:fromYDelta="1000" android:toXDelta="0" android:toYDelta="0" /> </set> <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="500" android:fromXDelta="0" android:fromYDelta="0" android:toXDelta="0" android:toYDelta="1000" /> </set>
完整代码
package com.baosight.wh.app; import android.annotation.SuppressLint; import android.app.Dialog; import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.widget.Toast; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.FileProvider; import cn.hutool.core.collection.CollectionUtil; import com.baosight.wh.app.bridge.appInfo.BSBridge; import com.baosight.wh.app.utils.CacheUtils; import com.baosight.wh.app.utils.ConfigUtils; import com.baosight.wh.app.utils.FileUtil; import wendu.dsbridge.DWebView; import java.io.File; import java.util.ArrayList; import java.util.List; //import com.baosight.wh.app.bridge.config.BSWebView; //import com.baosight.wh.app.bridge.config.BSWebViewClient; //import com.baosight.wh.app.bridge.config.JSResolver; public class MainActivity extends AppCompatActivity { private static final String MAIN_URL = "MAIN_URL"; private DWebView dWebView; //input文件回调 private ValueCallback<Uri[]> mFileCallback; @SuppressLint("StaticFieldLeak") public static Context context; //上传文件常量 public static final int REQUEST_CODE_CAMERA = 0x4001; public static final int REQUEST_CODE_PICTURE = 0x4002; private static int MODE_OPEN_MULTIPLE = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bridge_webview); context = this.getApplicationContext(); dWebView = findViewById(R.id.mainBridgeView); dWebView.addJavascriptObject(new BSBridge(this) , null); String mainUrl = ConfigUtils.getFieldBuildConfig(MAIN_URL); dWebView.loadUrl(mainUrl); //定义拍照 dWebView.setWebChromeClient(new WebChromeClient(){ @Override public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { mFileCallback = filePathCallback; MODE_OPEN_MULTIPLE = fileChooserParams.getMode(); showImageSelectDialog(); return true; } }); } //自主选择相机或者相册dialog private boolean isClick = false; private void showImageSelectDialog() { isClick = false; final Dialog dialog = new Dialog(this, R.style.Dialog); View view = View.inflate(this, R.layout.dialog_custom_layout, null); dialog.setContentView(view); Window window = dialog.getWindow(); window.setGravity(Gravity.BOTTOM); window.setWindowAnimations(R.style.main_menu_animStyle); window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); dialog.show(); //打开相机按钮 dialog.findViewById(R.id.tv_take_photo).setOnClickListener(view1 -> { isClick = true; openCamera(); dialog.dismiss(); }); //打开相册按钮 dialog.findViewById(R.id.tv_take_pic).setOnClickListener(view2 -> { isClick = true; takePhoto(); dialog.dismiss(); }); //取消按钮 dialog.findViewById(R.id.tv_cancel).setOnClickListener(view3 -> dialog.dismiss()); //关闭dialog dialog.setOnDismissListener(dialog1 -> { if ( ! isClick ) { mFileCallback.onReceiveValue(null); } }); } //打开相机 private void openCamera() { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File p = Environment.getExternalStorageDirectory(); File dir = new File(FileUtil.getImageTempPath()); if ( ! dir.exists() ) { dir.mkdirs(); } String picName = System.currentTimeMillis() + ".jpg"; String appId = ConfigUtils.getFieldBuildConfig("APPLICATION_ID"); File photoFile = new File(FileUtil.getImageTempPath() + picName); if ( Build.VERSION.SDK_INT >= 24 ) { Uri uri = FileProvider.getUriForFile(context, appId + ".fileprovider", photoFile); intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); } else { intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); } CacheUtils.getInstance().setCameraPhotoPath(this, photoFile.getAbsolutePath()); startActivityForResult(intent, REQUEST_CODE_CAMERA); } //打开相册 private void takePhoto() { //传递读取本地相册意图 Intent intent = new Intent(Intent.ACTION_PICK); intent.setType("image/*"); if(MODE_OPEN_MULTIPLE == 1){ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } // 判断系统中是否有处理该 Intent 的 Activity if (intent.resolveActivity(getPackageManager()) != null) { startActivityForResult(Intent.createChooser(intent, "Select Picture"), REQUEST_CODE_PICTURE); } else { Toast.makeText(MainActivity.this, "未找到图片查看器", Toast.LENGTH_SHORT).show(); } } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { //input控件相关回调设置 if ( resultCode == RESULT_OK ) { switch (requestCode) { case REQUEST_CODE_CAMERA: String path = CacheUtils.getInstance().getCameraPhotoPath(this); File file = new File(path); if ( file.exists() ) { mFileCallback.onReceiveValue(new Uri[]{Uri.fromFile(file)}); } else { mFileCallback.onReceiveValue(null); } mFileCallback = null; break; case REQUEST_CODE_PICTURE: if(data != null){ Uri uri = data.getData(); List<Uri> uriList = new ArrayList<>(); if (uri != null) { //没设置Intent.EXTRA_ALLOW_MULTIPLE,会回调这个 uriList.add(uri); } else if (data.getClipData() != null) { //设置Intent.EXTRA_ALLOW_MULTIPLE,会回调这个 ClipData clipData = data.getClipData(); int count = clipData.getItemCount(); if(count > 0){ for (int i=0; i<count; i++){ Uri imageUri =clipData.getItemAt(i).getUri(); uriList.add(imageUri); } }else { Toast.makeText(context , "获取数据为空", Toast.LENGTH_LONG).show(); } } else { Toast.makeText(context , "获取数据为空", Toast.LENGTH_LONG).show(); } if(CollectionUtil.isNotEmpty(uriList)){ Uri[] uris = uriList.toArray(new Uri[uriList.size()]); mFileCallback.onReceiveValue(uris); } } else { Toast.makeText(context , "获取数据为空", Toast.LENGTH_LONG).show(); } mFileCallback = null; break; default: break; } } if ( mFileCallback != null ) { mFileCallback.onReceiveValue(null); mFileCallback = null; } super.onActivityResult(requestCode, resultCode, data); } }
前端代码
<div class="blue_btn" @click="upload"> <input type="file" ref="file" accept="image/*" @change="getPic" multiple="multiple" style='display:none' />上传文件 </div>
async getPic(e) { var that = this const files = e.target.files; console.log(files); for (let i = 0; i < files.length; i++) { that.getBase64(files[i]).then(res => { that.base64List.push(res); }) } }, upload() { this.$refs.file.click(); }, /** * file 转Base64 DataURL * @param {File} file * @returns */ getBase64(file) { return new Promise((resolve, reject) => { ///FileReader类就是专门用来读文件的 const reader = new FileReader() //开始读文件 //readAsDataURL: dataurl它的本质就是图片的二进制数据, 进行base64加密后形成的一个字符串, reader.readAsDataURL(file) // 成功和失败返回对应的信息,reader.result一个base64,可以直接使用 reader.onload = () => resolve(reader.result) // 失败返回失败的信息 reader.onerror = error => reject(error) }) },
注:如果调用相机闪退,取相册数据为空,请检查相机相册已经媒体访问的权限是否加入