老样子,先上效果图:
这个是在浏览器中使用的效果:
这个两个是在华为畅玩X7中测试的效果,由于录制的视频不知道为什么是横屏,导致导出来的gif图也是横屏,同学们就将就看看把:
在使用混合式开发app的时候,有一些需要调用手机硬件的功能往往做起来比较繁琐,比如拍照上传图片的功能,而且这个是属于比较比较常见的需求,做到这样的功能时,同学们开发这个功能的过程可能是,在浏览器中写好调用插件的代码,然后打包编译安装测试,测试发现问题,好,修改代码重新打包编译安装测试,这个过程非常繁琐,有的业务需求必须要有图片的时候,那就更麻烦,即使这个调用摄像头插件的代码没问题,这个业务功能每次正式测试都得在真机上测试,一系列的问题导致,像拍照上传这一类的功能开发起来得特别小心,一点错误的代码,需要花费非常多的时间才能找到问题。
基于上面描述的问题,我写了一个图片选择指令,在浏览器下开发的时候,调用的是浏览器文件选择的功能(就是触发input type=file元素的点击事件),当真机运行的时候,调用插件获取图片(cordova-plugin-camera),最后把得到的图片返回给开发者处理,我这个指令获取完图片数据之后是自动把图片加载到img节点中,就是一个预览的功能:
这个获取的数据是双向绑定的,如果调用该指令的作用域中声明了img-src属性,图片选择指令会把图片数据自动绑定到这个声明的变量中。
本次总结环境:angularJs、jquery、onsenui2
指令的html模板代码:
<labelstyle=" width: 64px; height: 64px; display: inline-flex; align-items: center; justify-content: center; border: solid 1px #ddd; border-radius: 6px; position: relative;"ng-click="selectPicture()"> <ons-list-itemstyle=" display: flex; padding: 0px; align-items: center; justify-content: center; height: calc(100% + 2px); position: relative; top: -1px;"class="list-item-center"ripple> <ons-iconicon="ion-ios-cloud-upload"style="color: #469ce7;font-size: 24px"ng-if="!imgUrl"></ons-icon> <imgstyle="width: 100%;height: 100%;"ng-src="{{imgUrl}}"ref="img"ng-show="imgUrl"> <inputtype="file"style="position: absolute;left: 0;bottom: 0;top: 0;right: 0;width: 100%;height: 100%;opacity: 0"ref="fileInput"ng-show="!isDevice"> </ons-list-item></label>
指令声明代码:
app.directive('imagePick', function () { return { restrict: 'E', replace: true, templateUrl: 'js/directives/image-pick/image-pick.template.html', scope: { imgSrc: '=' }, controller: [ '$scope', '$element', '$attrs', '$transclude', function ($scope, $element, $attrs, $transclude) {/*将$scope.imgSrc与父作用域绑定,因为初始化的时候可能没有,所以实际上img元素的src绑定需要一个临时变量imgUrl,如果传入的imgSrc不是未定义,则将imgSrc赋给imgUrl,imgUrl改变时也将值赋给imgSrc,实现双向绑定*/ $scope.imgUrl = '';$scope.imgSrc && ($scope.imgSrc != '') && ($scope.imgUrl = $scope.imgSrc);const refs = getRefs($element);/*--------------------------用于开发调试,模拟浏览器开发模式还是真机模式--------------------------------*/ // navigator.camera = 'camera';/*------------------------------------------------------------------------------------------------*//*判断当前是浏览器开发模式还是真机模式*/ if (navigator.camera) {console.log('camera is ready, use cordova camera plugin...');$scope.isDevice = true; /*因为在真机模式下发现input type=file节点元素死活不会消失,又不能用ng-if指令(ng-if会导致无法触发change事件,不知道为什么),只能用ng-show或者ng-hide,所以真机模式下,只能通过修改这个input的type属性,摧毁这个元素节点*/ console.log(refs.fileInput);$(refs.fileInput).attr('type','');} else {console.log('browser file input is ready');$scope.isDevice = false;}/*修改图片*/ function changeImg(img) {$scope.imgUrl = img;if ($scope.imgSrc) $scope.imgSrc = $scope.imgUrl; /*因为修改图片数据之后,无论是使用ng-src动态绑定还是使用img.src=***手动修改图片都不会立即生效,每次修改图片都是上一次选择的图片,第一次选择得到的是一个错误的咩有的图片,这里通过动态绑定,然后修改之后通过$apply()方法可以暂时修复这个问题*/ $scope.$apply();}/*如果template html中存在input type=file元素(当开发模式下,才会存在),设置change事件,图片预览*/ if (refs.fileInput && !$scope.isDevice) {$(refs.fileInput).change(function () { console.log('img chang'); if (window.FileReader) { var file = ($(refs.fileInput).prop('files'))[0]; if (!/image\/\w+/.test(file.type)) { ons.notification.alert({cancelable: true, message: '请选择图片文件!', animation: 'none',}); return; } var fr = new FileReader(); fr.onloadend = function (e) { changeImg(e.target.result); fr = null; }; fr.readAsDataURL(file); } else { ons.notification.alert({cancelable: true, message: '您的设备不支持文件预览!', animation: 'none',}); } /*选择文件之后,把input的值置空,不然选择同一个文件是不会触发change事件的*/ $(refs.fileInput).val(null);});}/*如果是真机模式下,调用手机摄像头或者打开相册,选择图片文件*//*很奇怪的是,在android模式下,这个点击一次会触发两次,所以得用一个计数器,只能触发一次*/ var count = 1;$scope.selectPicture = function () {if (count>0){ count--; if (!$scope.isDevice) { console.log('current environment is not a device, refuse to use cordova camera plugin..'); } else { console.log('select picture'); ons.openActionSheet({ cancelable: true, buttons: ['摄像头拍照','从相册中选择图片','取消', ] }).then(function (index) { count++; switch (index) {case 0:CameraUtils.takeFromCamera(function (imgUri) {changeImg(imgUri)});break;case 1:CameraUtils.takeFromAlbum(function (imgUri) {changeImg(imgUri)});break;default:console.log('no action...');break; } }) }}else { console.log('count is empty, exist...');}} } ] }});
指令中使用的一些函数代码,这个包括插件调用的代码:
/** 在指令中,获取作用域范围内的所有用了ref属性标记的节点,如果ref值相同,通过 getRefs(element).别名 结果是一个数组,如果ref值只有一个,测获取的就是一个对象* element:在指令中,$element对象,也就是指令本身对象*/function getRefs(element) { var refs = ($(element).find("[ref]")); var result = {}; for (var i = 0; i < refs.length; i++) { var item = $(refs[i]); var refName = item.attr('ref'); if (!(refName in result)) result[refName] = []; result[refName].push(item[0]); } for (var key in result) { if (result[key].length == 1) result[key] = result[key][0]; } return result;}/** 封装使用cordova-plugin-camera插件代码的工具类*/varCameraUtils= { takeFromCamera: function (success) { console.log('打开摄像头..........'); var srcType = Camera.PictureSourceType.CAMERA; var options =CameraUtils.getDefaultOptions(srcType); navigator.camera.getPicture( function (imageUri) {success && success("data:image/jpeg;base64,"+imageUri); }, function (error) {console.error("Unable to obtain picture: " + error, "app"); }, options); }, takeFromAlbum: function (success) { console.log('打开相册........'); var srcType = Camera.PictureSourceType.SAVEDPHOTOALBUM; var options =CameraUtils.getDefaultOptions(srcType); navigator.camera.getPicture( function (imageUri) {success && success("data:image/jpeg;base64,"+imageUri); }, function (error) {console.error("Unable to obtain picture: " + error, "app"); }, options); }, getDefaultOptions: function (srcType) { return { // Some common settings are 20, 50, and 100 quality: 80, destinationType: Camera.DestinationType.DATA_URL, // In this app, dynamically set the picture source, Camera or photo gallery sourceType: srcType, encodingType: Camera.EncodingType.JPEG, mediaType: Camera.MediaType.PICTURE, allowEdit: true, correctOrientation: true //Corrects Android orientation quirks }; }};
调用图片选择指令的页面代码:
<ons-pageng-controller="devController"> <ons-toolbar> <divclass="left"> <ons-toolbar-buttonng-click="LnkMenu()"><ons-iconicon="ion-navicon-round"></ons-icon> </ons-toolbar-button> </div> <divclass="center"> <spanng-bind="'门店拜访'"></span> </div> </ons-toolbar> <ons-pageclass="c-p"> <image-pick></image-pick> <image-pickimg-src="ir"></image-pick> {{ir}}</ons-page></ons-page>
页面controller:
app.controller('devController', function ($scope, $rootScope, pageService) { $scope.ir = 'img/1.png';});
我觉得代码注释得已经非常详细了,这里我就不再讲解其中的原理,我这里还涵盖了其他两个知识点,一个是使用angularJs给节点元素取别名,相关说明博客地址:/mate_ge/article/details/78941679;一个是input type=file获取图片之后,怎么加载到img元素中,以及在java后台如何处理这个可以解析为二进制流的图片数据字符串:/mate_ge/article/details/78848269;