UVCAndroid,安卓UVC相机通用开发库(支持多预览和多摄像头)
文章目录
- 简介
- 主要功能
- 如何使用
- 1. 添加依赖到本地工程
- 2. 获取权限
- 3. 初始化UVC业务类,设置UVC摄像头状态回调,设置TextureView或者SurfaceView的Surface监听回调
- 4. 释放UVC业务类(包含取消UVC摄像头状态回调,停止Camera预览,关闭Camera等操作)
- 5. 图片抓拍
- 6. 录制视频
- 7. 改变摄像机预览参数(包括帧格式、宽度、高度、FPS)
- 8. 调整对比度、亮度、色调、饱和度、白平衡等等一些相机控制参数
- 9.旋转摄像头90度、180度、270度,设置摄像头预览镜像
- 10.设置多个预览
- 11.设置多个摄像头(USB2.0受带宽所限,有可能无法同时连接多个摄像头)
- 其他API
- 下载演示APK
- 参考
简介
UVCAndroid是一款用于安卓UVC相机的通用开发库。
GitHub源码地址:https://github.com/shiyinghan/UVCAndroid
主要功能
主要功能包括:
(1) 支持USB Camera设备检测,画面实时预览;
(2) 支持抓拍jpg格式图片,可设置图片压缩质量;
(3) 支持录制mp4格式视频,可屏蔽音频,可设置视频和音频的录制参数;
(4) 支持获取camera支持的分辨率,和分辨率切换;
(5) 支持预览自动识别各种相机的分辨率;
(6) 支持旋转摄像头90度、180度、270度;
(7) 支持调整对比度、亮度、色调、饱和度、白平衡等等一些相机控制参数;
(8) 支持多预览和多摄像头;
(9) 支持Android5.0+;
如何使用
1. 添加依赖到本地工程
第一步 添加mavenCentral仓库到工程gradle文件
Step 1. Add the mavenCentral repository to your build file
Add it in your root build.gradle at the end of repositories:
allprojects {repositories {...mavenCentral()}
}
第二步 添加依赖到app Module的gradle文件
dependencies {implementation 'com.herohan:UVCAndroid:1.0.4'
}
2. 获取权限
Request permissions
List<String> needPermissions = new ArrayList<>();needPermissions.add(Manifest.permission.CAMERA);needPermissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);//拍照和录制视频时需要该权限//needPermissions.add(Manifest.permission.MANAGE_EXTERNAL_STORAGE); //Android 11 使用该权限替代 WRITE_EXTERNAL_STORAGEneedPermissions.add(Manifest.permission.RECORD_AUDIO);//录制视频时需要音频时需要该权限//这里使用XXPermissions开源框架获取权限,你也可以使用系统原生的,或者其他开源框架获取权限XXPermissions.with(this).permission(needPermissions).request((permissions, all) -> {if(!all){return;}//摄像头业务操作});
<application...android:requestLegacyExternalStorage="true">
3. 初始化UVC业务类,设置UVC摄像头状态回调,设置TextureView或者SurfaceView的Surface监听回调
Initialize CameraHelper,set UVC Camera state callback
private ICameraHelper mCameraHelper;private AspectRatioSurfaceView mCameraViewMain;private ICameraHelper.StateCallback mStateListener;//UVC摄像头状态回调mStateListener = new ICameraHelper.StateCallback() {//插入UVC设备@Overridepublic void onAttach(UsbDevice device) {//设置为当前设备(如果没有权限,会显示授权对话框)mCameraHelper.selectDevice(device);}//打开UVC设备成功(也就是已经获取到UVC设备的权限)@Overridepublic void onDeviceOpen(UsbDevice device, boolean isFirstOpen) {//打开UVC摄像头mCameraHelper.openCamera();}//打开摄像头成功@Overridepublic void onCameraOpen(UsbDevice device) {//开始预览mCameraHelper.startPreview();//获取预览使用的Size(包括帧格式、宽度、高度、FPS)Size size = mCameraHelper.getPreviewSize();if (size != null) {int width = size.width;int height = size.height;//需要自适应摄像头分辨率的话,设置新的宽高比mCameraViewMain.setAspectRatio(width, height);}//添加预览SurfacemCameraHelper.addSurface(mCameraViewMain.getHolder().getSurface(), false);}//关闭摄像头成功@Overridepublic void onCameraClose(UsbDevice device) {if (mCameraHelper != null) {//移除预览SurfacemCameraHelper.removeSurface(mCameraViewMain.getHolder().getSurface());}}//关闭UVC设备成功@Overridepublic void onDeviceClose(UsbDevice device) {}//断开UVC设备@Overridepublic void onDetach(UsbDevice device) {}//用户没有授予访问UVC设备的权限@Overridepublic void onCancel(UsbDevice device) {}};//设置SurfaceView的Surface监听回调mCameraViewMain.getHolder().addCallback(new SurfaceHolder.Callback() {//创建了新的Surface@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {if (mCameraHelper != null) {//添加预览SurfacemCameraHelper.addSurface(holder.getSurface(), false);}}//Surface发生了改变@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}//销毁了原来的Surface@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {if (mCameraHelper != null) {//移除预览SurfacemCameraHelper.removeSurface(holder.getSurface());}}});mCameraHelper = new CameraHelper();//设置UVC摄像头状态回调mCameraHelper.setStateCallback(mStateListener);
4. 释放UVC业务类(包含取消UVC摄像头状态回调,停止Camera预览,关闭Camera等操作)
Release CameraHelper(including canceling UVC Camera state callback, stopping Camera preview, etc.)
mCameraHelper.release();
5. 图片抓拍
Image Capture
//设置视图片抓拍全局参数(非必须,可以不设置,使用默认值)mCameraHelper.setImageCaptureConfig(mCameraHelper.getImageCaptureConfig().setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY));
// mCameraHelper.setImageCaptureConfig(
// mCameraHelper.getImageCaptureConfig().setJpegCompressionQuality(90));//设置需要保存图片文件File file = FileUtils.getCaptureFile(this, Environment.DIRECTORY_DCIM, ".jpg");ImageCapture.OutputFileOptions options =new ImageCapture.OutputFileOptions.Builder(file).build();// ContentValues contentValues = new ContentValues();
// contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "NEW_IMAGE");
// contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
//
// ImageCapture.OutputFileOptions options = new ImageCapture.OutputFileOptions.Builder(
// getContentResolver(),
// MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
// contentValues).build();// ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// ImageCapture.OutputFileOptions options = new ImageCapture.OutputFileOptions.Builder(outputStream).build();//进行图片抓拍mCameraHelper.takePicture(options, new ImageCapture.OnImageCaptureCallback() {//图片抓拍成功@Overridepublic void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {Toast.makeText(TakePictureActivity.this,"save \"" + UriHelper.getPath(TakePictureActivity.this, outputFileResults.getSavedUri()) + "\"",Toast.LENGTH_SHORT).show();}//图片抓拍出现错误@Overridepublic void onError(int imageCaptureError, @NonNull String message, @Nullable Throwable cause) {Toast.makeText(TakePictureActivity.this, message, Toast.LENGTH_SHORT).show();}});
6. 录制视频
Video Capture
//设置视频录制全局参数(非必须,可以不设置,使用默认值)mCameraHelper.setVideoCaptureConfig(mCameraHelper.getVideoCaptureConfig().setAudioCaptureEnable(true) // true:有音频;false:没有音频(默认为true).setBitRate((int) (1024 * 1024 * 25 * 0.25)).setVideoFrameRate(25).setIFrameInterval(1));//设置需要保存视频文件File file = FileUtils.getCaptureFile(this, Environment.DIRECTORY_MOVIES, ".mp4");VideoCapture.OutputFileOptions options =new VideoCapture.OutputFileOptions.Builder(file).build();// ContentValues contentValues = new ContentValues();
// contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "NEW_VIDEO");
// contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4");
//
// VideoCapture.OutputFileOptions options = new VideoCapture.OutputFileOptions.Builder(
// getContentResolver(),
// MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
// contentValues).build();//开始录制mCameraHelper.startRecording(options, new VideoCapture.OnVideoCaptureCallback() {@Overridepublic void onStart() {}//视频录制成功@Overridepublic void onVideoSaved(@NonNull VideoCapture.OutputFileResults outputFileResults) {Toast.makeText(RecordVideoActivity.this,"save \"" + UriHelper.getPath(RecordVideoActivity.this, outputFileResults.getSavedUri()) + "\"",Toast.LENGTH_SHORT).show();}//视频录制出现错误@Overridepublic void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {Toast.makeText(RecordVideoActivity.this, message, Toast.LENGTH_LONG).show();}});
7. 改变摄像机预览参数(包括帧格式、宽度、高度、FPS)
Set camera preview parameters (including frame format, width, height, FPS)
//停止相机预览mCameraHelper.stopPreview();//设置摄像机预览参数mCameraHelper.setPreviewSize(size);//开始相机预览mCameraHelper.startPreview();//需要自适应摄像头分辨率的话,设置新的宽高比mCameraViewMain.setAspectRatio(mPreviewWidth, mPreviewHeight);
8. 调整对比度、亮度、色调、饱和度、白平衡等等一些相机控制参数
Adjust contrast, brightness, hue, saturation, white balance, and other camera controls
//获取UVCControl对象,通过该对象调整相机控制参数
UVCControl control = mCameraHelper.getUVCControl();//根据监听器设置各种相机控制参数
private void setAllControlChangeListener(UVCControl controls) {// BrightnessisbBrightness.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setBrightness(seekParams.progress));// ContrastisbContrast.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setContrast(seekParams.progress));// Contrast AutocbContrastAuto.setOnCheckedChangeListener((buttonView, isChecked) -> {controls.setContrastAuto(isChecked);});// HueisbHue.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setHue(seekParams.progress));// Hue AutocbHueAuto.setOnCheckedChangeListener((buttonView, isChecked) -> {controls.setHueAuto(isChecked);});// SaturationisbSaturation.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setSaturation(seekParams.progress));// SharpnessisbSharpness.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setSharpness(seekParams.progress));// GammaisbGamma.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setGamma(seekParams.progress));// White BalanceisbWhiteBalance.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setWhiteBalance(seekParams.progress));// White Balance AutocbWhiteBalanceAuto.setOnCheckedChangeListener((buttonView, isChecked) -> {controls.setWhiteBalanceAuto(isChecked);});// Backlight CompensationisbBacklightComp.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setBacklightComp(seekParams.progress));// GainisbGain.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setGain(seekParams.progress));// Exposure TimeisbExposureTime.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setExposureTimeAbsolute(seekParams.progress));// Exposure Time AutocbExposureTimeAuto.setOnCheckedChangeListener((buttonView, isChecked) -> {controls.setExposureTimeAuto(isChecked);});// IrisisbIris.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setIrisAbsolute(seekParams.progress));// FocusisbFocus.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setFocusAbsolute(seekParams.progress));// Focus AutocbFocusAuto.setOnCheckedChangeListener((buttonView, isChecked) -> {controls.setFocusAuto(isChecked);});// ZoomisbZoom.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setZoomAbsolute(seekParams.progress));// PanisbPan.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setPanAbsolute(seekParams.progress));// TiltisbTilt.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setTiltAbsolute(seekParams.progress));// RollisbRoll.setOnSeekChangeListener((MyOnSeekChangeListener) seekParams -> controls.setRollAbsolute(seekParams.progress));// Power Line FrequencyrgPowerLineFrequency.setOnCheckedChangeListener((group, checkedId) -> {int value = 0;if (checkedId == R.id.rbPowerLineFrequencyDisable) {value = 0;} else if (checkedId == R.id.rbPowerLineFrequency50Hz) {value = 1;} else if (checkedId == R.id.rbPowerLineFrequency60Hz) {value = 2;} else if (checkedId == R.id.rbPowerLineFrequencyAuto) {value = 3;}controls.setPowerlineFrequency(value);});}// 重置所有相机控制参数为初试值
private void resetAllControlParams(UVCControl control) {// Brightnesscontrol.resetBrightness();// Contrastcontrol.resetContrast();// Contrast Autocontrol.resetContrastAuto();// Huecontrol.resetHue();// Hue Autocontrol.resetHueAuto();// Saturationcontrol.resetSaturation();// Sharpnesscontrol.resetSharpness();// Gammacontrol.resetGamma();// White Balancecontrol.resetWhiteBalance();// White Balance Autocontrol.resetWhiteBalanceAuto();// Backlight Compensationcontrol.resetBacklightComp();// Gaincontrol.resetGain();// Exposure Timecontrol.resetExposureTimeAbsolute();// Auto-Exposure Modecontrol.resetAutoExposureMode();// Iriscontrol.resetIrisAbsolute();// Focuscontrol.resetFocusAbsolute();// Focus Autocontrol.resetFocusAuto();// Zoomcontrol.resetZoomAbsolute();// Pancontrol.resetPanAbsolute();// Tiltcontrol.resetTiltAbsolute();// Rollcontrol.resetRollAbsolute();// Power Line Frequencycontrol.resetPowerlineFrequency();}
9.旋转摄像头90度、180度、270度,设置摄像头预览镜像
Rotate the camera 90 degrees, 180 degrees, and 270 degrees , set the camera preview mirror
//旋转摄像头private void rotateBy(int angle) {mPreviewRotation += angle;mPreviewRotation %= 360;if (mPreviewRotation < 0) {mPreviewRotation += 360;}if (mCameraHelper != null) {mCameraHelper.setPreviewConfig(mCameraHelper.getPreviewConfig().setRotation(mPreviewRotation));}}//设置水平镜像显示private void flipHorizontally() {if (mCameraHelper != null) {mCameraHelper.setPreviewConfig(mCameraHelper.getPreviewConfig().setMirror(MirrorMode.MIRROR_HORIZONTAL));}}//设置垂直镜像显示private void flipVertically() {if (mCameraHelper != null) {mCameraHelper.setPreviewConfig(mCameraHelper.getPreviewConfig().setMirror(MirrorMode.MIRROR_VERTICAL));}}
10.设置多个预览
Set multiple previews
mCameraHelper.addSurface(svCameraViewMain.getHolder().getSurface(), false);
mCameraHelper.addSurface(svCameraViewSecond.getHolder().getSurface(), false);
mCameraHelper.addSurface(svCameraViewThird.getHolder().getSurface(), false);
mCameraHelper.addSurface(svCameraViewFourth.getHolder().getSurface(), false);
11.设置多个摄像头(USB2.0受带宽所限,有可能无法同时连接多个摄像头)
Setting multiple Cameras
private ICameraHelper mCameraHelperLeft;private ICameraHelper mCameraHelperRight;private AspectRatioSurfaceView svCameraViewLeft;private AspectRatioSurfaceView svCameraViewRight;private UsbDevice mUsbDeviceLeft;private UsbDevice mUsbDeviceRight;private final ICameraHelper.StateCallback mStateListenerLeft = new ICameraHelper.StateCallback() {@Overridepublic void onAttach(UsbDevice device) {synchronized (mSync) {if (mUsbDeviceLeft == null && !device.equals(mUsbDeviceRight)) {selectDeviceLeft(device);}}}@Overridepublic void onDeviceOpen(UsbDevice device, boolean isFirstOpen) {if (device.equals(mUsbDeviceLeft)) {UVCParam param = new UVCParam();param.setQuirks(UVCCamera.UVC_QUIRK_FIX_BANDWIDTH);mCameraHelperLeft.openCamera(param);}}@Overridepublic void onCameraOpen(UsbDevice device) {if (device.equals(mUsbDeviceLeft)) {mCameraHelperLeft.startPreview();Size size = mCameraHelperLeft.getPreviewSize();if (size != null) {int width = size.width;int height = size.height;//auto aspect ratiosvCameraViewLeft.setAspectRatio(width, height);}mCameraHelperLeft.addSurface(svCameraViewLeft.getHolder().getSurface(), false);mIsCameraLeftConnected = true;}}@Overridepublic void onCameraClose(UsbDevice device) {if (device.equals(mUsbDeviceLeft)) {if (mCameraHelperLeft != null) {mCameraHelperLeft.removeSurface(svCameraViewLeft.getHolder().getSurface());}mIsCameraLeftConnected = false;}}@Overridepublic void onDeviceClose(UsbDevice device) {}@Overridepublic void onDetach(UsbDevice device) {if (device.equals(mUsbDeviceLeft)) {mUsbDeviceLeft = null;}}@Overridepublic void onCancel(UsbDevice device) {if (device.equals(mUsbDeviceLeft)) {mUsbDeviceLeft = null;}}};private final ICameraHelper.StateCallback mStateListenerRight = new ICameraHelper.StateCallback() {@Overridepublic void onAttach(UsbDevice device) {synchronized (mSync) {if (mUsbDeviceRight == null && !device.equals(mUsbDeviceLeft)) {selectDeviceRight(device);}}}@Overridepublic void onDeviceOpen(UsbDevice device, boolean isFirstOpen) {if (device.equals(mUsbDeviceRight)) {UVCParam param = new UVCParam();param.setQuirks(UVCCamera.UVC_QUIRK_FIX_BANDWIDTH);mCameraHelperRight.openCamera(param);}}@Overridepublic void onCameraOpen(UsbDevice device) {if (device.equals(mUsbDeviceRight)) {mCameraHelperRight.startPreview();Size size = mCameraHelperRight.getPreviewSize();if (size != null) {int width = size.width;int height = size.height;//auto aspect ratiosvCameraViewRight.setAspectRatio(width, height);}mCameraHelperRight.addSurface(svCameraViewRight.getHolder().getSurface(), false);mIsCameraRightConnected = true;}}@Overridepublic void onCameraClose(UsbDevice device) {if (device.equals(mUsbDeviceRight)) {if (mCameraHelperRight != null) {mCameraHelperRight.removeSurface(svCameraViewRight.getHolder().getSurface());}mIsCameraRightConnected = false;}}@Overridepublic void onDeviceClose(UsbDevice device) {}@Overridepublic void onDetach(UsbDevice device) {if (device.equals(mUsbDeviceRight)) {mUsbDeviceRight = null;}}@Overridepublic void onCancel(UsbDevice device) {if (device.equals(mUsbDeviceRight)) {mUsbDeviceRight = null;}}};svCameraViewLeft.getHolder().addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {if (mCameraHelperLeft != null) {mCameraHelperLeft.addSurface(holder.getSurface(), false);}}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {if (mCameraHelperLeft != null) {mCameraHelperLeft.removeSurface(holder.getSurface());}}});svCameraViewRight.setAspectRatio(DEFAULT_WIDTH, DEFAULT_HEIGHT);svCameraViewRight.getHolder().addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {if (mCameraHelperRight != null) {mCameraHelperRight.addSurface(holder.getSurface(), false);}}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {if (mCameraHelperRight != null) {mCameraHelperRight.removeSurface(holder.getSurface());}}});mCameraHelperLeft = new CameraHelper();mCameraHelperLeft.setStateCallback(mStateListenerLeft);mCameraHelperRight = new CameraHelper();mCameraHelperRight.setStateCallback(mStateListenerRight);
其他API
方法 | 说明 |
---|---|
getDeviceList() | 获取当前检测到的所有UVC设备 |
getSupportedFormatList() | 获取当前摄像头支持的Format列表 |
getSupportedSizeList() | 获取当前摄像头支持的Size列表 |
getPreviewSize() | 获取当前摄像头正在使用的预览Size |
setButtonCallback() | 设置按钮事件回调 |
setFrameCallback() | 设置实时预览图像数据回调(请在StateCallback的onDeviceOpen或者onCameraOpen回调函数里面调用,使用方法可以参考demo里面的SetFrameCallbackActivity),支持格式 UVCCamera.PIXEL_FORMAT_YUV;PIXEL_FORMAT_NV12;PIXEL_FORMAT_NV21;PIXEL_FORMAT_RGB565;PIXEL_FORMAT_RGBX等格式 |
openCamera(Size size) | 用指定格式打开当前摄像头 |
closeCamera() | 关闭当前摄像头 |
isRecording() | 是否正在录像 |
isCameraOpened() | 是否已经打开当前摄像头 |
下载演示APK
Download demo APK
app-release.apk
demo-release.apk
参考
saki4510t/UVCCamera
UVCAndroid,安卓UVC相机通用开发库(支持多预览和多摄像头)相关推荐
- 从零开始安卓端相机功能开发(一)了解用什么去开发以及流程
目前已有章节大家可以去学习也可以参考一下 1.从零开始安卓端相机功能开发(一)了解用什么去开发以及流程 2.从零开始安卓端相机功能开发(二)让我们来开发一个相机 3.从零开始开发Android相机ap ...
- 从零开始安卓端相机功能开发(二)让我们来开发一个相机
目前已有章节大家可以去学习也可以参考一下 1.从零开始安卓端相机功能开发(一)了解用什么去开发以及流程 2.从零开始安卓端相机功能开发(二)让我们来开发一个相机 3.从零开始开发Android相机ap ...
- Android相机支持的预览格式详解
当我们想从相机的预览中拿到原始的图像用于处理,我们就要先设置相机的参数,让它输出我们期望的格式.本篇文档旨在解释清楚相机支持的预览格式,相机默认预览格式. 随便找了一台Android手机,通过log输 ...
- Android 音视频开发(三) -- Camera2 实现预览、拍照功能
音视频 系列文章 Android 音视频开发(一) – 使用AudioRecord 录制PCM(录音):AudioTrack播放音频 Android 音视频开发(二) – Camera1 实现预览.拍 ...
- Android 音视频开发(二) -- Camera1 实现预览、拍照功能
音视频 系列文章 Android 音视频开发(一) – 使用AudioRecord 录制PCM(录音):AudioTrack播放音频 Android 音视频开发(二) – Camera1 实现预览.拍 ...
- html5 图片上传,支持图片预览、压缩、及进度显示,兼容IE6+及标准浏览器
原文:html5 图片上传,支持图片预览.压缩.及进度显示,兼容IE6+及标准浏览器 以前写过上传组件,见 打造 html5 文件上传组件,实现进度显示及拖拽上传,兼容IE6+及其它标准浏览器,对付一 ...
- (0094)iOS开发之本地文件预览的三种方法(2)
(0090)iOS开发之本地文件预览的三种方法(1) (0095)iOS开发之本地文件预览的三种方法(3) QuickLook预览文件 quickLook预览文件也是系统提供的预览方法,具体使用如下 ...
- (0095)iOS开发之本地文件预览的三种方法(3)
(0090)iOS开发之本地文件预览的三种方法(1) (0094)iOS开发之本地文件预览的三种方法(2) 用功能强大的Webview来实现文件预览功能 我导入的 ios.pdf 前两种可以打开,但是 ...
- 模仿微信朋友圈 图片浏览 js javascript 支持图片预览,滑动切换,双指缩放,图片缓存
模仿微信朋友圈 图片浏览 js javascript 支持图片预览,滑动切换,双指缩放,图片缓存 2017年08月10日 12:11:38 阅读数:2311 previewImage-mobile 仿 ...
最新文章
- 线程的挂起是错误的概念实际是线程的阻塞,挂起只针对进程,将进程挂起会将进程从内存空间交换到磁盘空间的过程
- 第4章javascript变量、作用域和内存回收
- 最强代码生成器平台,杀疯了!
- 2017.10.8 软件工程----总体设计
- 64位LINUX下hadoop2.2.0重新编译及安装步骤
- linux qos 软件,linux下QOS:应用篇 - 博客 - 伯乐在线
- 为防盗装自动门 不想也会影响生活
- x=min(x, y)
- [saiku] JCR在saiku中的运用原理
- 声音模仿_澳洲这种鸟堪称“超级声音模仿秀”,比八哥还牛,却正遭山火毁灭...
- 查找功能_苹果查找功能怎么查找另一台设备?很简单,只需这样操作
- pycharm ubuntu 安装_pycharm使用远程python解释器
- python调用按键精灵插件_[良心教程]分享最新最实用的按键精灵封装函数
- blazeds_Spring BlazeDS集成:它是什么,它会发生什么变化?
- iPad入手必备手册——iPad Mini 使用手册(适用于iOS 6软件 )
- 记录一个小程序 input输入框格式手机号方法
- 使用NOKIA MMS LIBRARY发送中国移动彩信
- MFC之学习扇形绘制与绘制阴阳鱼图
- 并发控制五(封锁的粒度)
- 计算机按键 shift的作用,电脑shift键的十一个妙用
热门文章
- Java查找字符串最后一次出现的位置
- 色彩运用:10个最新的黑色风格网站作品
- 再搞CRUD,就真的变成废物獠!
- lodash中get方法
- 目标检测实战必会!4种基于YOLO目标检测(Python和C++两种版本实现)
- Grasp Pose Detection with Affordance-based Task Constraint Learning in Single-view Point Clouds
- struts开发包下载地址
- 【CSDN】每日一练。
- 【Python Onramp】 0. 卷首语:项目导向,或Learn by doing
- 不需权限获得Android设备唯一标识序列号