From b0d40d64d6b73871cc63fe8dc10c307d91de884a Mon Sep 17 00:00:00 2001 From: cyqresig Date: Wed, 26 Oct 2016 15:19:06 +0800 Subject: [PATCH] finished first version --- .gitignore | 12 + .npmignore | 12 + Barcode.js | 74 ++ README.md | 8 +- android/build.gradle | 26 + android/proguard-rules.pro | 17 + android/src/main/AndroidManifest.xml | 3 + .../barcode/CaptureView.java | 1090 +++++++++++++++++ .../barcode/QRCodeResultEvent.java | 51 + .../barcode/RCTCaptureManager.java | 223 ++++ .../barcode/RCTCaptureModule.java | 167 +++ .../barcode/RCTCapturePackage.java | 51 + .../barcode/RCTLinearGradientViewManager.java | 87 ++ .../barcode/camera/AutoFocusCallback.java | 51 + .../camera/CameraConfigurationManager.java | 305 +++++ .../barcode/camera/CameraManager.java | 366 ++++++ .../barcode/camera/FlashlightManager.java | 145 +++ .../camera/PlanarYUVLuminanceSource.java | 154 +++ .../barcode/camera/PreviewCallback.java | 58 + .../decoding/CaptureActivityHandler.java | 153 +++ .../barcode/decoding/DecodeFormatManager.java | 105 ++ .../barcode/decoding/DecodeHandler.java | 148 +++ .../barcode/decoding/DecodeThread.java | 113 ++ .../barcode/decoding/DecodeUtil.java | 168 +++ .../barcode/decoding/FinishListener.java | 48 + .../barcode/decoding/InactivityTimer.java | 72 ++ .../barcode/decoding/Intents.java | 192 +++ .../barcode/view/LinearGradientView.java | 186 +++ .../barcode/view/VerticalSeekBar.java | 179 +++ .../view/ViewfinderResultPointCallback.java | 34 + .../barcode/view/ViewfinderView.java | 402 ++++++ android/src/main/res/drawable/po_seekbar.xml | 23 + android/src/main/res/drawable/seek.9.png | Bin 0 -> 309 bytes android/src/main/res/drawable/seek_bkg.9.png | Bin 0 -> 270 bytes android/src/main/res/drawable/seek_thumb.png | Bin 0 -> 1766 bytes .../main/res/drawable/seekbar_horizontal.xml | 63 + .../src/main/res/drawable/seekbar_thumb.xml | 8 + .../res/drawable/seekbar_thumb_normal.png | Bin 0 -> 570 bytes .../res/drawable/seekbar_thumb_pressed.png | Bin 0 -> 583 bytes .../src/main/res/layout/seekbar_layout.xml | 13 + android/src/main/res/raw/beep.wav | Bin 0 -> 20044 bytes android/src/main/res/values/colors.xml | 15 + android/src/main/res/values/dimens.xml | 7 + android/src/main/res/values/ids.xml | 31 + android/src/main/res/values/strings.xml | 3 + android/src/main/res/values/styles.xml | 8 + .../RCTBarCode.xcodeproj/project.pbxproj | 298 +++++ .../contents.xcworkspacedata | 7 + ios/RCTBarcode/RCTBarcode/LineView.h | 10 + ios/RCTBarcode/RCTBarcode/LineView.m | 73 ++ ios/RCTBarcode/RCTBarcode/QRCScanner.h | 53 + ios/RCTBarcode/RCTBarcode/QRCScanner.m | 352 ++++++ ios/RCTBarcode/RCTBarcode/RCTBarcode.h | 25 + ios/RCTBarcode/RCTBarcode/RCTBarcode.m | 225 ++++ ios/RCTBarcode/RCTBarcode/RCTBarcodeManager.h | 24 + ios/RCTBarcode/RCTBarcode/RCTBarcodeManager.m | 288 +++++ ios/RCTBarcode/RCTBarcode/RectView.h | 11 + ios/RCTBarcode/RCTBarcode/RectView.m | 152 +++ ios/RCTBarcode/RCTBarcode/ScannerRect.h | 13 + ios/RCTBarcode/RCTBarcode/ScannerRect.m | 112 ++ ios/RCTBarcode/RCTBarcode/UIColor+Hex.h | 15 + ios/RCTBarcode/RCTBarcode/UIColor+Hex.m | 122 ++ ios/raw/beep.wav | Bin 0 -> 20044 bytes note.md | 5 + package.json | 25 + 65 files changed, 6680 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 Barcode.js create mode 100644 android/build.gradle create mode 100644 android/proguard-rules.pro create mode 100644 android/src/main/AndroidManifest.xml create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/CaptureView.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/QRCodeResultEvent.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/RCTCaptureManager.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/RCTCaptureModule.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/RCTCapturePackage.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/RCTLinearGradientViewManager.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/camera/AutoFocusCallback.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/camera/CameraConfigurationManager.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/camera/CameraManager.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/camera/FlashlightManager.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/camera/PlanarYUVLuminanceSource.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/camera/PreviewCallback.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/decoding/CaptureActivityHandler.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeFormatManager.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeHandler.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeThread.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeUtil.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/decoding/FinishListener.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/decoding/InactivityTimer.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/decoding/Intents.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/view/LinearGradientView.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/view/VerticalSeekBar.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/view/ViewfinderResultPointCallback.java create mode 100644 android/src/main/java/com/reactnativecomponent/barcode/view/ViewfinderView.java create mode 100644 android/src/main/res/drawable/po_seekbar.xml create mode 100644 android/src/main/res/drawable/seek.9.png create mode 100644 android/src/main/res/drawable/seek_bkg.9.png create mode 100644 android/src/main/res/drawable/seek_thumb.png create mode 100644 android/src/main/res/drawable/seekbar_horizontal.xml create mode 100644 android/src/main/res/drawable/seekbar_thumb.xml create mode 100644 android/src/main/res/drawable/seekbar_thumb_normal.png create mode 100644 android/src/main/res/drawable/seekbar_thumb_pressed.png create mode 100644 android/src/main/res/layout/seekbar_layout.xml create mode 100644 android/src/main/res/raw/beep.wav create mode 100644 android/src/main/res/values/colors.xml create mode 100644 android/src/main/res/values/dimens.xml create mode 100644 android/src/main/res/values/ids.xml create mode 100644 android/src/main/res/values/strings.xml create mode 100644 android/src/main/res/values/styles.xml create mode 100644 ios/RCTBarcode/RCTBarCode.xcodeproj/project.pbxproj create mode 100644 ios/RCTBarcode/RCTBarCode.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 ios/RCTBarcode/RCTBarcode/LineView.h create mode 100644 ios/RCTBarcode/RCTBarcode/LineView.m create mode 100644 ios/RCTBarcode/RCTBarcode/QRCScanner.h create mode 100644 ios/RCTBarcode/RCTBarcode/QRCScanner.m create mode 100644 ios/RCTBarcode/RCTBarcode/RCTBarcode.h create mode 100644 ios/RCTBarcode/RCTBarcode/RCTBarcode.m create mode 100644 ios/RCTBarcode/RCTBarcode/RCTBarcodeManager.h create mode 100644 ios/RCTBarcode/RCTBarcode/RCTBarcodeManager.m create mode 100644 ios/RCTBarcode/RCTBarcode/RectView.h create mode 100644 ios/RCTBarcode/RCTBarcode/RectView.m create mode 100644 ios/RCTBarcode/RCTBarcode/ScannerRect.h create mode 100644 ios/RCTBarcode/RCTBarcode/ScannerRect.m create mode 100644 ios/RCTBarcode/RCTBarcode/UIColor+Hex.h create mode 100644 ios/RCTBarcode/RCTBarcode/UIColor+Hex.m create mode 100644 ios/raw/beep.wav create mode 100644 note.md create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..294a516 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.[aod] +*.DS_Store +.DS_Store +*Thumbs.db +*.iml +.gradle +.idea +node_modules +npm-debug.log +/android/build +/ios/**/*xcuserdata* +/ios/**/*xcshareddata* \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..294a516 --- /dev/null +++ b/.npmignore @@ -0,0 +1,12 @@ +*.[aod] +*.DS_Store +.DS_Store +*Thumbs.db +*.iml +.gradle +.idea +node_modules +npm-debug.log +/android/build +/ios/**/*xcuserdata* +/ios/**/*xcshareddata* \ No newline at end of file diff --git a/Barcode.js b/Barcode.js new file mode 100644 index 0000000..33aea36 --- /dev/null +++ b/Barcode.js @@ -0,0 +1,74 @@ + +import React, { + PropTypes, + Component, +} from 'react' +import { + View, + requireNativeComponent, + NativeModules, + AppState, + Platform, +} from 'react-native' + +const BarcodeManager = Platform.OS == 'ios' ? NativeModules.Barcode : NativeModules.CaptureModule; + + +export default class Barcode extends Component { + + static defaultProps = { + barCodeTypes: Object.values(BarcodeManager.barCodeTypes), + scannerRectWidth: 255, + scannerRectHeight: 255, + scannerRectTop: 0, + scannerRectLeft: 0, + scannerLineInterval: 3000, + scannerRectCornerColor: `#09BB0D`, + } + + static propTypes = { + ...View.propTypes, + onBarCodeRead: PropTypes.func.isRequired, + barCodeTypes: PropTypes.array, + scannerRectWidth: PropTypes.number, + scannerRectHeight: PropTypes.number, + scannerRectTop: PropTypes.number, + scannerRectLeft: PropTypes.number, + scannerLineInterval: PropTypes.number, + scannerRectCornerColor: PropTypes.string, + } + + render() { + return ( + + ) + } + + componentDidMount() { + AppState.addEventListener('change', this._handleAppStateChange); + } + componentWillUnmount() { + AppState.removeEventListener('change', this._handleAppStateChange); + } + + startScan() { + BarcodeManager.startSession() + } + + stopScan() { + BarcodeManager.stopSession() + } + + _handleAppStateChange = (currentAppState) => { + if(currentAppState !== 'active' ) { + this.stopScan() + } + else { + this.startScan() + } + } +} + +const NativeBarCode = requireNativeComponent(Platform.OS == 'ios' ? 'RCTBarcode' : 'CaptureView', Barcode) diff --git a/README.md b/README.md index 113fcb8..f8045d9 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -# react-native-smart-barcode \ No newline at end of file +# react-native-smart-barcode + +A smart barcode scanner component for React Native app. +The library uses [https://github.com/zxing/zxing][1] to decode the barcodes for android. + +[0]: https://github.com/cyqresig/ReactNativeComponentDemos +[1]: https://github.com/zxing/zxing \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..75984e0 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion "24.0.0" + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:23.4.0' + compile 'com.facebook.react:react-native:+' + compile 'com.google.zxing:core:3.2.1' +} diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro new file mode 100644 index 0000000..02b46d1 --- /dev/null +++ b/android/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/cyqresig/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4ddd8fc --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/android/src/main/java/com/reactnativecomponent/barcode/CaptureView.java b/android/src/main/java/com/reactnativecomponent/barcode/CaptureView.java new file mode 100644 index 0000000..011c8b3 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/CaptureView.java @@ -0,0 +1,1090 @@ +package com.reactnativecomponent.barcode; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetFileDescriptor; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.hardware.Camera; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.os.Handler; +import android.os.Vibrator; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.PopupWindow; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.Result; +import com.reactnativecomponent.barcode.camera.CameraManager; +import com.reactnativecomponent.barcode.decoding.CaptureActivityHandler; +import com.reactnativecomponent.barcode.view.LinearGradientView; +import com.reactnativecomponent.barcode.view.ViewfinderView; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Vector; + + +public class CaptureView extends FrameLayout implements SurfaceHolder.Callback { + + + private CaptureActivityHandler handler; + private ViewfinderView viewfinderView; + private boolean hasSurface; + private Vector decodeFormats; + private String characterSet; + //private InactivityTimer inactivityTimer; + private MediaPlayer mediaPlayer; + private boolean playBeep=true; + private static final float BEEP_VOLUME = 0.10f; + private boolean vibrate; + private Activity activity; + private ViewGroup.LayoutParams param; + private int ScreenWidth, ScreenHeight; + private SurfaceView surfaceView; + private long beginTime; + private int height; + private int width; + public boolean decodeFlag = true; + /** + * 缩放级别拖动条 + */ + private Handler mHandler; + /** + * 当前缩放级别 默认为0 + */ + private int mZoom = 0; + /** + * react-native 设置属性 + */ + private int cX;//X轴偏移 + private int cY;//Y轴偏移 + private int CORNER_WIDTH = 4;//四个对应角宽度 + private int MIDDLE_LINE_WIDTH = 3;//扫描线宽度 + private int CORNER_COLOR = Color.GREEN; + private int Min_Frame_Width;//扫描框最小单位 + //框的颜色 + private String Text = "";//扫描框显示的文字 + //扫描框宽 + private int MAX_FRAME_WIDTH; + //扫描框高 + private int MAX_FRAME_HEIGHT; + private float density; + /** + * s扫码横线的移动时间 + */ + public int scanTime = 1000; + private long changeTime = 1000; + private int focusTime = 1000; + private long sleepTime = 2000; + public OnEvChangeListener onEvChangeListener; + + private View popupWindowContent; + private PopupWindow popupWindow; + private LinearGradientView linearGradientView; + SurfaceHolder holder; + boolean autoStart = true;//是否自动启动扫描 + String ResultStr=""; + + + + + /* private final VerticalSeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new VerticalSeekBar.OnSeekBarChangeListener() { + + @Override + public void onProgressChanged(VerticalSeekBar seekBar, int progress, + boolean fromUser) { + // TODO Auto-generated method stub + + //setZoom(progress); + + mHandler.removeCallbacksAndMessages(progressBar); + //ZOOM模式下 在结束四秒后隐藏seekbar 设置token为mZoomSeekBar用以在连续点击时移除前一个定时任务 + mHandler.postAtTime(new Runnable() { + + @Override + public void run() { + // TODO Auto-generated method stub +// progressBar.setVisibility(View.GONE); + if (popupWindow.isShowing()) { + popupWindow.dismiss(); + } + } + }, progressBar, SystemClock.uptimeMillis() + 4000); + } + + @Override + public void onStartTrackingTouch(VerticalSeekBar VerticalSeekBar) { + + } + + @Override + public void onStopTrackingTouch(VerticalSeekBar VerticalSeekBar) { + + } + + + };*/ + + + public CaptureView(Activity activity, Context context) { + super(context); + this.activity = activity; + CameraManager.init(activity.getApplication()); + param = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + density = context.getResources().getDisplayMetrics().density; + Min_Frame_Width = (int) (100 * density + 0.5f); + Resources resources = activity.getResources(); + DisplayMetrics dm = resources.getDisplayMetrics(); + ScreenWidth = dm.widthPixels; + ScreenHeight = dm.heightPixels; + + // x=screenResolution.x; + // y=screenResolution.y; + + hasSurface = false; + this.setOnTouchListener(new TouchListener()); + } + + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + height = getHeight(); + width = getWidth(); + } + + public ViewfinderView getViewfinderView() { + return viewfinderView; + } + + public Handler getHandler() { + return handler; + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + +// Log.i("Test", "height:" + height + "width:" + width); + initCameraManager(); + this.holder = holder; + + if (autoStart) { + startScan(); + } + + } + + private void initCameraManager() { + + CameraManager.get().x = cX + width; + CameraManager.get().y = cY + height; + CameraManager.get().MIN_FRAME_WIDTH = MAX_FRAME_WIDTH; + CameraManager.get().MIN_FRAME_HEIGHT = MAX_FRAME_HEIGHT; + CameraManager.get().MAX_FRAME_WIDTH = MAX_FRAME_WIDTH; + CameraManager.get().MAX_FRAME_HEIGHT = MAX_FRAME_HEIGHT; + CameraManager.get().setFocusTime(focusTime); + + } +/* + @Override + public void onViewAdded(View child) { + if (this.viewfinderView == child) return; + // remove and readd view to make sure it is in the back. + // @TODO figure out why there was a z order issue in the first place and fix accordingly. + if (viewfinderView != null) { + this.removeView(this.viewfinderView); + this.addView(this.viewfinderView); + } + }*/ + + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + + stopScan(); + + } + + /** + * Activity onResume后调用view的onAttachedToWindow + */ + @Override + protected void onAttachedToWindow() { + init(); + super.onAttachedToWindow(); + + + } + + /** + * surfaceview 扫码框 声音管理 + */ + private void init() { + if (mHandler == null) { + mHandler = new Handler(); + } + surfaceView = new SurfaceView(activity); + surfaceView.setLayoutParams(param); + surfaceView.getLayoutParams().height = ScreenHeight; + surfaceView.getLayoutParams().width = ScreenWidth; + SurfaceHolder surfaceHolder = surfaceView.getHolder(); + if (hasSurface) { + initCamera(surfaceHolder); + } else { + surfaceHolder.addCallback(this); + + } + this.addView(surfaceView); + viewfinderView = new ViewfinderView(activity, scanTime, CORNER_COLOR); + viewfinderView.CORNER_WIDTH = CORNER_WIDTH; + viewfinderView.ShowText = Text; + viewfinderView.setLayoutParams(param); + viewfinderView.getLayoutParams().height = ScreenHeight; + viewfinderView.getLayoutParams().width = ScreenWidth; + viewfinderView.setBackgroundColor(getResources().getColor(R.color.transparent)); + viewfinderView.setMIDDLE_LINE_WIDTH(this.MIDDLE_LINE_WIDTH); + this.addView(viewfinderView); + + linearGradientView = new LinearGradientView(activity, activity); + linearGradientView.setLayoutParams(param); + linearGradientView.setFrameColor(CORNER_COLOR); + + +// decodeFormats = null; + characterSet = null; + + + + vibrate = true; + + setPlayBeep(true); +// initProgressBar(); +// progressBar = new VerticalSeekBar(activity); + /* popupWindowContent = View.inflate(activity, R.layout.seekbar_layout, null); + progressBar = (VerticalSeekBar) popupWindowContent.findViewById(R.id.verticalSeekBar); + // 给progressbar准备一个FrameLayout的LayoutParams + + + progressBar.setIndeterminate(false); + progressBar.setThumb(null);*/ +// progressBar.setProgressDrawable(getResources().getDrawable(android.R.drawable.progress_horizontal)); + + +// + + /* + //渐变色drawable + int[] mColors= new int[]{Color.WHITE,Color.BLUE}; + GradientDrawable drawable=new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT,mColors); + drawable.setGradientType(GradientDrawable.LINEAR_GRADIENT); + drawable.setCornerRadius(15); + drawable.setStroke(10,-1); + */ + +/* LayerDrawable progressDrawable = (LayerDrawable) progressBar + .getProgressDrawable(); + Drawable[] outDrawables = new Drawable[progressDrawable + .getNumberOfLayers()]; + for (int i = 0; i < progressDrawable.getNumberOfLayers(); i++) { + switch (progressDrawable.getId(i)) { + case android.R.id.background:// 设置进度条背景 + outDrawables[i] = getResources().getDrawable(R.drawable.seek_bkg); + break; + case android.R.id.secondaryProgress:// 设置二级进度条 + outDrawables[i] = getResources().getDrawable(R.drawable.seek); + break; + case android.R.id.progress:// 设置进度条 + ClipDrawable oidDrawable = (ClipDrawable) progressDrawable + .getDrawable(i); + Drawable drawable=getResources().getDrawable(R.drawable.seek); + ClipDrawable proDrawable = new ClipDrawable(drawable, + Gravity.LEFT, ClipDrawable.HORIZONTAL); + proDrawable.setLevel(oidDrawable.getLevel()); + outDrawables[i] = proDrawable; + break; + default: + break; + } + } + progressDrawable = new LayerDrawable(outDrawables); + progressBar.setProgressDrawable(progressDrawable);*/ + + + // progressBar.setBackgroundResource(R.drawable.seek_bkg); +// progressBar.setSecondaryProgress(R.drawable.seek); +// progressBar.setThumb(getResources().getDrawable(R.drawable.seek_thumb)); +// progressBar.setMinimumHeight(20); + + + //获取当前照相机支持的最大缩放级别,值小于0表示不支持缩放。当支持缩放时,加入拖动条。 + int maxZoom = getMaxZoom(); + if (maxZoom > 0) { +// progressBar.setMax(maxZoom); +// progressBar.setOnSeekBarChangeListener(onSeekBarChangeListener); + } + + } + + public void setPlayBeep(boolean b) { + playBeep = b; + AudioManager audioService = (AudioManager) activity.getSystemService(activity.AUDIO_SERVICE); + if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) { + playBeep = false; + } + initBeepSound(); + } + + + private void initProgressBar() { +/* if (progressBar != null) { + LayoutParams progresslp = new LayoutParams( + 120, + MAX_FRAME_HEIGHT); + // 设置对其方式为:屏幕居中 + int leftMargin = (width / 2) + cX + MAX_FRAME_WIDTH / 2; + int topMargin = height / 2 + cY / 2 - MAX_FRAME_HEIGHT; +// progresslp.gravity = Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL; +// progressBar.setLayoutParams(param); + + // 创建PopupWindow实例,200,LayoutParams.MATCH_PARENT分别是宽度和高度 + popupWindow = new PopupWindow(CaptureView.this); +*//* popupWindow.setWidth((int) (20 * density)); + popupWindow.setHeight(MAX_FRAME_HEIGHT-CORNER_WIDTH*2); + popupWindow.setContentView(popupWindowContent);*//* + + popupWindow.setWidth(MAX_FRAME_WIDTH); + popupWindow.setHeight(30); + popupWindow.setContentView(linearGradientView); + + popupWindow.setBackgroundDrawable(new BitmapDrawable()); + popupWindow.setFocusable(false); + popupWindow.setOutsideTouchable(false); + + }*/ + } + + + public void startScan() { + if (!hasSurface) { + viewfinderView.drawLine = true; + hasSurface = true; + CameraManager.get().framingRectInPreview = null; + initCamera(holder); + + CameraManager.get().initPreviewCallback(); + CameraManager.get().startPreview(); + + } +// decodeFormats = null; + + handler = new CaptureActivityHandler(this, decodeFormats, + characterSet); +// handler.restartPreviewAndDecode(); + } + + public void stopScan() { + hasSurface = false; + viewfinderView.drawLine = false; + if (handler != null) { + handler.quitSynchronously(); + } + CameraManager.get().stopPreview(); + CameraManager.get().closeDriver(); + } + + public void stopQR() { + this.decodeFlag = false; + } + + public void startQR() { + this.decodeFlag = true; + startScan(); + } + + /** + * ondestroy调用,会执行onDetachedFromWindow + */ + + @Override + protected void onDetachedFromWindow() { + this.removeView(viewfinderView); + this.removeView(surfaceView); +// if(popupWindow.isShowing()){ +// popupWindow.dismiss(); +// } + if (handler != null) { + handler.quitSynchronously(); + } + + super.onDetachedFromWindow(); + } + + + private void initCamera(SurfaceHolder surfaceHolder) { + try { + CameraManager.get().openDriver(surfaceHolder); + + + } catch (IOException ioe) { + return; + } catch (RuntimeException e) { + return; + } + if (handler == null) { + handler = new CaptureActivityHandler(this, decodeFormats, + characterSet); + } + } + + public void drawViewfinder() { + viewfinderView.drawViewfinder(); + + } + + + public void handleDecode(Result obj, Bitmap barcode) { + +// viewfinderView.drawResultBitmap(barcode);//画结果图片 + + if (obj != null&& this.decodeFlag) { + playBeepSoundAndVibrate(); + String str = obj.getText();//获得扫码的结果 + /* + activity.getCapturePackage().mModuleInstance.sendMsgToRn(str); //发送到RN侧*/ + onEvChangeListener.getQRCodeResult(str,obj.getBarcodeFormat()); //观察者模式发送到RN侧 + } + stopQR(); + + +// viewfinderView.drawResultBitmap(null);//清除结果图片 + + /* + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + initCamera(surfaceView.getHolder()); + if (handler != null) { + handler.restartPreviewAndDecode(); + }*/ + + } + /* public Handler Scanhandler=new Handler() { + @Override + public void handleMessage(Message msg) { + // TODO Auto-generated method stub + super.handleMessage(msg); + // 此处可以更新UI + Bundle b = msg.getData(); + String result = b.getString("result"); + onEvChangeListener.getQRCodeResult(result); //观察者模式发送到RN侧 + } + };//不加这个分号则不能自动添加代码 +*/ + /* public void DecodeFromPath(final String path){ + + + Runnable scan_thread =new Runnable() { + @Override + public void run() { + String ResultStr= DecodeUtil.getStringFromQRCode(path); + Message msg = new Message(); + Bundle b = new Bundle();// 存放数据 + b.putString("result",ResultStr); + msg.setData(b); + + Scanhandler.sendMessage(msg); + } + }; + + Scanhandler.post(scan_thread); + + + }*/ + + + /** + * 返回数据 + * + * @param intent + * @return + */ + public String ShowResult(Intent intent) { + return intent.getData().toString(); + } + + + private void initBeepSound() { + if (playBeep && mediaPlayer == null) { + activity.setVolumeControlStream(AudioManager.STREAM_MUSIC); + mediaPlayer = new MediaPlayer(); + mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mediaPlayer.setOnCompletionListener(beepListener); + + AssetFileDescriptor file = getResources().openRawResourceFd( + R.raw.beep); + try { + mediaPlayer.setDataSource(file.getFileDescriptor(), + file.getStartOffset(), file.getLength()); + file.close(); + mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME); + mediaPlayer.prepare(); + } catch (IOException e) { + mediaPlayer = null; + } + } + } + + private static final long VIBRATE_DURATION = 200L; + + + private void playBeepSoundAndVibrate() { + if (playBeep && mediaPlayer != null) { + mediaPlayer.start(); + } + //抖动机身 + /* if (vibrate) { + Vibrator vibrator = (Vibrator) activity.getSystemService(activity.VIBRATOR_SERVICE); + vibrator.vibrate(VIBRATE_DURATION); + }*/ + } + + /** + * When the beep has finished playing, rewind to queue up another one. + */ + private final MediaPlayer.OnCompletionListener beepListener = new MediaPlayer.OnCompletionListener() { + public void onCompletion(MediaPlayer mediaPlayer) { + mediaPlayer.seekTo(0); + } + }; + + + public Activity getActivity() { + return activity; + } + + + public void setHandler(CaptureActivityHandler handler) { + this.handler = handler; + + } + + public void setAutoStart(boolean autoStart) { + this.autoStart = autoStart; + } + + public void setChangeTime(long changeTime) { + this.changeTime = changeTime; + } + + public void setFocusTime(int focusTime) { + this.focusTime = focusTime; + } + + public void setSleepTime(long sleepTime) { + this.sleepTime = sleepTime; + } + + public void setcX(int cX) { + + if (width != 0 && ((cX > width / 2 - Min_Frame_Width) || cX < (Min_Frame_Width - width / 2))) { + if (cX > 0) { + cX = width / 2 - Min_Frame_Width; + } else { + cX = Min_Frame_Width - width / 2; + } + } + + this.cX = cX; + CameraManager.get().x = cX + width; + CameraManager.get().framingRect = null; + if (viewfinderView != null) { + viewfinderView.invalidate(); + } + initProgressBar(); + } + + public void setcY(int cY) { + + + if (height != 0 && ((cY > height / 2 - Min_Frame_Width) || cY < (Min_Frame_Width - height / 2))) { + if (cY > 0) { + cY = height / 2 - Min_Frame_Width; + } else { + cY = Min_Frame_Width - height / 2; + } + } + + this.cY = cY; + CameraManager.get().y = cY + height; + CameraManager.get().framingRect = null; + if (viewfinderView != null) { + viewfinderView.invalidate(); + } + + initProgressBar(); + } + + public void setMAX_FRAME_WIDTH(int MAX_FRAME_WIDTH) { + if (width != 0 && MAX_FRAME_WIDTH > width) { + MAX_FRAME_WIDTH = width; + } + this.MAX_FRAME_WIDTH = MAX_FRAME_WIDTH; + CameraManager.get().MIN_FRAME_WIDTH = this.MAX_FRAME_WIDTH; + + CameraManager.get().MAX_FRAME_WIDTH = this.MAX_FRAME_WIDTH; + CameraManager.get().framingRect = null; + if (viewfinderView != null) { + viewfinderView.invalidate(); + } + + initProgressBar(); + } + + public void setMAX_FRAME_HEIGHT(int MAX_FRAME_HEIGHT) { + + if (height != 0 && MAX_FRAME_HEIGHT > height) { + MAX_FRAME_HEIGHT = width; + } + this.MAX_FRAME_HEIGHT = MAX_FRAME_HEIGHT; + + CameraManager.get().MIN_FRAME_HEIGHT = this.MAX_FRAME_HEIGHT; + + CameraManager.get().MAX_FRAME_HEIGHT = this.MAX_FRAME_HEIGHT; + CameraManager.get().framingRect = null; + if (viewfinderView != null) { + viewfinderView.invalidate(); + } + + initProgressBar(); + } + + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + + super.onWindowFocusChanged(hasWindowFocus); + + if (hasWindowFocus) { + //对应onresume + this.holder = surfaceView.getHolder(); + startScan(); + } else { + //对应onpause + stopScan(); + } + + } + + public void setText(String text) { + Text = text; + } + + /** + * 设置扫描线运动时间 + * + * @param scanTime + */ + public void setScanTime(int scanTime) { + this.scanTime = scanTime; + if (viewfinderView != null) { + viewfinderView.scanTime = scanTime; + } + } + + public void setCORNER_COLOR(int CORNER_COLOR) { + this.CORNER_COLOR = CORNER_COLOR; + if (viewfinderView != null) { + viewfinderView.frameColor = this.CORNER_COLOR; + viewfinderView.frameBaseColor = reSetColor(this.CORNER_COLOR); + } + + } + + /** + * 设置四个角的颜色 + * + * @param CORNER_WIDTH + */ + public void setCORNER_WIDTH(int CORNER_WIDTH) { + this.CORNER_WIDTH = CORNER_WIDTH; + + if (viewfinderView != null) { + viewfinderView.setCORNER_WIDTH(this.CORNER_WIDTH); + } + } + + public void setMIDDLE_LINE_WIDTH(int MIDDLE_LINE_WIDTH) { + this.MIDDLE_LINE_WIDTH = MIDDLE_LINE_WIDTH; + if (viewfinderView != null) { + viewfinderView.setMIDDLE_LINE_WIDTH(this.MIDDLE_LINE_WIDTH); + } + } + + /** + * 开启闪光灯常亮 + */ + public void OpenFlash(){ + try { + Camera.Parameters param =CameraManager.get().getCamera().getParameters(); + + param.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); + + CameraManager.get().getCamera().setParameters(param); + } catch (Exception e) { + e.printStackTrace(); + } + } /** + * 关闭闪光灯常亮 + */ + public void CloseFlash(){ + try { + Camera.Parameters param = CameraManager.get().getCamera().getParameters(); + + param.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); + + + CameraManager.get().getCamera().setParameters(param); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * RN调用方法 + * 改变扫描框大小 + * + * @param WIDTH + * @param HEIGHT + */ + public void setCHANGE_WIDTH(final int WIDTH, final int HEIGHT) { + //属性动画 +// Toast.makeText(activity, "width" + WIDTH, Toast.LENGTH_SHORT).show(); + if (viewfinderView != null) { + +// if(popupWindow.isShowing()){ +// popupWindow.dismiss(); +// } + + int widthScan = (width / 2 - Math.abs(cX)) - CORNER_WIDTH; + + if (widthScan < Min_Frame_Width) { + widthScan = Min_Frame_Width - CORNER_WIDTH; + } + int heightScan = (height / 2 - Math.abs(cY)) - CORNER_WIDTH; + + if (heightScan < Min_Frame_Width) { + heightScan = Min_Frame_Width - CORNER_WIDTH; + } + + ObjectAnimator animWidth = ObjectAnimator.ofInt(CaptureView.this, "MAX_FRAME_WIDTH", MAX_FRAME_WIDTH, WIDTH / 2 > widthScan ? widthScan * 2 : WIDTH - CORNER_WIDTH); + ObjectAnimator animHeight = ObjectAnimator.ofInt(CaptureView.this, "MAX_FRAME_HEIGHT", MAX_FRAME_HEIGHT, HEIGHT / 2 > heightScan ? heightScan * 2 : HEIGHT - CORNER_WIDTH); + + + AnimatorSet animSet = new AnimatorSet(); + animSet.play(animWidth).with(animHeight); + animSet.setDuration(changeTime); + + animSet.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + + viewfinderView.drawLine = false; + stopQR(); + } + + @Override + public void onAnimationEnd(Animator animation) { +// stopScan(); + + + int widthScan = (width / 2 - Math.abs(cX)) - CORNER_WIDTH; + + if (widthScan < Min_Frame_Width) { + widthScan = Min_Frame_Width - CORNER_WIDTH; + } + int heightScan = (height / 2 - Math.abs(cY)) - CORNER_WIDTH; + + if (heightScan < Min_Frame_Width) { + heightScan = Min_Frame_Width - CORNER_WIDTH; + } + + MAX_FRAME_WIDTH = WIDTH / 2 > widthScan ? widthScan * 2 : WIDTH - CORNER_WIDTH; + MAX_FRAME_HEIGHT = HEIGHT / 2 > heightScan ? heightScan * 2 : HEIGHT - CORNER_WIDTH; + + CameraManager.get().MIN_FRAME_WIDTH = MAX_FRAME_WIDTH; + CameraManager.get().MIN_FRAME_HEIGHT = MAX_FRAME_HEIGHT; + CameraManager.get().MAX_FRAME_WIDTH = MAX_FRAME_WIDTH; + CameraManager.get().MAX_FRAME_HEIGHT = MAX_FRAME_HEIGHT; + +// Log.i("Test", "width:" + width + ",height:" + height); +// Log.i("Test", "cX:" + cX + ",cY:" + cY); +// Log.i("Test", "MAX_FRAME_WIDTH:" + MAX_FRAME_WIDTH + ",MAX_FRAME_HEIGHT:" + MAX_FRAME_HEIGHT); + + CameraManager.get().framingRectInPreview = null; +// decodeFormats = null; + viewfinderView.drawLine = true; + holder = surfaceView.getHolder(); +// startScan(); + startQR(); + initProgressBar(); + } + + @Override + public void onAnimationCancel(Animator animation) { + stopScan(); + CameraManager.get().framingRectInPreview = null; +// decodeFormats = null; + holder = surfaceView.getHolder(); + startScan(); + viewfinderView.drawLine = true; + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + + animSet.start(); + + + } + + } + + + private final class TouchListener implements OnTouchListener { + + /** + * 放大缩小照片模式 + */ + private static final int MODE_ZOOM = 1; + private int mode = MODE_ZOOM;// 初始状态 + + /** + * 用于记录拖拉图片移动的坐标位置 + */ + + private float startDis; + + + @Override + public boolean onTouch(View v, MotionEvent event) { + + /** 通过与运算保留最后八位 MotionEvent.ACTION_MASK = 255 */ + switch (event.getAction() & MotionEvent.ACTION_MASK) { + // 手指压下屏幕 + case MotionEvent.ACTION_DOWN: + mode = MODE_ZOOM; + startDis = event.getY(); +// Log.i("Test", "ACTION_DOWN"); + + +// int leftMargin = (width / 2) + cX + MAX_FRAME_WIDTH/2-(int)(25*density); + int leftMargin = cX / 2 + MAX_FRAME_WIDTH / 2 - (int) (25 * density); + int topMargin = cY / 2 - (int) (25 * density); +/** + * 显示seekbar + */ +// popupWindow.showAtLocation(v, Gravity.CENTER, leftMargin,topMargin); + + break; + /* case MotionEvent.ACTION_POINTER_DOWN: + //如果mZoomSeekBar为null 表示该设备不支持缩放 直接跳过设置mode Move指令也无法执行 + if (seekbar == null) return true; + //移除token对象为mZoomSeekBar的延时任务 + mHandler.removeCallbacksAndMessages(seekbar); + seekbar.setVisibility(View.VISIBLE); + + mode = MODE_ZOOM; + *//** 计算两个手指间的距离 *//* + startDis = distance(event); + break;*/ + case MotionEvent.ACTION_MOVE: + + /** + * 控制设置zoom没16毫秒触发,不到时间不触发 + */ + + if (mode == MODE_ZOOM) { + /* //只有同时触屏两个点的时候才执行 + if(event.getPointerCount()<2) return true;*/ + float endDis = startDis - event.getY();// 结束距离 + + + int scale = (int) (endDis / (ScreenHeight / getMaxZoom())); + + if (scale == 0 && endDis < 0) { + scale = -1; + } else if (scale == 0 && endDis > 0) { + scale = 1; + } +// Log.i("Test", "scale:" + scale); + + /** + * 处理时间 + */ + long endTime = System.currentTimeMillis(); + + long time = endTime - beginTime; + + beginTime = System.currentTimeMillis(); + + if (scale >= 1 || scale <= -1) { + int zoom = getZoom() + scale; + //zoom不能超出范围 + if (zoom > getMaxZoom()) zoom = getMaxZoom(); + if (zoom < 0) zoom = 0; +// Log.i("Test", "zoom:" + zoom + ",Time:" + time); + setZoom(zoom); +// progressBar.setProgress(zoom); + //将最后一次的距离设为当前距离 +// startDis = endDis; + } + } + break; + // 手指离开屏幕 + case MotionEvent.ACTION_UP: + + /*if(mode!=MODE_ZOOM){ + //设置聚焦 + Point point=new Point((int)event.getX(), (int)event.getY()); + mCameraView.onFocus(point,autoFocusCallback); + mFocusImageView.startFocus(point); + }else {*/ + //ZOOM模式下 在结束两秒后隐藏seekbar 设置token为mZoomSeekBar用以在连续点击时移除前一个定时任务 + + /*mHandler.postAtTime(new Runnable() { + + @Override + public void run() { + // TODO Auto-generated method stub + if(popupWindow.isShowing()){ + popupWindow.dismiss(); + } + } + }, progressBar,SystemClock.uptimeMillis()+5000); +// }*/ + break; + } + return true; + } + +// /** +// * 计算两个手指间的距离 +// */ +// private float distance(MotionEvent event) { +// float dx = event.getX(1) - event.getX(0); +// float dy = event.getY(1) - event.getY(0); +// /** 使用勾股定理返回两点之间的距离 */ +// return (float) Math.sqrt(dx * dx + dy * dy); +// } + + } + + /** + * 获取最大缩放级别,最大为40 + * + * @return + */ + + public int getMaxZoom() { + if (CameraManager.get().getCamera() != null) { + Camera.Parameters parameters = CameraManager.get().getCamera().getParameters(); + if (!parameters.isZoomSupported()) return -1; + + return parameters.getMaxZoom() > 40 ? 40 : parameters.getMaxZoom(); + } + return 40; + } + + /** + * 设置相机缩放级别 + * + * @param zoom + */ + + public void setZoom(int zoom) { + + Camera.Parameters parameters; + //注意此处为录像模式下的setZoom方式。在Camera.unlock之后,调用getParameters方法会引起android框架底层的异常 + //stackoverflow上看到的解释是由于多线程同时访问Camera导致的冲突,所以在此使用录像前保存的mParameters。 + if (CameraManager.get().getCamera() != null) { + parameters = CameraManager.get().getCamera().getParameters(); + if (!parameters.isZoomSupported()) return; + parameters.setZoom(zoom); + CameraManager.get().getCamera().setParameters(parameters); + mZoom = zoom; + } + } + + public int getZoom() { + return mZoom; + } + + public Vector getDecodeFormats() { + return decodeFormats; + } + + public void setDecodeFormats(List decode) { + decodeFormats=new Vector(); + for(BarcodeFormat format : BarcodeFormat.values()){ + if(decode.contains(format.toString())){ + decodeFormats.add(format); + } + } + + } + + public interface OnEvChangeListener { + public void getQRCodeResult(String result, BarcodeFormat format); + } + + public void setOnEvChangeListener(OnEvChangeListener onEvChangeListener) { + this.onEvChangeListener = onEvChangeListener; + } + + + + /** + * 颜色换算 + */ + public int reSetColor(int startInt) { + + int startA = (startInt >> 24) & 0xff; + int startR = (startInt >> 16) & 0xff; + int startG = (startInt >> 8) & 0xff; + int startB = startInt & 0xff; + + int endA = ((startInt / 2) >> 24) & 0xff; + return ((startA + (endA - startA)) << 24) + | (startR << 16) + | (startG << 8) + | (startB); + + + } +} + + diff --git a/android/src/main/java/com/reactnativecomponent/barcode/QRCodeResultEvent.java b/android/src/main/java/com/reactnativecomponent/barcode/QRCodeResultEvent.java new file mode 100644 index 0000000..e3abf66 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/QRCodeResultEvent.java @@ -0,0 +1,51 @@ +package com.reactnativecomponent.barcode; + +import android.util.Log; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; +import com.google.zxing.BarcodeFormat; + + +public class QRCodeResultEvent extends Event { + String result; + BarcodeFormat format; + public QRCodeResultEvent(int viewTag, long timestampMs,String result,BarcodeFormat format) { +// super(viewTag, timestampMs); + super(viewTag); + this.result=result; + this.format=format; + } + + @Override + public String getEventName(){ + return "QRCodeResult"; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData()); + } + + private WritableMap serializeEventData() { + WritableMap eventData = Arguments.createMap(); + WritableMap data = Arguments.createMap(); + data.putString("code", getResult()); + data.putString("type",format.toString()); +// Log.i("Test","code="+getResult()); + eventData.putMap("data",data); + + + return eventData; + } + + public String getResult() { + return result; + } + + public BarcodeFormat getFormat() { + return format; + } +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/RCTCaptureManager.java b/android/src/main/java/com/reactnativecomponent/barcode/RCTCaptureManager.java new file mode 100644 index 0000000..017b1a7 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/RCTCaptureManager.java @@ -0,0 +1,223 @@ +package com.reactnativecomponent.barcode; + + +import android.app.Activity; +import android.graphics.Color; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.common.SystemClock; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.react.uimanager.annotations.ReactProp; +import com.google.zxing.BarcodeFormat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Vector; + + +public class RCTCaptureManager extends ViewGroupManager { + private static final String REACT_CLASS = "CaptureView";//要与类名一致 + public static final int CHANGE_SHOW = 0;//用来标记方法的下标 + Activity activity; + CaptureView cap; + private float density; + + + public RCTCaptureManager(Activity activity) { + this.activity = activity; + density = activity.getResources().getDisplayMetrics().density; + } + + @Override + public String getName() { + return REACT_CLASS; + } + + @Override + public CaptureView createViewInstance(ThemedReactContext context) { + cap = new CaptureView(activity, context); +// ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); +// +// cap.setLayoutParams(params); + + return cap; + } + @ReactProp(name = "barCodeTypes") + public void setbarCodeTypes(CaptureView view, ReadableArray barCodeTypes) { + + if (barCodeTypes == null) { + return; + } + List result = new ArrayList(barCodeTypes.size()); + for (int i = 0; i < barCodeTypes.size(); i++) { + result.add(barCodeTypes.getString(i)); + } + view.setDecodeFormats(result); + + } + @ReactProp(name = "scannerRectLeft", defaultInt = 0) + public void setCX(CaptureView view, int cX) { + view.setcX((int) (cX* density + 0.5f)); + } + + @ReactProp(name = "scannerRectTop", defaultInt = 0) + public void setCY(CaptureView view, int cY) { + view.setcY((int)(cY* density + 0.5f)); + } + + @ReactProp(name = "scannerRectWidth", defaultInt = 255) + public void setMAX_FRAME_WIDTH(CaptureView view, int FRAME_WIDTH) { + view.setMAX_FRAME_WIDTH((int) (FRAME_WIDTH * density + 0.5f)); + } + + @ReactProp(name = "scannerRectHeight", defaultInt = 255) + public void setMAX_FRAME_HEIGHT(CaptureView view, int FRAME_HEIGHT) { + view.setMAX_FRAME_HEIGHT((int) (FRAME_HEIGHT * density + 0.5f)); + } + +/* @ReactProp(name = "text") + public void setText(CaptureView view, String text) { + view.setText(text); + }*/ + + + /* @ReactProp(name = "scannerRectCornerWidth", defaultInt = 4) + public void setCORNER_WIDTH(CaptureView view, int CORNER_WIDTH) { + if(CORNER_WIDTH<4){ + CORNER_WIDTH=4; + } + view.setCORNER_WIDTH(CORNER_WIDTH); + }*/ + + /* @ReactProp(name = "scannerLineWidth", defaultInt = 3) + public void setMIDDLE_LINE_WIDTH(CaptureView view, int MIDDLE_LINE_WIDTH) { + if(MIDDLE_LINE_WIDTH<3){ + MIDDLE_LINE_WIDTH=3; + } + view.setMIDDLE_LINE_WIDTH(MIDDLE_LINE_WIDTH); + }*/ + + //扫描线移动一圈时间 + @ReactProp(name = "scannerLineInterval", defaultInt = 1000) + public void setTime(CaptureView view, int time) { + view.setScanTime(time); + } + + /* //扫描框尺寸动画持续时间 + @ReactProp(name = "changeTime", defaultInt = 1000) + public void setChangeTime(CaptureView view, int time) { + view.setChangeTime(time); + } + + //camera聚集时间 + @ReactProp(name = "focusTime", defaultInt = 1000) + public void setfocusTime(CaptureView view, int time) { + view.setFocusTime(time); + } + + @ReactProp(name = "autoStart", defaultBoolean = true) + public void setAutoStart(CaptureView view, boolean start) { + view.setAutoStart(start); + }*/ + + @ReactProp(name = "scannerRectCornerColor") + public void setCORNER_COLOR(CaptureView view, String color) { + if (color != null && !color.isEmpty()) { + view.setCORNER_COLOR(Color.parseColor(color));//转换成16进制 + } + } + + /* //扫码成功提示音 + @ReactProp(name = "playBeep",defaultBoolean = true) + public void setPlayBeep(CaptureView view, boolean isBeep) { + view.setPlayBeep(isBeep); + } +*/ + + @Override + public + @Nullable + Map getCommandsMap() { + return MapBuilder.of( + "change", + CHANGE_SHOW);//js处发送的方法名字 + } + + @Override + public void receiveCommand(CaptureView root, int commandId, @Nullable ReadableArray config) { + // super.receiveCommand(root, commandId, config); + if (commandId == CHANGE_SHOW) { + this.changeWidthHeight(config.getMap(0)); + } + } + + + @ReactMethod + public void changeWidthHeight(final ReadableMap config) { +// Log.i("Test", "changeWidthHeight"); + if (cap != null) { + activity.runOnUiThread(new Runnable() { + public void run() { + int width = config.getInt("FRAME_WIDTH"); + int height = config.getInt("FRAME_HEIGHT"); + cap.setCHANGE_WIDTH((int)(width* density + 0.5f), (int)(height* density + 0.5f)); + } + }); + } + } + + @Override + protected void addEventEmitters( + final ThemedReactContext reactContext, + final CaptureView view) { + view.setOnEvChangeListener( + new CaptureView.OnEvChangeListener() { + @Override + public void getQRCodeResult(String result,BarcodeFormat format) { + reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher() + .dispatchEvent(new QRCodeResultEvent(view.getId(), SystemClock.nanoTime(), result,format)); + } + + }); + } + + @Override + public Map getExportedCustomDirectEventTypeConstants() { + return MapBuilder.builder() + .put("QRCodeResult", MapBuilder.of("registrationName", "onBarCodeRead"))//registrationName 后的名字,RN中方法也要是这个名字否则不执行 + .build(); + } + +/* + + @ReactProp(name = "aspect") + public void setAspect(CaptureView view, int aspect) { + view.setAspect(aspect); + } + + @ReactProp(name = "captureMode") + public void setCaptureMode(RCTCameraView view, int captureMode) { + // TODO - implement video mode + } + + @ReactProp(name = "captureTarget") + public void setCaptureTarget(RCTCameraView view, int captureTarget) { + // No reason to handle this props value here since it's passed again to the RCTCameraModule capture method + } + + @ReactProp(name = "type") + public void setType(RCTCameraView view, int type) { + view.setCameraType(type); + } +*/ + + +} \ No newline at end of file diff --git a/android/src/main/java/com/reactnativecomponent/barcode/RCTCaptureModule.java b/android/src/main/java/com/reactnativecomponent/barcode/RCTCaptureModule.java new file mode 100644 index 0000000..924b2ee --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/RCTCaptureModule.java @@ -0,0 +1,167 @@ +package com.reactnativecomponent.barcode; + +import android.os.Environment; +import android.support.annotation.Nullable; +import android.widget.Toast; + +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.google.zxing.BarcodeFormat; +import com.reactnativecomponent.barcode.decoding.DecodeUtil; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + + +public class RCTCaptureModule extends ReactContextBaseJavaModule { + private ReactApplicationContext mContext; + RCTCaptureManager captureManager; + + + public RCTCaptureModule(ReactApplicationContext reactContext, RCTCaptureManager captureManager) { + super(reactContext); + mContext = reactContext; + + this.captureManager = captureManager; + + } + + @Override + public String getName() { + return "CaptureModule"; + } + +// public void sendMsgToRn(String msg) { +// //将消息msg发送给RN侧 +// mContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("AndroidToRNMessage", msg); +// +// } + + + + @Nullable + @Override + public Map getConstants() { + return Collections.unmodifiableMap(new HashMap() { + { + put("barCodeTypes", getBarCodeTypes()); + } + private Map getBarCodeTypes() { + return Collections.unmodifiableMap(new HashMap() { + { + put("upce", BarcodeFormat.UPC_E.toString()); + put("code39", BarcodeFormat.CODE_39.toString()); +// put("code39mod43",BarcodeFormat. ); + put("ean13",BarcodeFormat.EAN_13.toString() ); + put("ean8",BarcodeFormat.EAN_8.toString() ); + put("code93", BarcodeFormat.CODE_93.toString()); + put("code128", BarcodeFormat.CODE_128.toString()); + put("pdf417",BarcodeFormat.PDF_417.toString() ); + put("qr",BarcodeFormat.QR_CODE.toString() ); + put("aztec", BarcodeFormat.AZTEC.toString()); +// put("interleaved2of5", BarcodeFormat.); + put("itf14",BarcodeFormat.ITF.toString()); + put("datamatrix", BarcodeFormat.DATA_MATRIX.toString()); + } + + + }); + } + }); + } + + + + @ReactMethod + public void startSession() { + + if (captureManager.cap != null) { + getCurrentActivity().runOnUiThread(new Runnable() { + public void run() { + captureManager.cap.startQR(); +// captureManager.cap.startScan(); +// Toast.makeText(getCurrentActivity(), "startScan", Toast.LENGTH_SHORT).show(); + } + }); + } + } + + + @ReactMethod + public void stopSession() { + if (captureManager.cap != null) { + getCurrentActivity().runOnUiThread(new Runnable() { + public void run() { +// captureManager.cap.stopQR(); + captureManager.cap.stopScan(); +// Toast.makeText(getCurrentActivity(), "stopScan", Toast.LENGTH_SHORT).show(); + } + }); + } + } + + @ReactMethod + public void stopFlash() { + if (captureManager.cap != null) { + getCurrentActivity().runOnUiThread(new Runnable() { + public void run() { + captureManager.cap.CloseFlash(); +// Toast.makeText(getCurrentActivity(), "stopFlash", Toast.LENGTH_SHORT).show(); + } + }); + } + } + + @ReactMethod + public void startFlash() { + if (captureManager.cap != null) { + getCurrentActivity().runOnUiThread(new Runnable() { + public void run() { + captureManager.cap.OpenFlash(); +// Toast.makeText(getCurrentActivity(), "startFlash", Toast.LENGTH_SHORT).show(); + } + }); + } + } + + @ReactMethod + public void DecodeFromPath(final String path, + final Callback errorCallback, + final Callback successCallback) { + + new Thread(new Runnable() { + public void run() { + try { + String s = Environment.getExternalStorageDirectory() + .getAbsolutePath() + "/" + "IMG_20161011_170552.jpg"; + //不加这个分号则不能自动添加代码 + + String ResultStr = DecodeUtil.getStringFromQRCode(s); + successCallback.invoke(ResultStr); + + } catch (Exception e) { + e.printStackTrace(); + errorCallback.invoke(e.getMessage()); + } + } + }).start(); +// Toast.makeText(getCurrentActivity(), "DecodeFromPath:"+path, Toast.LENGTH_SHORT).show(); + + } + + +/* + @ReactMethod + public void changeWidthHeight(final int width,final int height) { + + if (captureManager.cap != null) { + activity.runOnUiThread(new Runnable() { + public void run() { + captureManager.cap.setCHANGE_WIDTH(width, height); + } + }); + } + }*/ +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/RCTCapturePackage.java b/android/src/main/java/com/reactnativecomponent/barcode/RCTCapturePackage.java new file mode 100644 index 0000000..5b5930a --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/RCTCapturePackage.java @@ -0,0 +1,51 @@ +package com.reactnativecomponent.barcode; + +import android.app.Activity; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + + +public class RCTCapturePackage implements ReactPackage { + Activity activity; + RCTCaptureModule mModuleInstance; + RCTCaptureManager captureManager; +// RCTLinearGradientViewManager linearGradientViewManager; + + public RCTCapturePackage(Activity activity) { + this.activity = activity; + captureManager = new RCTCaptureManager(activity); +// linearGradientViewManager = new RCTLinearGradientViewManager(activity); + } + + + + @Override + public List createNativeModules(ReactApplicationContext reactApplicationContext) { + mModuleInstance = new RCTCaptureModule(reactApplicationContext,captureManager); + return Arrays.asList( + mModuleInstance + ); + } + + @Override + public List> createJSModules() { + return Collections.emptyList(); + } + + @Override + public List createViewManagers(ReactApplicationContext reactApplicationContext) { + //noinspection ArraysAsListWithZeroOrOneArgument + +// return Arrays.asList(captureManager,linearGradientViewManager); + return Arrays.asList(captureManager); + } + + } diff --git a/android/src/main/java/com/reactnativecomponent/barcode/RCTLinearGradientViewManager.java b/android/src/main/java/com/reactnativecomponent/barcode/RCTLinearGradientViewManager.java new file mode 100644 index 0000000..6caa3ae --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/RCTLinearGradientViewManager.java @@ -0,0 +1,87 @@ +package com.reactnativecomponent.barcode; + + + +import android.app.Activity; +import android.graphics.Color; +import android.support.annotation.Nullable; +import android.util.Log; +import android.view.ViewGroup; + +import com.facebook.react.uimanager.SimpleViewManager; +import com.facebook.react.uimanager.ThemedReactContext; + +import com.facebook.react.uimanager.annotations.ReactProp; +import com.reactnativecomponent.barcode.view.LinearGradientView; + +public class RCTLinearGradientViewManager extends SimpleViewManager{ + + private static final String REACT_CLASS = "LinearGradientView";//要与类名一致 + LinearGradientView linearGradientView; + private float density; + Activity activity; + + + public RCTLinearGradientViewManager(Activity activity) { + this.activity=activity; + density = activity.getResources().getDisplayMetrics().density; + } + + @Override + public String getName() { + return REACT_CLASS; + } + @Override + protected LinearGradientView createViewInstance(ThemedReactContext reactContext) { + linearGradientView=new LinearGradientView(reactContext,activity); + return linearGradientView; + + } + + + @ReactProp(name = "size" ,defaultInt = 1) + public void setSize(LinearGradientView view,int size) { + int num=(int)(size*density+0.5f); + if(num<3){ + num=3; + }else if(num >10){ + num=5; + } + ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + + params.height=num; + params.width =view.width; + view.setLayoutParams(params); + view.size=num; + + } + + + @ReactProp(name = "width" ,defaultInt = 0) + public void setWidth(LinearGradientView view, int width) { + ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); +if((int) (width * density + 0.5f)>1) { + params.width = (int) (width * density + 0.5f); + params.height=view.size; +} + view.width=(int)(width*density+0.5f); + view.setLayoutParams(params); + + + } + + + + @ReactProp(name = "frameColor") + public void setFrameColor(LinearGradientView view, String color) { + if (color != null && !color.isEmpty()) { + view.setFrameColor(Color.parseColor(color));//转换成16进制 + } + } + + + @Override + public void setBackgroundColor(LinearGradientView view, int backgroundColor) { + // super.setBackgroundColor(view, backgroundColor); + } +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/camera/AutoFocusCallback.java b/android/src/main/java/com/reactnativecomponent/barcode/camera/AutoFocusCallback.java new file mode 100644 index 0000000..9ad5a7b --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/camera/AutoFocusCallback.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.camera; + +import android.hardware.Camera; +import android.os.Handler; +import android.os.Message; + +final class AutoFocusCallback implements Camera.AutoFocusCallback { + + private static final String TAG = AutoFocusCallback.class.getSimpleName(); + + public long AUTOFOCUS_INTERVAL_MS = 500L;//自动聚焦触发 毫秒数 +// private static final long AUTOFOCUS_INTERVAL_MS = 1500L; + + private Handler autoFocusHandler; + private int autoFocusMessage; + + void setHandler(Handler autoFocusHandler, int autoFocusMessage) { + this.autoFocusHandler = autoFocusHandler; + this.autoFocusMessage = autoFocusMessage; + } + + public void onAutoFocus(boolean success, Camera camera) { + if (autoFocusHandler != null) { + Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success); + // Simulate continuous autofocus by sending a focus request every + // AUTOFOCUS_INTERVAL_MS milliseconds. + //Log.d(TAG, "Got auto-focus callback; requesting another"); + autoFocusHandler.sendMessageDelayed(message, AUTOFOCUS_INTERVAL_MS); + autoFocusHandler = null; + } else { +// Log.d(TAG, "Got auto-focus callback, but no handler for it"); + } + } + +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/camera/CameraConfigurationManager.java b/android/src/main/java/com/reactnativecomponent/barcode/camera/CameraConfigurationManager.java new file mode 100644 index 0000000..fbf1466 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/camera/CameraConfigurationManager.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.camera; + +import android.content.Context; +import android.graphics.Point; +import android.hardware.Camera; +import android.os.Build; +import android.util.Log; +import android.view.Display; +import android.view.WindowManager; + +import java.lang.reflect.Method; +import java.util.regex.Pattern; + +final class CameraConfigurationManager { + + private static final String TAG = CameraConfigurationManager.class.getSimpleName(); + + private static final int TEN_DESIRED_ZOOM = 27; + @SuppressWarnings("unused") +private static final int DESIRED_SHARPNESS = 30; + + private static final Pattern COMMA_PATTERN = Pattern.compile(","); + + private final Context context; + private Point screenResolution; + private Point cameraResolution; + private int previewFormat; + private String previewFormatString; + + CameraConfigurationManager(Context context) { + this.context = context; + } + + /** + * Reads, one time, values from the camera that are needed by the app. + */ + void initFromCameraParameters(Camera camera) { + Camera.Parameters parameters = camera.getParameters(); + previewFormat = parameters.getPreviewFormat(); + previewFormatString = parameters.get("preview-format"); +// Log.d(TAG, "Default preview format: " + previewFormat + '/' + previewFormatString); + WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = manager.getDefaultDisplay(); + screenResolution = new Point(display.getWidth(), display.getHeight()); +// Log.d(TAG, "Screen resolution: " + screenResolution); + Point screenResolutionForCamera = new Point(); + screenResolutionForCamera.x = screenResolution.x; + screenResolutionForCamera.y = screenResolution.y; + // preview size is always something like 480*320, other 320*480 + if (screenResolution.x < screenResolution.y) { + screenResolutionForCamera.x = screenResolution.y; + screenResolutionForCamera.y = screenResolution.x; + } + cameraResolution = getCameraResolution(parameters, screenResolutionForCamera); +// Log.d(TAG, "Camera resolution: " + screenResolution); + } + + /** + * Sets the camera up to take preview images which are used for both preview and decoding. + * We detect the preview format here so that buildLuminanceSource() can build an appropriate + * LuminanceSource subclass. In the future we may want to force YUV420SP as it's the smallest, + * and the planar Y can be used for barcode scanning without a copy in some cases. + */ + void setDesiredCameraParameters(Camera camera) { + Camera.Parameters parameters = camera.getParameters(); +// Log.d(TAG, "Setting preview size: " + cameraResolution); + parameters.setPreviewSize(cameraResolution.x, cameraResolution.y); + setFlash(parameters); + setZoom(parameters); + //setSharpness(parameters); +// parameters.set("orientation", "portrait"); + if (Integer.parseInt(Build.VERSION.SDK) >= 8) { + setDisplayOrientation(camera, 90); + } + camera.setParameters(parameters); + } + + protected void setDisplayOrientation(Camera camera, int angle) { + Method downPolymorphic; + try { + downPolymorphic = camera.getClass().getMethod( + "setDisplayOrientation", new Class[] { int.class }); + if (downPolymorphic != null) + downPolymorphic.invoke(camera, new Object[] { angle }); + } catch (Exception e1) { + } + } + + Point getCameraResolution() { + return cameraResolution; + } + + Point getScreenResolution() { + return screenResolution; + } + + int getPreviewFormat() { + return previewFormat; + } + + String getPreviewFormatString() { + return previewFormatString; + } + + private static Point getCameraResolution(Camera.Parameters parameters, Point screenResolution) { + + String previewSizeValueString = parameters.get("preview-size-values"); + // saw this on Xperia + if (previewSizeValueString == null) { + previewSizeValueString = parameters.get("preview-size-value"); + } + + Point cameraResolution = null; + + if (previewSizeValueString != null) { +// Log.d(TAG, "preview-size-values parameter: " + previewSizeValueString); + cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution); + } + + if (cameraResolution == null) { + // Ensure that the camera resolution is a multiple of 8, as the screen may not be. + cameraResolution = new Point( + (screenResolution.x >> 3) << 3, + (screenResolution.y >> 3) << 3); + } + + return cameraResolution; + } + + private static Point findBestPreviewSizeValue(CharSequence previewSizeValueString, Point screenResolution) { + int bestX = 0; + int bestY = 0; + int diff = Integer.MAX_VALUE; + for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) { + + previewSize = previewSize.trim(); + int dimPosition = previewSize.indexOf('x'); + if (dimPosition < 0) { +// Log.w(TAG, "Bad preview-size: " + previewSize); + continue; + } + + int newX; + int newY; + try { + newX = Integer.parseInt(previewSize.substring(0, dimPosition)); + newY = Integer.parseInt(previewSize.substring(dimPosition + 1)); + } catch (NumberFormatException nfe) { +// Log.w(TAG, "Bad preview-size: " + previewSize); + continue; + } + + int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y); + if (newDiff == 0) { + bestX = newX; + bestY = newY; + break; + } else if (newDiff < diff) { + bestX = newX; + bestY = newY; + diff = newDiff; + } + + } + + if (bestX > 0 && bestY > 0) { + return new Point(bestX, bestY); + } + return null; + } + + private static int findBestMotZoomValue(CharSequence stringValues, int tenDesiredZoom) { + int tenBestValue = 0; + for (String stringValue : COMMA_PATTERN.split(stringValues)) { + stringValue = stringValue.trim(); + double value; + try { + value = Double.parseDouble(stringValue); + } catch (NumberFormatException nfe) { + return tenDesiredZoom; + } + int tenValue = (int) (10.0 * value); + if (Math.abs(tenDesiredZoom - value) < Math.abs(tenDesiredZoom - tenBestValue)) { + tenBestValue = tenValue; + } + } + return tenBestValue; + } + + private void setFlash(Camera.Parameters parameters) { + // FIXME: This is a hack to turn the flash off on the Samsung Galaxy. + // And this is a hack-hack to work around a different value on the Behold II + // Restrict Behold II check to Cupcake, per Samsung's advice + //if (Build.MODEL.contains("Behold II") && + // CameraManager.SDK_INT == Build.VERSION_CODES.CUPCAKE) { + if (Build.MODEL.contains("Behold II") && CameraManager.SDK_INT == 3) { // 3 = Cupcake + parameters.set("flash-value", 1); + } else { + parameters.set("flash-value", 2); + } + // This is the standard setting to turn the flash off that all devices should honor. + parameters.set("flash-mode", "off"); + } + + private void setZoom(Camera.Parameters parameters) { + + String zoomSupportedString = parameters.get("zoom-supported"); + if (zoomSupportedString != null && !Boolean.parseBoolean(zoomSupportedString)) { + return; + } + + int tenDesiredZoom = TEN_DESIRED_ZOOM; + + String maxZoomString = parameters.get("max-zoom"); + if (maxZoomString != null) { + try { + int tenMaxZoom = (int) (10.0 * Double.parseDouble(maxZoomString)); + if (tenDesiredZoom > tenMaxZoom) { + tenDesiredZoom = tenMaxZoom; + } + } catch (NumberFormatException nfe) { +// Log.w(TAG, "Bad max-zoom: " + maxZoomString); + } + } + + String takingPictureZoomMaxString = parameters.get("taking-picture-zoom-max"); + if (takingPictureZoomMaxString != null) { + try { + int tenMaxZoom = Integer.parseInt(takingPictureZoomMaxString); + if (tenDesiredZoom > tenMaxZoom) { + tenDesiredZoom = tenMaxZoom; + } + } catch (NumberFormatException nfe) { +// Log.w(TAG, "Bad taking-picture-zoom-max: " + takingPictureZoomMaxString); + } + } + + String motZoomValuesString = parameters.get("mot-zoom-values"); + if (motZoomValuesString != null) { + tenDesiredZoom = findBestMotZoomValue(motZoomValuesString, tenDesiredZoom); + } + + String motZoomStepString = parameters.get("mot-zoom-step"); + if (motZoomStepString != null) { + try { + double motZoomStep = Double.parseDouble(motZoomStepString.trim()); + int tenZoomStep = (int) (10.0 * motZoomStep); + if (tenZoomStep > 1) { + tenDesiredZoom -= tenDesiredZoom % tenZoomStep; + } + } catch (NumberFormatException nfe) { + // continue + } + } + + // Set zoom. This helps encourage the user to pull back. + // Some devices like the Behold have a zoom parameter + if (maxZoomString != null || motZoomValuesString != null) { + parameters.set("zoom", String.valueOf(tenDesiredZoom / 10.0)); + } + + // Most devices, like the Hero, appear to expose this zoom parameter. + // It takes on values like "27" which appears to mean 2.7x zoom + if (takingPictureZoomMaxString != null) { + parameters.set("taking-picture-zoom", tenDesiredZoom); + } + } + + /* + private void setSharpness(Camera.Parameters parameters) { + + int desiredSharpness = DESIRED_SHARPNESS; + + String maxSharpnessString = parameters.get("sharpness-max"); + if (maxSharpnessString != null) { + try { + int maxSharpness = Integer.parseInt(maxSharpnessString); + if (desiredSharpness > maxSharpness) { + desiredSharpness = maxSharpness; + } + } catch (NumberFormatException nfe) { + Log.w(TAG, "Bad sharpness-max: " + maxSharpnessString); + } + } + + parameters.set("sharpness", desiredSharpness); + } + */ +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/camera/CameraManager.java b/android/src/main/java/com/reactnativecomponent/barcode/camera/CameraManager.java new file mode 100644 index 0000000..ac6c7bc --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/camera/CameraManager.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.camera; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.Camera; +import android.os.Build; +import android.os.Handler; +import android.util.Log; +import android.view.SurfaceHolder; + +import java.io.IOException; + + +/** + * This object wraps the Camera service object and expects to be the only one talking to it. The + * implementation encapsulates the steps needed to take preview-sized images, which are used for + * both preview and decoding. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class CameraManager { + + private static final String TAG ="Test"; + +/* + public static int MIN_FRAME_WIDTH = 300; + public static int MIN_FRAME_HEIGHT = 300; + public static int MAX_FRAME_WIDTH = 600; + public static int MAX_FRAME_HEIGHT = 400; */ + public static int MIN_FRAME_WIDTH ; + public static int MIN_FRAME_HEIGHT ; + public static int MAX_FRAME_WIDTH ; + public static int MAX_FRAME_HEIGHT; + public static int x; + public static int y; + + private static CameraManager cameraManager; + private int focusTime=500; + + static final int SDK_INT; // Later we can use Build.VERSION.SDK_INT + static { + int sdkInt; + try { + sdkInt = Build.VERSION.SDK_INT; + } catch (NumberFormatException nfe) { + // Just to be safe + sdkInt = 10000; + } + SDK_INT = sdkInt; + } + + @SuppressWarnings("unused") +private final Context context; + private final CameraConfigurationManager configManager; + private Camera camera; + public Rect framingRect; + public Rect framingRectInPreview; + private boolean initialized; + private boolean previewing; + private final boolean useOneShotPreviewCallback; + /** + * Preview frames are delivered here, which we pass on to the registered handler. Make sure to + * clear the handler so it will only receive one message. + */ + private PreviewCallback previewCallback; + /** Autofocus callbacks arrive here, and are dispatched to the Handler which requested them. */ + private AutoFocusCallback autoFocusCallback; + + /** + * Initializes this static object with the Context of the calling Activity. + * + * @param context The Activity which wants to use the camera. + */ + public static void init(Context context) { + if (cameraManager == null) { + cameraManager = new CameraManager(context); + } + } + + /** + * Gets the CameraManager singleton instance. + * + * @return A reference to the CameraManager singleton. + */ + public static CameraManager get() { + return cameraManager; + } + + public void setFocusTime(int focusTime) { + this.focusTime = focusTime; + } + + private CameraManager(Context context) { + + this.context = context; + this.configManager = new CameraConfigurationManager(context); + + // Camera.setOneShotPreviewCallback() has a race condition in Cupcake, so we use the older + // Camera.setPreviewCallback() on 1.5 and earlier. For Donut and later, we need to use + // the more efficient one shot callback, as the older one can swamp the system and cause it + // to run out of memory. We can't use SDK_INT because it was introduced in the Donut SDK. + //useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > Build.VERSION_CODES.CUPCAKE; + useOneShotPreviewCallback = Build.VERSION.SDK_INT > 3; // 3 = Cupcake + + initPreviewCallback(); + + } + + public void initPreviewCallback() { + previewCallback = new PreviewCallback(configManager, useOneShotPreviewCallback); + autoFocusCallback = new AutoFocusCallback(); + autoFocusCallback.AUTOFOCUS_INTERVAL_MS=focusTime; + } + + /** + * Opens the camera driver and initializes the hardware parameters. + * + * @param holder The surface object which the camera will draw preview frames into. + * @throws IOException Indicates the camera driver failed to open. + */ + public void openDriver(SurfaceHolder holder) throws IOException { + if (camera == null) { + camera = Camera.open(); + if (camera == null) { + throw new IOException(); + } + camera.setPreviewDisplay(holder); + + if (!initialized) { + initialized = true; + configManager.initFromCameraParameters(camera); + } + configManager.setDesiredCameraParameters(camera); + +// SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); +// if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false)) { +// FlashlightManager.enableFlashlight(); +// } + FlashlightManager.enableFlashlight(); + } + } + + /** + * Closes the camera driver if still in use. + */ + public void closeDriver() { + if (camera != null) { + FlashlightManager.disableFlashlight(); + camera.release(); + camera = null; + } + } + + /** + * Asks the camera hardware to begin drawing preview frames to the screen. + */ + public void startPreview() { + if (camera != null && !previewing) { + camera.startPreview(); + previewing = true; + } + } + + /** + * Tells the camera to stop drawing preview frames. + */ + public void stopPreview() { + if (camera != null && previewing) { + if (!useOneShotPreviewCallback) { + camera.setPreviewCallback(null); + } + camera.stopPreview(); + previewCallback.setHandler(null, 0); + autoFocusCallback.setHandler(null, 0); + previewing = false; + } + } + + /** + * A single preview frame will be returned to the handler supplied. The data will arrive as byte[] + * in the message.obj field, with width and height encoded as message.arg1 and message.arg2, + * respectively. + * + * @param handler The handler to send the message to. + * @param message The what field of the message to be sent. + */ + public void requestPreviewFrame(Handler handler, int message) { + if (camera != null && previewing) { + previewCallback.setHandler(handler, message); + if (useOneShotPreviewCallback) { + camera.setOneShotPreviewCallback(previewCallback); + } else { + camera.setPreviewCallback(previewCallback); + } + } + } + + /** + * Asks the camera hardware to perform an autofocus. + * + * @param handler The Handler to notify when the autofocus completes. + * @param message The message to deliver. + */ + public void requestAutoFocus(Handler handler, int message) { + if (camera != null && previewing) { + autoFocusCallback.setHandler(handler, message); + //Log.d(TAG, "Requesting auto-focus callback"); + camera.autoFocus(autoFocusCallback); + + } + } + + + + /** + * Calculates the framing rect which the UI should draw to show the user where to place the + * barcode. This target helps with alignment as well as forces the user to hold the device + * far enough away to ensure the image will be in focus. + * + * @return The rectangle to draw on screen in window coordinates. + */ + public Rect getFramingRect() { + if (framingRect == null) { + Point screenResolution = configManager.getScreenResolution(); +// int x=this.x+screenResolution.x; +// int y=this.y+screenResolution.y; + int x=this.x; + int y=this.y; +// Log.i(TAG, "x: " + this.x+",y:"+this.y); + + if (camera == null) { + return null; + } + int width = x * 3 / 4; + if (width < MIN_FRAME_WIDTH) { + width = MIN_FRAME_WIDTH; + } else if (width > MAX_FRAME_WIDTH) { + width = MAX_FRAME_WIDTH; + } + int height = y * 3 / 4; + if (height < MIN_FRAME_HEIGHT) { + height = MIN_FRAME_HEIGHT; + } else if (height > MAX_FRAME_HEIGHT) { + height = MAX_FRAME_HEIGHT; + } + int leftOffset = (x - width) / 2; + int topOffset = (y - height) / 2; + // int topOffset = (screenResolution.y - height)/3; + framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height); +// Log.i(TAG, "Camera_width: " + width+",Camera_height:"+height); + } + return framingRect; + } + + /** + * Like {@link #getFramingRect} but coordinates are in terms of the preview frame, + * not UI / screen. + * 如果需要重设扫码区域必须重置framingRectInPreview = null + */ + public Rect getFramingRectInPreview() { + if (framingRectInPreview == null) { + + Rect rect = new Rect(getFramingRect()); + Point cameraResolution = configManager.getCameraResolution(); + Point screenResolution = configManager.getScreenResolution(); + //横屏 +// rect.left = rect.left * cameraResolution.x / screenResolution.x; +// rect.right = rect.right * cameraResolution.x / screenResolution.x; +// rect.top = rect.top * cameraResolution.y / screenResolution.y; +// rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y; + //竖屏 + rect.left = rect.left * cameraResolution.y / screenResolution.x; + rect.right = rect.right * cameraResolution.y / screenResolution.x; + rect.top = rect.top * cameraResolution.x / screenResolution.y; + rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y; + + + framingRectInPreview = rect; + } + return framingRectInPreview; + } + + /** + * Converts the result points from still resolution coordinates to screen coordinates. + * + * @param points The points returned by the Reader subclass through Result.getResultPoints(). + * @return An array of Points scaled to the size of the framing rect and offset appropriately + * so they can be drawn in screen coordinates. + */ + /* + public Point[] convertResultPoints(ResultPoint[] points) { + Rect frame = getFramingRectInPreview(); + int count = points.length; + Point[] output = new Point[count]; + for (int x = 0; x < count; x++) { + output[x] = new Point(); + output[x].x = frame.left + (int) (points[x].getX() + 0.5f); + output[x].y = frame.top + (int) (points[x].getY() + 0.5f); + } + return output; + } + */ + + /** + * A factory method to build the appropriate LuminanceSource object based on the format + * of the preview buffers, as described by Camera.Parameters. + * + * @param data A preview frame. + * @param width The width of the image. + * @param height The height of the image. + * @return A PlanarYUVLuminanceSource instance. + */ + public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) { + Rect rect = getFramingRectInPreview(); + int previewFormat = configManager.getPreviewFormat(); + String previewFormatString = configManager.getPreviewFormatString(); + + +// Log.i("Test","PlanarYUVLuminanceSource_width:"+width+",height:"+height+",Rect_left:"+rect.left+"Rect_top:"+rect.top+ +// "Rect_width:"+rect.width()+ "Rect_height:"+rect.height() +// ); + + switch (previewFormat) { + // This is the standard Android format which all devices are REQUIRED to support. + // In theory, it's the only one we should ever care about. + case PixelFormat.YCbCr_420_SP: + // This format has never been seen in the wild, but is compatible as we only care + // about the Y channel, so allow it. + case PixelFormat.YCbCr_422_SP: + return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, + rect.width(), rect.height()); + default: + // The Samsung Moment incorrectly uses this variant instead of the 'sp' version. + // Fortunately, it too has all the Y data up front, so we can read it. + if ("yuv420p".equals(previewFormatString)) { + return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, + rect.width(), rect.height()); + } + } + throw new IllegalArgumentException("Unsupported picture format: " + + previewFormat + '/' + previewFormatString); + } + + public Camera getCamera() { + return camera; + } + +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/camera/FlashlightManager.java b/android/src/main/java/com/reactnativecomponent/barcode/camera/FlashlightManager.java new file mode 100644 index 0000000..c1208c2 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/camera/FlashlightManager.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.camera; + +import android.os.IBinder; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * This class is used to activate the weak light on some camera phones (not flash) + * in order to illuminate surfaces for scanning. There is no official way to do this, + * but, classes which allow access to this function still exist on some devices. + * This therefore proceeds through a great deal of reflection. + * + * See + * http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-programatically/ and + * + * http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo/DroidLED.java. + * Thanks to Ryan Alford for pointing out the availability of this class. + */ +final class FlashlightManager { + + private static final String TAG = FlashlightManager.class.getSimpleName(); + + private static final Object iHardwareService; + private static final Method setFlashEnabledMethod; + static { + iHardwareService = getHardwareService(); + setFlashEnabledMethod = getSetFlashEnabledMethod(iHardwareService); + if (iHardwareService == null) { +// Log.v(TAG, "This device does supports control of a flashlight"); + } else { +// Log.v(TAG, "This device does not support control of a flashlight"); + } + } + + private FlashlightManager() { + } + + private static Object getHardwareService() { + Class serviceManagerClass = maybeForName("android.os.ServiceManager"); + if (serviceManagerClass == null) { + return null; + } + + Method getServiceMethod = maybeGetMethod(serviceManagerClass, "getService", String.class); + if (getServiceMethod == null) { + return null; + } + + Object hardwareService = invoke(getServiceMethod, null, "hardware"); + if (hardwareService == null) { + return null; + } + + Class iHardwareServiceStubClass = maybeForName("android.os.IHardwareService$Stub"); + if (iHardwareServiceStubClass == null) { + return null; + } + + Method asInterfaceMethod = maybeGetMethod(iHardwareServiceStubClass, "asInterface", IBinder.class); + if (asInterfaceMethod == null) { + return null; + } + + return invoke(asInterfaceMethod, null, hardwareService); + } + + private static Method getSetFlashEnabledMethod(Object iHardwareService) { + if (iHardwareService == null) { + return null; + } + Class proxyClass = iHardwareService.getClass(); + return maybeGetMethod(proxyClass, "setFlashlightEnabled", boolean.class); + } + + private static Class maybeForName(String name) { + try { + return Class.forName(name); + } catch (ClassNotFoundException cnfe) { + // OK + return null; + } catch (RuntimeException re) { +// Log.w(TAG, "Unexpected error while finding class " + name, re); + return null; + } + } + + private static Method maybeGetMethod(Class clazz, String name, Class... argClasses) { + try { + return clazz.getMethod(name, argClasses); + } catch (NoSuchMethodException nsme) { + // OK + return null; + } catch (RuntimeException re) { +// Log.w(TAG, "Unexpected error while finding method " + name, re); + return null; + } + } + + private static Object invoke(Method method, Object instance, Object... args) { + try { + return method.invoke(instance, args); + } catch (IllegalAccessException e) { +// Log.w(TAG, "Unexpected error while invoking " + method, e); + return null; + } catch (InvocationTargetException e) { +// Log.w(TAG, "Unexpected error while invoking " + method, e.getCause()); + return null; + } catch (RuntimeException re) { +// Log.w(TAG, "Unexpected error while invoking " + method, re); + return null; + } + } + + static void enableFlashlight() { + setFlashlight(true); + } + + static void disableFlashlight() { + setFlashlight(false); + } + + private static void setFlashlight(boolean active) { + if (iHardwareService != null) { + invoke(setFlashEnabledMethod, iHardwareService, active); + } + } + +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/camera/PlanarYUVLuminanceSource.java b/android/src/main/java/com/reactnativecomponent/barcode/camera/PlanarYUVLuminanceSource.java new file mode 100644 index 0000000..bbb1828 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/camera/PlanarYUVLuminanceSource.java @@ -0,0 +1,154 @@ +/* + * Copyright 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.camera; + +import android.graphics.Bitmap; +import android.util.Log; + +import com.google.zxing.LuminanceSource; + +/** + * This object extends LuminanceSource around an array of YUV data returned from the camera driver, + * with the option to crop to a rectangle within the full data. This can be used to exclude + * superfluous pixels around the perimeter and speed up decoding. + * + * It works for any pixel format where the Y channel is planar and appears first, including + * YCbCr_420_SP and YCbCr_422_SP. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class PlanarYUVLuminanceSource extends LuminanceSource { + private final byte[] yuvData; + private final int dataWidth; + private final int dataHeight; + private final int left; + private final int top; + + public PlanarYUVLuminanceSource(byte[] yuvData, int dataWidth, int dataHeight, int left, int top, + int width, int height) { + super(width, height); + + if (left + width > dataWidth || top + height > dataHeight) { + throw new IllegalArgumentException("Crop rectangle does not fit within image data."); + } + + this.yuvData = yuvData; + this.dataWidth = dataWidth; + this.dataHeight = dataHeight; + this.left = left; + this.top = top; + } + + @Override + public byte[] getRow(int y, byte[] row) { + if (y < 0 || y >= getHeight()) { + throw new IllegalArgumentException("Requested row is outside the image: " + y); + } + int width = getWidth(); + if (row == null || row.length < width) { + row = new byte[width]; + } + int offset = (y + top) * dataWidth + left; + + try { + System.arraycopy(yuvData, offset, row, 0, width); + } catch (ArrayIndexOutOfBoundsException e) { +// Log.e("Exception","ArrayIndexOutOfBoundsException:"+e); + e.printStackTrace(); + } + + return row; + } + + @Override + public byte[] getMatrix() { + int width = getWidth(); + int height = getHeight(); + + // If the caller asks for the entire underlying image, save the copy and give them the + // original data. The docs specifically warn that result.length must be ignored. + if (width == dataWidth && height == dataHeight) { + return yuvData; + } + + int area = width * height; + byte[] matrix = new byte[area]; + int inputOffset = top * dataWidth + left; + + // If the width matches the full width of the underlying data, perform a single copy. + if (width == dataWidth) { + System.arraycopy(yuvData, inputOffset, matrix, 0, area); + return matrix; + } + + // Otherwise copy one cropped row at a time. + byte[] yuv = yuvData; + for (int y = 0; y < height; y++) { + int outputOffset = y * width; + + try { + System.arraycopy(yuv, inputOffset, matrix, outputOffset, width); + } catch (ArrayIndexOutOfBoundsException e) { +// Log.e("Exception","ArrayIndexOutOfBoundsException:"+e); + e.printStackTrace(); + } + + inputOffset += dataWidth; + } + return matrix; + } + + @Override + public boolean isCropSupported() { + return true; + } + + public int getDataWidth() { + return dataWidth; + } + + public int getDataHeight() { + return dataHeight; + } + + public Bitmap renderCroppedGreyscaleBitmap() { + int width = getWidth(); + int height = getHeight(); + int[] pixels = new int[width * height]; + byte[] yuv = yuvData; + int inputOffset = top * dataWidth + left; + + for (int y = 0; y < height; y++) { + int outputOffset = y * width; + for (int x = 0; x < width; x++) { + int grey = 0; + try { + grey = yuv[inputOffset + x] & 0xff; + } catch (ArrayIndexOutOfBoundsException e) { +// Log.e("Exception","ArrayIndexOutOfBoundsException:"+e); + e.printStackTrace(); + } + pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101); + } + inputOffset += dataWidth; + } + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return bitmap; + } +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/camera/PreviewCallback.java b/android/src/main/java/com/reactnativecomponent/barcode/camera/PreviewCallback.java new file mode 100644 index 0000000..b5c9500 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/camera/PreviewCallback.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.camera; + +import android.graphics.Point; +import android.hardware.Camera; +import android.os.Handler; +import android.os.Message; + +final class PreviewCallback implements Camera.PreviewCallback { + + private static final String TAG = PreviewCallback.class.getSimpleName(); + + private final CameraConfigurationManager configManager; + private final boolean useOneShotPreviewCallback; + private Handler previewHandler; + private int previewMessage; + + PreviewCallback(CameraConfigurationManager configManager, boolean useOneShotPreviewCallback) { + this.configManager = configManager; + this.useOneShotPreviewCallback = useOneShotPreviewCallback; + } + + void setHandler(Handler previewHandler, int previewMessage) { + this.previewHandler = previewHandler; + this.previewMessage = previewMessage; + } + + public void onPreviewFrame(byte[] data, Camera camera) { + Point cameraResolution = configManager.getCameraResolution(); + if (!useOneShotPreviewCallback) { + camera.setPreviewCallback(null); + } + if (previewHandler != null) { + Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x, + cameraResolution.y, data); + message.sendToTarget(); + previewHandler = null; + } else { +// Log.d(TAG, "Got preview callback, but no handler for it"); + } + } + +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/decoding/CaptureActivityHandler.java b/android/src/main/java/com/reactnativecomponent/barcode/decoding/CaptureActivityHandler.java new file mode 100644 index 0000000..982f23e --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/decoding/CaptureActivityHandler.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.decoding; + +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.Result; +import com.reactnativecomponent.barcode.CaptureView; +import com.reactnativecomponent.barcode.R; +import com.reactnativecomponent.barcode.camera.CameraManager; +import com.reactnativecomponent.barcode.view.ViewfinderResultPointCallback; + + +import java.util.Vector; + +/** + * This class handles all the messaging which comprises the state machine for capture. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class CaptureActivityHandler extends Handler { + + private static final String TAG = CaptureActivityHandler.class.getSimpleName(); + + private final CaptureView captureView; + private final DecodeThread decodeThread; + private State state; + + private enum State { + PREVIEW, + SUCCESS, + DONE + } + + public CaptureActivityHandler(CaptureView captureView, Vector decodeFormats, + String characterSet) { + this.captureView = captureView; + decodeThread = new DecodeThread(captureView, decodeFormats, characterSet, + new ViewfinderResultPointCallback(captureView.getViewfinderView())); + decodeThread.start(); + state = State.SUCCESS; + + // Start ourselves capturing previews and decoding. + CameraManager.get().startPreview(); + restartPreviewAndDecode(); + } + + @Override + public void handleMessage(Message message) { + if (message.what == R.id.auto_focus) {// case R.id.auto_focus: + //Log.d(TAG, "Got auto-focus message"); + // When one auto focus pass finishes, start another. This is the closest thing to + // continuous AF. It does seem to hunt a bit, but I'm not sure what else to do. + if (state == State.PREVIEW) { + CameraManager.get().requestAutoFocus(this, R.id.auto_focus); + } + + } else if (message.what == R.id.restart_preview) {// case R.id.restart_preview: +// Log.d(TAG, "Got restart preview message"); + restartPreviewAndDecode(); + + } else if (message.what == R.id.decode_succeeded) {// case R.id.decode_succeeded: +// Log.d(TAG, "Got decode succeeded message"); + + state = State.SUCCESS; + Bundle bundle = message.getData(); + /* Bitmap barcode = bundle == null ? null : + (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);*/ + captureView.handleDecode((Result) message.obj, null); + +// case R.id.decode_failed: + } else if (message.what == R.id.decode_failed) {/** + *扫码失败继续执行线程 + */ + + // We're decoding as fast as possible, so when one decode fails, start another. + state = State.PREVIEW; + CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode); + + } else if (message.what == R.id.return_scan_result) {// Log.d(TAG, "Got return scan result message"); + captureView.ShowResult((Intent) message.obj); + + + /* case R.id.launch_product_query: + Log.d(TAG, "Got product query message"); + String url = (String) message.obj; + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + activity.startActivity(intent); + break;*/ + } + } + + + + + public void quitSynchronously() { + if (state != State.DONE) { + state = State.DONE; + CameraManager.get().stopPreview(); + Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit); + quit.sendToTarget(); + decodeThread.flag=false; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + decodeThread.interrupt(); + try { + decodeThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + // continue + } + + // Be absolutely sure we don't send any queued up messages + removeMessages(R.id.decode_succeeded); + removeMessages(R.id.decode_failed); + } + } + + public void restartPreviewAndDecode() { + if (state == State.SUCCESS) { + state = State.PREVIEW; + decodeThread.flag=true; + CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode); + CameraManager.get().requestAutoFocus(this, R.id.auto_focus); + captureView.drawViewfinder(); + } + } + + +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeFormatManager.java b/android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeFormatManager.java new file mode 100644 index 0000000..821d788 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeFormatManager.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.decoding; + +import android.content.Intent; +import android.net.Uri; + +import com.google.zxing.BarcodeFormat; + +import java.util.Arrays; +import java.util.List; +import java.util.Vector; +import java.util.regex.Pattern; + +public class DecodeFormatManager { + + private static final Pattern COMMA_PATTERN = Pattern.compile(","); + + static final Vector PRODUCT_FORMATS; + static final Vector ONE_D_FORMATS; + static final Vector QR_CODE_FORMATS; + static final Vector DATA_MATRIX_FORMATS; + static { + PRODUCT_FORMATS = new Vector(5); + PRODUCT_FORMATS.add(BarcodeFormat.UPC_A); + PRODUCT_FORMATS.add(BarcodeFormat.UPC_E); + PRODUCT_FORMATS.add(BarcodeFormat.EAN_13); + PRODUCT_FORMATS.add(BarcodeFormat.EAN_8); + PRODUCT_FORMATS.add(BarcodeFormat.RSS_14); + ONE_D_FORMATS = new Vector(PRODUCT_FORMATS.size() + 4); + ONE_D_FORMATS.addAll(PRODUCT_FORMATS); + ONE_D_FORMATS.add(BarcodeFormat.CODE_39); + ONE_D_FORMATS.add(BarcodeFormat.CODE_93); + ONE_D_FORMATS.add(BarcodeFormat.CODE_128); + ONE_D_FORMATS.add(BarcodeFormat.ITF); + QR_CODE_FORMATS = new Vector(1); + QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE); + DATA_MATRIX_FORMATS = new Vector(1); + DATA_MATRIX_FORMATS.add(BarcodeFormat.DATA_MATRIX); + } + + private DecodeFormatManager() {} + + static Vector parseDecodeFormats(Intent intent) { + List scanFormats = null; + String scanFormatsString = intent.getStringExtra(Intents.Scan.SCAN_FORMATS); + if (scanFormatsString != null) { + scanFormats = Arrays.asList(COMMA_PATTERN.split(scanFormatsString)); + } + return parseDecodeFormats(scanFormats, intent.getStringExtra(Intents.Scan.MODE)); + } + + static Vector parseDecodeFormats(Uri inputUri) { + List formats = inputUri.getQueryParameters(Intents.Scan.SCAN_FORMATS); + if (formats != null && formats.size() == 1 && formats.get(0) != null){ + formats = Arrays.asList(COMMA_PATTERN.split(formats.get(0))); + } + return parseDecodeFormats(formats, inputUri.getQueryParameter(Intents.Scan.MODE)); + } + + private static Vector parseDecodeFormats(Iterable scanFormats, + String decodeMode) { + if (scanFormats != null) { + Vector formats = new Vector(); + try { + for (String format : scanFormats) { + formats.add(BarcodeFormat.valueOf(format)); + } + return formats; + } catch (IllegalArgumentException iae) { + // ignore it then + } + } + if (decodeMode != null) { + if (Intents.Scan.PRODUCT_MODE.equals(decodeMode)) { + return PRODUCT_FORMATS; + } + if (Intents.Scan.QR_CODE_MODE.equals(decodeMode)) { + return QR_CODE_FORMATS; + } + if (Intents.Scan.DATA_MATRIX_MODE.equals(decodeMode)) { + return DATA_MATRIX_FORMATS; + } + if (Intents.Scan.ONE_D_MODE.equals(decodeMode)) { + return ONE_D_FORMATS; + } + } + return null; + } + +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeHandler.java b/android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeHandler.java new file mode 100644 index 0000000..cd74e29 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeHandler.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.decoding; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import com.google.zxing.BinaryBitmap; +import com.google.zxing.DecodeHintType; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; +import com.google.zxing.common.HybridBinarizer; +import com.reactnativecomponent.barcode.CaptureView; +import com.reactnativecomponent.barcode.R; +import com.reactnativecomponent.barcode.camera.CameraManager; +import com.reactnativecomponent.barcode.camera.PlanarYUVLuminanceSource; + + +import java.util.Hashtable; + +final class +DecodeHandler extends Handler { + +// private static final String TAG = DecodeHandler.class.getSimpleName(); + private static final String TAG ="Test"; + + + private final CaptureView captureView; + private final MultiFormatReader multiFormatReader; + + DecodeHandler(CaptureView captureView, Hashtable hints) { + + multiFormatReader = new MultiFormatReader(); + multiFormatReader.setHints(hints); + this.captureView = captureView; + } + + @Override + public void handleMessage(Message message) { + int id=message.what; + if(id== R.id.decode) { + if(captureView.decodeFlag) { + decode((byte[]) message.obj, message.arg1, message.arg2); + } + }else if(id==R.id.quit) { +// Log.i(TAG, "decode quit"); + Looper.myLooper().quit(); + } + + } + + + + /** + * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency, + * reuse the same reader objects from one decode to the next. + * + * @param data The YUV preview frame. + * @param width The width of the preview frame. + * @param height The height of the preview frame. + */ + private void decode(byte[] data, int width, int height) { + // Log.v("DecodeHandler", data.length + ""); +// { +// StringBuilder tmp; +// // if the data width is 480 +// { +// int x = 40; +// tmp = new StringBuilder(); +// for (int y = 0; y < height; y += 20) +// tmp.append(Integer.toHexString(data[y * width + x]) + "_"); +// Log.v("DecodeHandler", tmp.toString()); +// } +// // if the data width is 320 +// { +// int x = 40; +// tmp = new StringBuilder(); +// for (int y = 0; y < width; y += 20) +// tmp.append(Integer.toHexString(data[y * height + x]) + "_"); +// Log.v("DecodeHandler", tmp.toString()); +// } +// } + // rotate the data 90 degree clockwise. + // note that on a HTC G2, data length is 230400, but 480*320=153600, + // which means u and v channels are not touched. + // from the rotated data you will get a wrong image, whatever, only Y + // channel is used. + // check PlanarYUVLuminanceSource for more. + // it says: "It works for any pixel format where + // the Y channel is planar and appears first, including + // YCbCr_420_SP and YCbCr_422_SP." + byte[] rotatedData = new byte[data.length]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) + rotatedData[x * height + height - y - 1] = data[x + y * width]; + } + + long start = System.currentTimeMillis(); + Result rawResult = null; +// PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(data, width, height); +// PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(data, height, width); + // switch width and height +// Log.i("Test","DecodeHandler_height:"+height+",width:"+width); + PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(rotatedData, height, width); + + BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + try { + rawResult = multiFormatReader.decodeWithState(bitmap); + } catch (ReaderException re) { + // continue + } finally { + multiFormatReader.reset(); + } + + if (rawResult != null) { + long end = System.currentTimeMillis(); +// Log.d(TAG, "Found barcode (" + (end - start) + " ms):\n" + rawResult.toString()); + Message message = Message.obtain(captureView.getHandler(), R.id.decode_succeeded, rawResult); + Bundle bundle = new Bundle(); + bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap()); + message.setData(bundle); + //Log.d(TAG, "Sending decode succeeded message..."); + message.sendToTarget(); + } else { + Message message = Message.obtain(captureView.getHandler(), R.id.decode_failed); + message.sendToTarget(); + } + } + +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeThread.java b/android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeThread.java new file mode 100644 index 0000000..bd34b11 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeThread.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.decoding; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.DecodeHintType; +import com.google.zxing.ResultPointCallback; +import com.reactnativecomponent.barcode.CaptureView; +import com.reactnativecomponent.barcode.R; + + +import java.util.Hashtable; +import java.util.Vector; +import java.util.concurrent.CountDownLatch; + +/** + * This thread does all the heavy lifting of decoding the images. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +final class DecodeThread extends Thread { + + public static final String BARCODE_BITMAP = "barcode_bitmap"; + + private final CaptureView captureView; + private final Hashtable hints; + private DecodeHandler handler; + private final CountDownLatch handlerInitLatch;//到计数的锁 + public boolean flag=true; + + DecodeThread(CaptureView captureView, + Vector decodeFormats, + String characterSet, + ResultPointCallback resultPointCallback) { +// Log.i("Test", "DecodeThread create"); + this.captureView = captureView; + handlerInitLatch = new CountDownLatch(1);//从1开始到计数 + + hints = new Hashtable(3); + +// // The prefs can't change while the thread is running, so pick them up once here. +// if (decodeFormats == null || decodeFormats.isEmpty()) { +// SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); +// decodeFormats = new Vector(); +// if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D, true)) { +// decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS); +// } +// if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_QR, true)) { +// decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS); +// } +// if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_DATA_MATRIX, true)) { +// decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS); +// } +// } + if (decodeFormats == null || decodeFormats.isEmpty()) { + decodeFormats = new Vector(); + decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS); + decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS); + decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS); + + } + + hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats); + + if (characterSet != null) { + hints.put(DecodeHintType.CHARACTER_SET, characterSet); + } + + hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback); + } + + Handler getHandler() { + try { + handlerInitLatch.await();//阻塞先等handler被初始化了才能返回结果。改计数锁即等countdown-->0。 + } catch (InterruptedException ie) { + // continue? + } + return handler; + } + + @Override + public void run() { + + Looper.prepare(); + handler = new DecodeHandler(captureView, hints); + handlerInitLatch.countDown();//启动到计数,countdown-1 变成0; +// Log.i("Test","The worker thread id = " + Thread.currentThread().getId()); //判断线程ID + Looper.loop(); + + } + + + +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeUtil.java b/android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeUtil.java new file mode 100644 index 0000000..f254367 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/decoding/DecodeUtil.java @@ -0,0 +1,168 @@ +package com.reactnativecomponent.barcode.decoding; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.DecodeHintType; +import com.google.zxing.PlanarYUVLuminanceSource; +import com.google.zxing.Result; +import com.google.zxing.common.HybridBinarizer; +import com.google.zxing.qrcode.QRCodeReader; + +import java.lang.ref.WeakReference; +import java.util.Hashtable; + + +public class DecodeUtil { + + public static Bitmap convertToBitmap(String path) { + BitmapFactory.Options opts = new BitmapFactory.Options(); + // 设置为ture只获取图片大小 + opts.inJustDecodeBounds = true; + opts.inPreferredConfig = Bitmap.Config.RGB_565; + // 返回为空 + BitmapFactory.decodeFile(path, opts); + int width = opts.outWidth; + int height = opts.outHeight; + /* float scaleWidth = 0.f, scaleHeight = 0.f; + if (width > w || height > h){ + // 缩放 + scaleWidth = ((float) width) / w; + scaleHeight = ((float) height) / h; + }*/ + opts.inJustDecodeBounds = false; +// float scale = Math.max(scaleWidth, scaleHeight); + float scale = 1.5f; + opts.inSampleSize = (int)scale; + WeakReference weak = new WeakReference(BitmapFactory.decodeFile(path, opts)); + return Bitmap.createScaledBitmap(weak.get(), (int)(width/scale), (int)(height/scale), true); + } + + public static String getStringFromQRCode(String path) { + String httpString = null; + + Bitmap bmp = convertToBitmap(path); + byte[] data = getYUV420sp(bmp.getWidth(), bmp.getHeight(), bmp); + // 处理 + try { + Hashtable hints = new Hashtable(); +// hints.put(DecodeHintType.CHARACTER_SET, "utf-8"); + hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); + hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE); + PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(data, + bmp.getWidth(), + bmp.getHeight(), + 0, 0, + bmp.getWidth(), + bmp.getHeight(), + false); + BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source)); + QRCodeReader reader2= new QRCodeReader(); + Result result = reader2.decode(bitmap1, hints); + + httpString = result.getText(); + } catch (Exception e) { + e.printStackTrace(); + } + + bmp.recycle(); + bmp = null; + + return httpString; + } + + + /** + * YUV420sp + * + * @param inputWidth + * @param inputHeight + * @param scaled + * @return + */ + public static byte[] getYUV420sp(int inputWidth, int inputHeight, + Bitmap scaled) { + int[] argb = new int[inputWidth * inputHeight]; + + scaled.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight); + + byte[] yuv = new byte[inputWidth * inputHeight * 3 / 2]; + + encodeYUV420SP(yuv, argb, inputWidth, inputHeight); + + scaled.recycle(); + + return yuv; + } + + /** + * RGB转YUV420sp + * + * @param yuv420sp + * inputWidth * inputHeight * 3 / 2 + * @param argb + * inputWidth * inputHeight + * @param width + * @param height + */ + private static void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, + int height) { + // 帧图片的像素大小 + final int frameSize = width * height; + // ---YUV数据--- + int Y, U, V; + // Y的index从0开始 + int yIndex = 0; + // UV的index从frameSize开始 + int uvIndex = frameSize; + + // ---颜色数据--- +// int a, R, G, B; + int R, G, B; + // + int argbIndex = 0; + // + + // ---循环所有像素点,RGB转YUV--- + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + + // a is not used obviously +// a = (argb[argbIndex] & 0xff000000) >> 24; + R = (argb[argbIndex] & 0xff0000) >> 16; + G = (argb[argbIndex] & 0xff00) >> 8; + B = (argb[argbIndex] & 0xff); + // + argbIndex++; + + // well known RGB to YUV algorithm + Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16; + U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128; + V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128; + + // + Y = Math.max(0, Math.min(Y, 255)); + U = Math.max(0, Math.min(U, 255)); + V = Math.max(0, Math.min(V, 255)); + + // NV21 has a plane of Y and interleaved planes of VU each + // sampled by a factor of 2 + // meaning for every 4 Y pixels there are 1 V and 1 U. Note the + // sampling is every other + // pixel AND every other scanline. + // ---Y--- + yuv420sp[yIndex++] = (byte) Y; + // ---UV--- + if ((j % 2 == 0) && (i % 2 == 0)) { + // + yuv420sp[uvIndex++] = (byte) V; + // + yuv420sp[uvIndex++] = (byte) U; + } + } + } + } +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/decoding/FinishListener.java b/android/src/main/java/com/reactnativecomponent/barcode/decoding/FinishListener.java new file mode 100644 index 0000000..db89d3d --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/decoding/FinishListener.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.decoding; + +import android.app.Activity; +import android.content.DialogInterface; + +/** + * Simple listener used to exit the app in a few cases. + * + * @author Sean Owen + */ +public final class FinishListener + implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener, Runnable { + + private final Activity activityToFinish; + + public FinishListener(Activity activityToFinish) { + this.activityToFinish = activityToFinish; + } + + public void onCancel(DialogInterface dialogInterface) { + run(); + } + + public void onClick(DialogInterface dialogInterface, int i) { + run(); + } + + public void run() { + activityToFinish.finish(); + } + +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/decoding/InactivityTimer.java b/android/src/main/java/com/reactnativecomponent/barcode/decoding/InactivityTimer.java new file mode 100644 index 0000000..40e5948 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/decoding/InactivityTimer.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.decoding; + +import android.app.Activity; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +/** + * Finishes an activity after a period of inactivity. + * + */ +public final class InactivityTimer { + + private static final int INACTIVITY_DELAY_SECONDS = 5 * 60; + + private final ScheduledExecutorService inactivityTimer = + Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory()); + private final Activity activity; + private ScheduledFuture inactivityFuture = null; + + public InactivityTimer(Activity activity) { + this.activity = activity; + onActivity(); + } + + public void onActivity() { + cancel(); + inactivityFuture = inactivityTimer.schedule(new FinishListener(activity), + INACTIVITY_DELAY_SECONDS, + TimeUnit.SECONDS); + } + + private void cancel() { + if (inactivityFuture != null) { + inactivityFuture.cancel(true); + inactivityFuture = null; + } + } + + public void shutdown() { + cancel(); + inactivityTimer.shutdown(); + } + + private static final class DaemonThreadFactory implements ThreadFactory { + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(runnable); + thread.setDaemon(true); + return thread; + } + } + +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/decoding/Intents.java b/android/src/main/java/com/reactnativecomponent/barcode/decoding/Intents.java new file mode 100644 index 0000000..6c3495a --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/decoding/Intents.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.decoding; + +/** + * This class provides the constants to use when sending an Intent to Barcode Scanner. + * These strings are effectively API and cannot be changed. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class Intents { + private Intents() { + } + + public static final class Scan { + /** + * Send this intent to open the Barcodes app in scanning mode, find a barcode, and return + * the results. + */ + public static final String ACTION = "com.google.zxing.client.android.SCAN"; + + /** + * By default, sending Scan.ACTION will decode all barcodes that we understand. However it + * may be useful to limit scanning to certain formats. Use Intent.putExtra(MODE, value) with + * one of the values below ({@link #PRODUCT_MODE}, {@link #ONE_D_MODE}, {@link #QR_CODE_MODE}). + * Optional. + * + * Setting this is effectively shorthnad for setting explicit formats with {@link #SCAN_FORMATS}. + * It is overridden by that setting. + */ + public static final String MODE = "SCAN_MODE"; + + /** + * Comma-separated list of formats to scan for. The values must match the names of + * {@link com.google.zxing.BarcodeFormat}s, such as {@link com.google.zxing.BarcodeFormat#EAN_13}. + * Example: "EAN_13,EAN_8,QR_CODE" + * + * This overrides {@link #MODE}. + */ + public static final String SCAN_FORMATS = "SCAN_FORMATS"; + + /** + * @see com.google.zxing.DecodeHintType#CHARACTER_SET + */ + public static final String CHARACTER_SET = "CHARACTER_SET"; + + /** + * Decode only UPC and EAN barcodes. This is the right choice for shopping apps which get + * prices, reviews, etc. for products. + */ + public static final String PRODUCT_MODE = "PRODUCT_MODE"; + + /** + * Decode only 1D barcodes (currently UPC, EAN, Code 39, and Code 128). + */ + public static final String ONE_D_MODE = "ONE_D_MODE"; + + /** + * Decode only QR codes. + */ + public static final String QR_CODE_MODE = "QR_CODE_MODE"; + + /** + * Decode only Data Matrix codes. + */ + public static final String DATA_MATRIX_MODE = "DATA_MATRIX_MODE"; + + /** + * If a barcode is found, Barcodes returns RESULT_OK to onActivityResult() of the app which + * requested the scan via startSubActivity(). The barcodes contents can be retrieved with + * intent.getStringExtra(RESULT). If the user presses Back, the result code will be + * RESULT_CANCELED. + */ + public static final String RESULT = "SCAN_RESULT"; + + /** + * Call intent.getStringExtra(RESULT_FORMAT) to determine which barcode format was found. + * See Contents.Format for possible values. + */ + public static final String RESULT_FORMAT = "SCAN_RESULT_FORMAT"; + + /** + * Setting this to false will not save scanned codes in the history. + */ + public static final String SAVE_HISTORY = "SAVE_HISTORY"; + + private Scan() { + } + } + + public static final class Encode { + /** + * Send this intent to encode a piece of data as a QR code and display it full screen, so + * that another person can scan the barcode from your screen. + */ + public static final String ACTION = "com.google.zxing.client.android.ENCODE"; + + /** + * The data to encode. Use Intent.putExtra(DATA, data) where data is either a String or a + * Bundle, depending on the type and format specified. Non-QR Code formats should + * just use a String here. For QR Code, see Contents for details. + */ + public static final String DATA = "ENCODE_DATA"; + + /** + * The type of data being supplied if the format is QR Code. Use + * Intent.putExtra(TYPE, type) with one of Contents.Type. + */ + public static final String TYPE = "ENCODE_TYPE"; + + /** + * The barcode format to be displayed. If this isn't specified or is blank, + * it defaults to QR Code. Use Intent.putExtra(FORMAT, format), where + * format is one of Contents.Format. + */ + public static final String FORMAT = "ENCODE_FORMAT"; + + private Encode() { + } + } + + public static final class SearchBookContents { + /** + * Use Google Book Search to search the contents of the book provided. + */ + public static final String ACTION = "com.google.zxing.client.android.SEARCH_BOOK_CONTENTS"; + + /** + * The book to search, identified by ISBN number. + */ + public static final String ISBN = "ISBN"; + + /** + * An optional field which is the text to search for. + */ + public static final String QUERY = "QUERY"; + + private SearchBookContents() { + } + } + + public static final class WifiConnect { + /** + * Internal intent used to trigger connection to a wi-fi network. + */ + public static final String ACTION = "com.google.zxing.client.android.WIFI_CONNECT"; + + /** + * The network to connect to, all the configuration provided here. + */ + public static final String SSID = "SSID"; + + /** + * The network to connect to, all the configuration provided here. + */ + public static final String TYPE = "TYPE"; + + /** + * The network to connect to, all the configuration provided here. + */ + public static final String PASSWORD = "PASSWORD"; + + private WifiConnect() { + } + } + + + public static final class Share { + /** + * Give the user a choice of items to encode as a barcode, then render it as a QR Code and + * display onscreen for a friend to scan with their phone. + */ + public static final String ACTION = "com.google.zxing.client.android.SHARE"; + + private Share() { + } + } +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/view/LinearGradientView.java b/android/src/main/java/com/reactnativecomponent/barcode/view/LinearGradientView.java new file mode 100644 index 0000000..0f072aa --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/view/LinearGradientView.java @@ -0,0 +1,186 @@ +package com.reactnativecomponent.barcode.view; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Shader; +import android.graphics.drawable.GradientDrawable; +import android.view.View; +import android.view.ViewGroup; + + +public class LinearGradientView extends View { + +// /* *//** +// * 中间滑动线的最顶端位置 +// *//* +// private int slideTop; +// +// *//** +// * 中间那条线每次刷新移动的距离 +// *//* +// private static int SPEEN_DISTANCE = 3; +// +// *//** +// * 中间滑动线的最底端位置 +// *//* +// private int slideBottom; +// +// *//** +// * 四个蓝色边角对应的宽度 +// *//* +// public int CORNER_WIDTH = 3;*/ + + /** + * 框架颜色 + */ + public int frameColor=Color.GREEN; + + /** + * 扫描线渐变色中间色 + */ + public int frameBaseColor; + + /** + * 线的厚度 + */ + public int size; + /** + * 线的宽度 + */ + public int width; + + Activity activity; + +// /** +// * 扫描框中的中间线的宽度 +// */ +// private static final int MIDDLE_LINE_WIDTH = 3; + + +// public int top,left,right; + + +// private Paint paintLine; + + + public LinearGradientView(Context context,Activity activity) { + super(context); + this.activity=activity; + +// paintLine=new Paint(); + } + + public void setFrameColor(int frameColor) { + + this.frameColor = frameColor; + + this.frameBaseColor = reSetColor(frameColor); + //渐变色drawable + int[] mColors = new int[]{Color.TRANSPARENT, frameBaseColor, frameColor, frameColor, frameColor, frameColor, frameColor, frameBaseColor, Color.TRANSPARENT}; + + GradientDrawable drawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, mColors); + drawable.setGradientType(GradientDrawable.LINEAR_GRADIENT); +// drawable.setCornerRadius(15); +// drawable.setStroke(10,-1); + setBackground(drawable); + } + + @Override + protected void onAttachedToWindow() { + ViewGroup.LayoutParams params= getLayoutParams(); + if(size>1) { + params.height = size; + } + if(width>1){ + params.width=width; + } + setLayoutParams(params); + + super.onAttachedToWindow(); + + } + + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + + super.onWindowFocusChanged(hasWindowFocus); + + + + } + + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + final ViewGroup.LayoutParams params= getLayoutParams(); + int height = getHeight(); + if (height < 3) { + params.height = 3; + } else if (height > 10) { + params.height = 10; + } + + activity.runOnUiThread(new Runnable() { + public void run() { + LinearGradientView.this.setLayoutParams(params); + } + }); + + + super.onLayout(changed, left, top, right, bottom); + + } + + + + +/* @Override + protected void onDraw(Canvas canvas) { + paintLine.setColor(frameColor); + + + slideTop += SPEEN_DISTANCE; + if (slideTop >= slideBottom) { + slideTop = top + CORNER_WIDTH; + } + //自己画 + paintLine.setColor(frameColor); + +// 0x8800FF00 + Shader mShader = new LinearGradient(left + CORNER_WIDTH, slideTop, right + - CORNER_WIDTH, slideTop + MIDDLE_LINE_WIDTH,new int[] {Color.TRANSPARENT,frameBaseColor,frameColor,frameColor,frameColor,frameColor,frameColor,frameBaseColor,Color.TRANSPARENT},null, Shader.TileMode.CLAMP); +//新建一个线性渐变,前两个参数是渐变开始的点坐标,第三四个参数是渐变结束的点的坐标。 +// 连接这2个点就拉出一条渐变线了,玩过PS的都懂。然后那个数组是渐变的颜色。下一个参数是渐变颜色的分布,如果为空,每个颜色就是均匀分布的。 +// 最后是模式,这里设置的是Clamp渐变 + paintLine.setShader(mShader); + canvas.drawRect(left + CORNER_WIDTH, slideTop, right + - CORNER_WIDTH, slideTop + MIDDLE_LINE_WIDTH, paintLine); + + }*/ + + /** + * 中间色颜色换算 + */ + public int reSetColor(int startInt) { + + int startA = (startInt >> 24) & 0xff; + int startR = (startInt >> 16) & 0xff; + int startG = (startInt >> 8) & 0xff; + int startB = startInt & 0xff; + + int endA = startA / 2;// 转化后可以设置 2:半透明度 4:4分之一透明度 + + + return ((startA + (endA - startA)) << 24) + | (startR << 16) + | (startG << 8) + | (startB); + + + } +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/view/VerticalSeekBar.java b/android/src/main/java/com/reactnativecomponent/barcode/view/VerticalSeekBar.java new file mode 100644 index 0000000..fc4c901 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/view/VerticalSeekBar.java @@ -0,0 +1,179 @@ +package com.reactnativecomponent.barcode.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.AbsSeekBar; + +public class VerticalSeekBar extends AbsSeekBar { + + private Drawable mThumb; + + public interface OnSeekBarChangeListener { + void onProgressChanged(VerticalSeekBar VerticalSeekBar, int progress, boolean fromUser); + + void onStartTrackingTouch(VerticalSeekBar VerticalSeekBar); + + void onStopTrackingTouch(VerticalSeekBar VerticalSeekBar); + } + + private OnSeekBarChangeListener mOnSeekBarChangeListener; + + public VerticalSeekBar(Context context) { + this(context, null); + } + + public VerticalSeekBar(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.seekBarStyle); + } + + public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) { + mOnSeekBarChangeListener = l; + } + + void onStartTrackingTouch() { + if (mOnSeekBarChangeListener != null) { + mOnSeekBarChangeListener.onStartTrackingTouch(this); + } + } + + void onStopTrackingTouch() { + if (mOnSeekBarChangeListener != null) { + mOnSeekBarChangeListener.onStopTrackingTouch(this); + } + } + + void onProgressRefresh(float scale, boolean fromUser) { + Drawable thumb = mThumb; + if (thumb != null) { + setThumbPos(getHeight(), thumb, scale, Integer.MIN_VALUE); + invalidate(); + } + if (mOnSeekBarChangeListener != null) { + mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), isPressed()); + } + } + + private void setThumbPos(int w, Drawable thumb, float scale, int gap) { + int available = w - getPaddingLeft() - getPaddingRight(); + int thumbWidth = thumb.getIntrinsicWidth(); + int thumbHeight = thumb.getIntrinsicHeight(); + available -= thumbWidth; + + // The extra space for the thumb to move on the track + available += getThumbOffset() * 2; + + int thumbPos = (int) (scale * available); + + int topBound, bottomBound; + if (gap == Integer.MIN_VALUE) { + Rect oldBounds = thumb.getBounds(); + topBound = oldBounds.top; + bottomBound = oldBounds.bottom; + } else { + topBound = gap; + bottomBound = gap + thumbHeight; + } + thumb.setBounds(thumbPos, topBound, thumbPos + thumbWidth, bottomBound); + } + + @Override + protected void onDraw(Canvas c) { + c.rotate(-90);// 反转90度,将水平SeekBar竖起来 + c.translate(-getHeight(), 0);// 将经过旋转后得到的VerticalSeekBar移到正确的位置,注意经旋转后宽高值互换 + super.onDraw(c); + } + + @Override + protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(heightMeasureSpec, widthMeasureSpec); + setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());// 宽高值互换 + } + + @Override + public void setThumb(Drawable thumb) { + mThumb = thumb; + super.setThumb(thumb); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(h, w, oldw, oldh);// 宽高值互换 + } + + // 与源码完全相同,仅为调用宽高值互换处理的onStartTrackingTouch()方法 + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isEnabled()) { + return false; + } + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + setPressed(true); + onStartTrackingTouch(); + trackTouchEvent(event); + break; + } + + case MotionEvent.ACTION_MOVE: { + trackTouchEvent(event); + attemptClaimDrag(); + break; + } + + case MotionEvent.ACTION_UP: { + trackTouchEvent(event); + onStopTrackingTouch(); + setPressed(false); + // ProgressBar doesn't know to repaint the thumb drawable + // in its inactive state when the touch stops (because the + // value has not apparently changed) + invalidate(); + break; + } + + case MotionEvent.ACTION_CANCEL: { + onStopTrackingTouch(); + setPressed(false); + invalidate(); // see above explanation + break; + } + + default: + break; + } + return true; + } + + // 宽高值互换处理 + private void trackTouchEvent(MotionEvent event) { + final int height = getHeight(); + final int available = height - getPaddingBottom() - getPaddingTop(); + int Y = (int) event.getY(); + float scale; + float progress = 0; + if (Y > height - getPaddingBottom()) { + scale = 0.0f; + } else if (Y < getPaddingTop()) { + scale = 1.0f; + } else { + scale = (float) (height - getPaddingBottom() - Y) / (float) available; + } + final int max = getMax(); + progress = scale * max; + setProgress((int) progress); + } + + private void attemptClaimDrag() { + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(true); + } + } +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/view/ViewfinderResultPointCallback.java b/android/src/main/java/com/reactnativecomponent/barcode/view/ViewfinderResultPointCallback.java new file mode 100644 index 0000000..2cd4a55 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/view/ViewfinderResultPointCallback.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.view; + +import com.google.zxing.ResultPoint; +import com.google.zxing.ResultPointCallback; + +public final class ViewfinderResultPointCallback implements ResultPointCallback { + + private final ViewfinderView viewfinderView; + + public ViewfinderResultPointCallback(ViewfinderView viewfinderView) { + this.viewfinderView = viewfinderView; + } + + public void foundPossibleResultPoint(ResultPoint point) { + viewfinderView.addPossibleResultPoint(point); + } + +} diff --git a/android/src/main/java/com/reactnativecomponent/barcode/view/ViewfinderView.java b/android/src/main/java/com/reactnativecomponent/barcode/view/ViewfinderView.java new file mode 100644 index 0000000..54b0a31 --- /dev/null +++ b/android/src/main/java/com/reactnativecomponent/barcode/view/ViewfinderView.java @@ -0,0 +1,402 @@ +/* + * Copyright (C) 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.reactnativecomponent.barcode.view; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Shader; +import android.graphics.Typeface; +import android.util.Log; +import android.view.View; + +import com.google.zxing.ResultPoint; +import com.reactnativecomponent.barcode.R; +import com.reactnativecomponent.barcode.camera.CameraManager; + + +import java.util.Collection; +import java.util.HashSet; + +/** + * This view is overlaid on top of the camera preview. It adds the viewfinder rectangle and partial + * transparency outside it, as well as the laser scanner animation and result points. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class ViewfinderView extends View +{ + /**扫描页面透明度*/ + private static final int[] SCANNER_ALPHA = { 0, 64, 128, 192, 255, 192, + 128, 64 }; + /**动画延迟*/ + private static final long ANIMATION_DELAY = 10L; + private static final int OPAQUE = 0xFF;//不透明 + + /** + * 四个蓝色边角对应的长度 + */ + private int ScreenRate; + + /** + * 四个蓝色边角对应的宽度 + */ + public int CORNER_WIDTH = 3; + /** + * 扫描框中的中间线的宽度 + */ + private int MIDDLE_LINE_WIDTH = 3; + + /** + * 扫描框中的中间线的与扫描框左右的间隙 + */ + private static final int MIDDLE_LINE_PADDING = 5; + + /** + * 中间那条线每次刷新移动的距离 + */ + private static int SPEEN_DISTANCE = 3; + + /** + * 手机的屏幕密度 + */ + private static float density; + /** + * 字体大小 + */ + private static final int TEXT_SIZE = 16; + + public String ShowText; + /** + * 字体距离扫描框下面的距离 + */ + private static final int TEXT_PADDING_TOP = 30; + + private final Paint paint; + private final Paint paintLine; + /**返回的照片*/ + private Bitmap resultBitmap; + /**遮盖物颜色*/ + private final int maskColor; + /**结果颜色*/ + private final int resultColor; + /**框架颜色*/ + public int frameColor; + /** + * 扫描线渐变色中间色 + */ + public int frameBaseColor; + /**扫描线颜色*/ + private final int laserColor; + /**结果点的颜色*/ + private final int resultPointColor; + private int scannerAlpha;//扫描透明度 + /**可能的结果点数*/ + private Collection possibleResultPoints; + /**最后的结果点数*/ + private Collection lastPossibleResultPoints; + /** + * 是否画中间线 + */ + public boolean drawLine = false; + + /** + * 中间滑动线的最顶端位置 + */ + private int slideTop; + + /** + * 中间滑动线的最底端位置 + */ + private int slideBottom; + private boolean isFirst; + + /** + *s扫码横线的移动时间 + */ + public int scanTime; + + + // This constructor is used when the class is built from an XML resource. + public ViewfinderView(Context context,int time,int color) + { + super(context); + density = context.getResources().getDisplayMetrics().density; + //将像素转化成dp + ScreenRate = (int) (25 * density); + + // Initialize these once for performance rather than calling them every time in onDraw(). + paint = new Paint(); + paintLine=new Paint(); + Resources resources = getResources(); + maskColor = resources.getColor(R.color.viewfinder_mask); + resultColor = resources.getColor(R.color.backgroud); + frameColor = color;//resources.getColor(R.color.viewfinder_frame); + frameBaseColor=reSetColor(frameColor); +// Log.i("Test","reSetColor"+frameBaseColor); + laserColor = resources.getColor(R.color.viewfinder_laser); + resultPointColor = resources.getColor(R.color.possible_result_points); + scannerAlpha = 0; + possibleResultPoints = new HashSet(5); + drawLine=true; + scanTime=time; + } + + public void setCORNER_WIDTH(int CORNER_WIDTH) { + this.CORNER_WIDTH = CORNER_WIDTH; + } + + public void setMIDDLE_LINE_WIDTH(int MIDDLE_LINE_WIDTH) { + this.MIDDLE_LINE_WIDTH = MIDDLE_LINE_WIDTH; +// Log.i("Test","MIDDLE_LINE_WIDTH:"+MIDDLE_LINE_WIDTH); + } + + @Override + public void onDraw(Canvas canvas) + { + Rect frame = CameraManager.get().getFramingRect(); + if (frame == null) + { + return; + } + + if (!isFirst) + { + isFirst = true; + slideTop = frame.top + CORNER_WIDTH; + slideBottom = frame.bottom - CORNER_WIDTH; + + SPEEN_DISTANCE= (slideBottom-slideTop)/((scanTime/16)+2); + + + } + + int width = canvas.getWidth(); + int height = canvas.getHeight(); + + // Draw the exterior (i.e. outside the framing rect) darkened + //画区域 + paint.setColor(resultBitmap != null ? resultColor : maskColor); + canvas.drawRect(0, 0, width, frame.top, paint); + canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint); + canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, + paint); + canvas.drawRect(0, frame.bottom + 1, width, height, paint); + + if (resultBitmap != null) + { + // Draw the opaque result bitmap over the scanning rectangle + paint.setAlpha(OPAQUE); + canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint); + } + else + { + + // Draw a two pixel solid black border inside the framing rect + //画框架 + paint.setColor(frameColor); + // canvas.drawRect(frame.left, frame.top, frame.right + 1, frame.top + 2, paint); + // canvas.drawRect(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1, paint); + // canvas.drawRect(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1, paint); + // canvas.drawRect(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1, paint); + //public void drawRect (float left, float top, float right, float bottom, Paint paint) + //自己画(扫描框边上的角,共8个部分) + canvas.drawRect(frame.left - CORNER_WIDTH/2 , frame.top + - CORNER_WIDTH /2, frame.left + ScreenRate, frame.top + + CORNER_WIDTH /2, paint);//左上角横线 + canvas.drawRect(frame.left - CORNER_WIDTH/2 , frame.top + - CORNER_WIDTH/2, frame.left + CORNER_WIDTH/2, + frame.top + ScreenRate, paint);//左上角竖线 + + paint.setColor(Color.WHITE); + //画白线 + canvas.drawRect(frame.left - CORNER_WIDTH/2 , frame.top + + ScreenRate, frame.left, + frame.bottom - ScreenRate, paint);//左白线 + canvas.drawRect(frame.right , frame.top + + ScreenRate, frame.right+ CORNER_WIDTH/2, + frame.bottom - ScreenRate, paint);//右白线 + canvas.drawRect(frame.left+ScreenRate , frame.top - CORNER_WIDTH/2, + frame.right- ScreenRate,frame.top, paint);//上白线 + canvas.drawRect(frame.left+ScreenRate , frame.bottom , + frame.right- ScreenRate,frame.bottom + CORNER_WIDTH/2, paint);//下白线 + + + paint.setColor(frameColor); + + canvas.drawRect(frame.left - CORNER_WIDTH/2 , frame.bottom + - ScreenRate, frame.left + CORNER_WIDTH/2 , frame.bottom + + CORNER_WIDTH/2 , paint);//左下角竖线 + canvas.drawRect(frame.left - CORNER_WIDTH/2 , frame.bottom + - CORNER_WIDTH/2 , frame.left + ScreenRate, frame.bottom + + CORNER_WIDTH/2 , paint);//左下角横线 + canvas.drawRect(frame.right - ScreenRate, frame.top - CORNER_WIDTH/2 + , frame.right + CORNER_WIDTH/2 , frame.top + + CORNER_WIDTH/2 , paint);//右上横线 + canvas.drawRect(frame.right - CORNER_WIDTH / 2, frame.top + - CORNER_WIDTH / 2, frame.right + CORNER_WIDTH / 2, + frame.top + ScreenRate, paint);//右上竖线 + canvas.drawRect(frame.right - CORNER_WIDTH/2 , frame.bottom + - ScreenRate, frame.right + CORNER_WIDTH /2, frame.bottom + + CORNER_WIDTH /2, paint);//右下竖线 + canvas.drawRect(frame.right - ScreenRate, frame.bottom + - CORNER_WIDTH / 2, frame.right + CORNER_WIDTH / 2, + frame.bottom + CORNER_WIDTH / 2, paint);//右下横线 + //直接用图片 + // Rect bigRect = new Rect(); + // bigRect.left = frame.left; + // bigRect.right = frame.right; + // bigRect.top = frame.top; + // bigRect.bottom = frame.bottom; + // Drawable drawable = getResources().getDrawable(R.drawable.qr_mask); + // BitmapDrawable b= (BitmapDrawable) drawable; + // canvas.drawBitmap(b.getBitmap(), null, bigRect, paint); + + // Draw a red "laser scanner" line through the middle to show decoding is active + // paint.setColor(laserColor); + // paint.setAlpha(SCANNER_ALPHA[scannerAlpha]); + // scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length; + // int middle = frame.height() / 2 + frame.top; + // canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint); + + //画中间移动的线 (int)(SPEEN_DISTANCE*density+0.5f) + if(drawLine) { + slideTop += SPEEN_DISTANCE; + if (slideTop >= slideBottom) { + slideTop = frame.top + CORNER_WIDTH; + } + //自己画 + paintLine.setColor(frameColor); + +// 0x8800FF00 + Shader mShader = new LinearGradient(frame.left + CORNER_WIDTH, slideTop, frame.right + - CORNER_WIDTH, slideTop + MIDDLE_LINE_WIDTH,new int[] {Color.TRANSPARENT,frameBaseColor,frameColor,frameColor,frameColor,frameColor,frameColor,frameBaseColor,Color.TRANSPARENT},null, Shader.TileMode.CLAMP); +//新建一个线性渐变,前两个参数是渐变开始的点坐标,第三四个参数是渐变结束的点的坐标。 +// 连接这2个点就拉出一条渐变线了,玩过PS的都懂。然后那个数组是渐变的颜色。下一个参数是渐变颜色的分布,如果为空,每个颜色就是均匀分布的。 +// 最后是模式,这里设置的是Clamp渐变 + paintLine.setShader(mShader); + canvas.drawRect(frame.left + CORNER_WIDTH, slideTop, frame.right + - CORNER_WIDTH, slideTop + MIDDLE_LINE_WIDTH, paintLine); + + + + } + //用图片 + // Rect lineRect = new Rect(); + // lineRect.left = frame.left; + // lineRect.right = frame.right; + // lineRect.top = slideTop; + // lineRect.bottom = slideTop + MIDDLE_LINE_PADDING; + // canvas.drawBitmap(((BitmapDrawable)(getResources().getDrawable(R.drawable.qrcode_scan_line))).getBitmap(), null, lineRect, paint); + + //画扫描框下面的字 + paint.setColor(Color.WHITE); + paint.setTextSize(TEXT_SIZE * density); + paint.setAlpha(0x40); + paint.setTypeface(Typeface.create("System", Typeface.BOLD)); + paint.setTextAlign(Paint.Align.CENTER);//文字居中,X,Y 对应文字坐标中心 + canvas.drawText( + ShowText, + width/2, frame.bottom + TEXT_PADDING_TOP * density, + paint); + + Collection currentPossible = possibleResultPoints; + Collection currentLast = lastPossibleResultPoints; + if (currentPossible.isEmpty()) + { + lastPossibleResultPoints = null; + } + else + { + possibleResultPoints = new HashSet(5); + lastPossibleResultPoints = currentPossible; + paint.setAlpha(OPAQUE); + paint.setColor(resultPointColor); + for (ResultPoint point : currentPossible) + { + canvas.drawCircle(frame.left + point.getX(), frame.top + + point.getY(), 6.0f, paint);//画扫描到的可能的点 + } + } + if (currentLast != null) + { + paint.setAlpha(OPAQUE / 2); + paint.setColor(resultPointColor); + for (ResultPoint point : currentLast) + { + canvas.drawCircle(frame.left + point.getX(), frame.top + + point.getY(), 3.0f, paint); + } + } + + // Request another update at the animation interval, but only repaint the laser line, + // not the entire viewfinder mask. + postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, + frame.right, frame.bottom); + } + } + + public void drawViewfinder() + { + resultBitmap = null; + invalidate(); + } + + /** + * Draw a bitmap with the result points highlighted instead of the live scanning display. + * + * @param barcode An image of the decoded barcode. + */ + public void drawResultBitmap(Bitmap barcode) + { + resultBitmap = barcode; + invalidate(); + } + + public void addPossibleResultPoint(ResultPoint point) + { + possibleResultPoints.add(point); + } + + + /** + * 中间色颜色换算 + */ + public int reSetColor(int startInt) { + + int startA = (startInt >> 24) & 0xff; + int startR = (startInt >> 16) & 0xff; + int startG = (startInt >> 8) & 0xff; + int startB = startInt & 0xff; + + int endA = startA/2; + + + return ((startA + (endA - startA)) << 24) + | (startR << 16) + | (startG << 8) + | (startB ); + + + } + +} diff --git a/android/src/main/res/drawable/po_seekbar.xml b/android/src/main/res/drawable/po_seekbar.xml new file mode 100644 index 0000000..4cca522 --- /dev/null +++ b/android/src/main/res/drawable/po_seekbar.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/src/main/res/drawable/seek.9.png b/android/src/main/res/drawable/seek.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0869d8d1d987f4321fa7b23a9370e936820c95ce GIT binary patch literal 309 zcmV-50m}Y~P)&v`t(1{ zr_&S`Ui>dU{{qc0$b67JaG$C#x~K?B)>5<1{1=*blIj>_F35b4Jy`Rp)QnUA1tuK% z&)av1Vhl0|WFFRh3S&Tgd}St3(Q_)5NY6p$A^VVFpvqkUd!YC`oveoa00000NkvXX Hu0mjfnkkW1 literal 0 HcmV?d00001 diff --git a/android/src/main/res/drawable/seek_bkg.9.png b/android/src/main/res/drawable/seek_bkg.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6dad8d1917fcff3db65386ff17989ec9d1029a08 GIT binary patch literal 270 zcmV+p0rCEcP)S{V4iqz_&)Px4!!YJ?aNyaSueNh%; z4a0Eg`~G5#@m<%^US1vn?`9x}o%n-$RaKPdIY}vZ0}p)g!oO4c7{_N_)|idKh!91ZjciSk#caFp13=9m6j*iaF%?X7< z9*>uwpa1pZ1OlO_r)P0-admZdd3kwhY3bv~k20AI<_QT2a;1@xkw74T zzIpSerKJUyv9Yney}c|JOWuOf(9jSnp|_-@WDPbqH$&Lj+3D))Du)aP1MbM>a^_TjOTVW%S7>)yi7~811y|AIt65D-Vy*z_YDJCxZbW8K;nD>Fg6(W0oh4V zyxWQn#5f`FB>)g_c^u?e8|;Y$6?->D0|wssG$^+91nG90wJoI}+J2`^3y?Wr3CVzm z*-wyytIjUjpdaUID+ZA<;Q0Y3Blm33x91=c#{@#>Y``PvA0b7<3Gyu+4}0R%C4fov zMhAG=UVR|}JgS?Af1sN;8Gn4=Auf=llv0*xX z)|b^UTa$K#VYT&giIm{5(D%aOu@T)NU2QEL&9Rdw2wY5vf+d}b&$*F*zbd7KmQoXM z@&flvt94!OA)H|Fy59MaL-zw z+}ExO4#BoSe0IH?0md>wn{pz2XC14BkfKVYb;YaS5t7F1x_VuX(6X~CREZ@f@nw-S zM&#I=cXN05Y>^(KXiDZkEQ;$j=+!=|+t zC|VPushn6ek=ChSpCnLv(&Sx0VyP57!}2Nhnb=;F@RkP-Drl=zW{ z$cj^Q#1qyvHp%|{=%)5iUR3;g5A9N>I1^2Km+0YF)}ZWH?*H$d>w4Gp8ZG?n&dU-~ zd+G~b1|Wwv&~VTw&gcH=EJ zE=Qv`+Fz0)&GElXOXDz%w}C|GPJfHsWku3M+pEH98`BG`4aLc+5t-zy+r@jc$oLp( zcVO+sg}w!Yz4b#~BD*WI5o(C80V4Z#Khfe`S44Hd%;2wSMN)BF9w$k%)6u4PuRra2 za^koS(vn`Z`+QwTxZrqT?X{(*F-1wz(n@v4P=s-CAFl4lwDA-ZV_9b6%|=zN^lmc) z#Gkl)Mqcd9kk`ARrw`j4S(Ep+8EkhCNa=aHk!wbTzbPVdydf`j;0O+NN*z&$Tt4M* Rj7|J}@ZDTU&SeBj{C{99G5r7l literal 0 HcmV?d00001 diff --git a/android/src/main/res/drawable/seekbar_horizontal.xml b/android/src/main/res/drawable/seekbar_horizontal.xml new file mode 100644 index 0000000..1df04b7 --- /dev/null +++ b/android/src/main/res/drawable/seekbar_horizontal.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/src/main/res/drawable/seekbar_thumb.xml b/android/src/main/res/drawable/seekbar_thumb.xml new file mode 100644 index 0000000..70bd183 --- /dev/null +++ b/android/src/main/res/drawable/seekbar_thumb.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/android/src/main/res/drawable/seekbar_thumb_normal.png b/android/src/main/res/drawable/seekbar_thumb_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..91b9362f84c0695a38863c3f82617ddadd8c81b8 GIT binary patch literal 570 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k3?#4J%UA`Z<^=eJxc;wWpc?8D|3BUE|7^?u zKt{jP|2fwGJ4F6BF#qom1zFK9^nZ@k{}yf#d#c|5E(s80mc{=@w*U2vAU04Iq8-Qv zqghUu3V`l3DGBlmW)L(9N-wKr(oL>!P|9KP$GlDd{{R1C z{r^1Q&c}CJJT85HKksMTLp{qD1xwrOpPLVSzcbHy3)fo~{*oUjC?Qh#;xxhsT~UCzImd}QjYpGiBT{^~rN^m3A^UU$*vr?;lCmf1dfT(teZ*{$~*sudXZ z`qdfd_TAu3Sz*KdBIeYD{C}4m{}ukVU+bw_S}u4umaCFwjqUQKM(WGfsqVj*cRePy z`)bo$Kh-5-?&((dqhx;W?cVwSxo4!UpP%iQGs;I3Cq%Nah2QO)cS|H`0q;gNzDU(a f8|k3?#4J%UA`Z76tf(xc;wWpc?8D|3BUE|7^?u zKt{jP|2fwG@9zHp>&yQ;yFpg83;myC_5aPy|3ENR@Bj7nAi-G{|G&Qe|M%Db>l;8^ zpn_jtp@zU6_T)6su?%JAn?FK ze_4@FwXRkgn>Y^7EHRPm-&fElD1CJD%N)D^^ZWuXsc{>g`Si3aduDplHN|xj>&#bs z&E7iWu8==Z`ERLP?~kl$U-&1$xn0J2-M^)}dVkh0Q(l$n$aUrJ%6IRkJQOsj^n3o< zXO{5f>yzTvUH8^ne>CG@+VV%i&5JM3&5xA-xwdoX-{;vr?=A6xg1welF{r5}E*!6eG6) literal 0 HcmV?d00001 diff --git a/android/src/main/res/layout/seekbar_layout.xml b/android/src/main/res/layout/seekbar_layout.xml new file mode 100644 index 0000000..2b12dac --- /dev/null +++ b/android/src/main/res/layout/seekbar_layout.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/android/src/main/res/raw/beep.wav b/android/src/main/res/raw/beep.wav new file mode 100644 index 0000000000000000000000000000000000000000..120affda041648c128ac43367e68b1ab35c64043 GIT binary patch literal 20044 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 + + + #c0ffbd21 + #ff000000 + + #00000000 + #ffcc0000 + #cb000000 + #ff4EEE94 + #ffffffff + #ffffffff + + + diff --git a/android/src/main/res/values/dimens.xml b/android/src/main/res/values/dimens.xml new file mode 100644 index 0000000..55c1e59 --- /dev/null +++ b/android/src/main/res/values/dimens.xml @@ -0,0 +1,7 @@ + + + + 16dp + 16dp + + diff --git a/android/src/main/res/values/ids.xml b/android/src/main/res/values/ids.xml new file mode 100644 index 0000000..3500cc5 --- /dev/null +++ b/android/src/main/res/values/ids.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml new file mode 100644 index 0000000..f6437f0 --- /dev/null +++ b/android/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + QRCode_Zxing + diff --git a/android/src/main/res/values/styles.xml b/android/src/main/res/values/styles.xml new file mode 100644 index 0000000..319eb0c --- /dev/null +++ b/android/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/ios/RCTBarcode/RCTBarCode.xcodeproj/project.pbxproj b/ios/RCTBarcode/RCTBarCode.xcodeproj/project.pbxproj new file mode 100644 index 0000000..eba07b7 --- /dev/null +++ b/ios/RCTBarcode/RCTBarCode.xcodeproj/project.pbxproj @@ -0,0 +1,298 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 9F0396621DB5F98F00393F03 /* UIColor+Hex.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F0396611DB5F98F00393F03 /* UIColor+Hex.m */; }; + 9F924BA81DAE17A5002340CC /* RectView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F924BA71DAE17A5002340CC /* RectView.m */; }; + 9FCCD75D1DAC8DA800025BFD /* RCTBarcode.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FCCD75C1DAC8DA800025BFD /* RCTBarcode.m */; }; + 9FCCD75E1DAC8DA800025BFD /* RCTBarcode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9FCCD75B1DAC8DA800025BFD /* RCTBarcode.h */; }; + 9FCCD7931DACC77A00025BFD /* LineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FCCD7921DACC77A00025BFD /* LineView.m */; }; + 9FCCD7C01DACCCD500025BFD /* RCTBarcodeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FCCD7BF1DACCCD500025BFD /* RCTBarcodeManager.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9FCCD7561DAC8DA800025BFD /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + 9FCCD75E1DAC8DA800025BFD /* RCTBarcode.h in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 9F0396601DB5F98F00393F03 /* UIColor+Hex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+Hex.h"; sourceTree = ""; }; + 9F0396611DB5F98F00393F03 /* UIColor+Hex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+Hex.m"; sourceTree = ""; }; + 9F924BA61DAE17A5002340CC /* RectView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RectView.h; sourceTree = ""; }; + 9F924BA71DAE17A5002340CC /* RectView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RectView.m; sourceTree = ""; }; + 9FCCD7581DAC8DA800025BFD /* libRCTBarcode.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTBarcode.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 9FCCD75B1DAC8DA800025BFD /* RCTBarcode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBarcode.h; sourceTree = ""; }; + 9FCCD75C1DAC8DA800025BFD /* RCTBarcode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTBarcode.m; sourceTree = ""; }; + 9FCCD7911DACC77A00025BFD /* LineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LineView.h; sourceTree = ""; }; + 9FCCD7921DACC77A00025BFD /* LineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LineView.m; sourceTree = ""; }; + 9FCCD7BE1DACCCD500025BFD /* RCTBarcodeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBarcodeManager.h; sourceTree = ""; }; + 9FCCD7BF1DACCCD500025BFD /* RCTBarcodeManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBarcodeManager.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 9FCCD7551DAC8DA800025BFD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9FCCD74F1DAC8DA800025BFD = { + isa = PBXGroup; + children = ( + 9FCCD75A1DAC8DA800025BFD /* RCTBarCode */, + 9FCCD7591DAC8DA800025BFD /* Products */, + ); + sourceTree = ""; + }; + 9FCCD7591DAC8DA800025BFD /* Products */ = { + isa = PBXGroup; + children = ( + 9FCCD7581DAC8DA800025BFD /* libRCTBarcode.a */, + ); + name = Products; + sourceTree = ""; + }; + 9FCCD75A1DAC8DA800025BFD /* RCTBarCode */ = { + isa = PBXGroup; + children = ( + 9FCCD7911DACC77A00025BFD /* LineView.h */, + 9FCCD7921DACC77A00025BFD /* LineView.m */, + 9FCCD75B1DAC8DA800025BFD /* RCTBarcode.h */, + 9FCCD75C1DAC8DA800025BFD /* RCTBarcode.m */, + 9FCCD7BE1DACCCD500025BFD /* RCTBarcodeManager.h */, + 9FCCD7BF1DACCCD500025BFD /* RCTBarcodeManager.m */, + 9F924BA61DAE17A5002340CC /* RectView.h */, + 9F924BA71DAE17A5002340CC /* RectView.m */, + 9F0396601DB5F98F00393F03 /* UIColor+Hex.h */, + 9F0396611DB5F98F00393F03 /* UIColor+Hex.m */, + ); + name = RCTBarCode; + path = RCTBarcode; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 9FCCD7571DAC8DA800025BFD /* RCTBarcode */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9FCCD7611DAC8DA800025BFD /* Build configuration list for PBXNativeTarget "RCTBarcode" */; + buildPhases = ( + 9FCCD7541DAC8DA800025BFD /* Sources */, + 9FCCD7551DAC8DA800025BFD /* Frameworks */, + 9FCCD7561DAC8DA800025BFD /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RCTBarcode; + productName = RCTBarcode; + productReference = 9FCCD7581DAC8DA800025BFD /* libRCTBarcode.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 9FCCD7501DAC8DA800025BFD /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = "react-native-component"; + TargetAttributes = { + 9FCCD7571DAC8DA800025BFD = { + CreatedOnToolsVersion = 8.0; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 9FCCD7531DAC8DA800025BFD /* Build configuration list for PBXProject "RCTBarCode" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 9FCCD74F1DAC8DA800025BFD; + productRefGroup = 9FCCD7591DAC8DA800025BFD /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 9FCCD7571DAC8DA800025BFD /* RCTBarcode */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 9FCCD7541DAC8DA800025BFD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9FCCD75D1DAC8DA800025BFD /* RCTBarcode.m in Sources */, + 9F0396621DB5F98F00393F03 /* UIColor+Hex.m in Sources */, + 9FCCD7C01DACCCD500025BFD /* RCTBarcodeManager.m in Sources */, + 9F924BA81DAE17A5002340CC /* RectView.m in Sources */, + 9FCCD7931DACC77A00025BFD /* LineView.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 9FCCD75F1DAC8DA800025BFD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 9FCCD7601DAC8DA800025BFD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 9FCCD7621DAC8DA800025BFD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../../react-native/React/**", + ); + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 9FCCD7631DAC8DA800025BFD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../../react-native/React/**", + ); + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 9FCCD7531DAC8DA800025BFD /* Build configuration list for PBXProject "RCTBarCode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9FCCD75F1DAC8DA800025BFD /* Debug */, + 9FCCD7601DAC8DA800025BFD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9FCCD7611DAC8DA800025BFD /* Build configuration list for PBXNativeTarget "RCTBarcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9FCCD7621DAC8DA800025BFD /* Debug */, + 9FCCD7631DAC8DA800025BFD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 9FCCD7501DAC8DA800025BFD /* Project object */; +} diff --git a/ios/RCTBarcode/RCTBarCode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/RCTBarcode/RCTBarCode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ce5bbb1 --- /dev/null +++ b/ios/RCTBarcode/RCTBarCode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/RCTBarcode/RCTBarcode/LineView.h b/ios/RCTBarcode/RCTBarcode/LineView.h new file mode 100644 index 0000000..c8852ba --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/LineView.h @@ -0,0 +1,10 @@ + +#import + +@interface LineView : UIView + +@property (nonatomic, copy) NSString *scannerLineColor; + +- (id)initWithScannerLineColor:(NSString*)scannerLineColor frame:(CGRect)frame; + +@end diff --git a/ios/RCTBarcode/RCTBarcode/LineView.m b/ios/RCTBarcode/RCTBarcode/LineView.m new file mode 100644 index 0000000..17298fb --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/LineView.m @@ -0,0 +1,73 @@ + +#import "LineView.h" +#import "UIColor+Hex.h" +//#define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] + +@implementation LineView + +- (id)initWithScannerLineColor:(NSString*)scannerLineColor frame:(CGRect)frame +{ + + if ((self = [super initWithFrame:frame])) { + self.scannerLineColor = scannerLineColor; + } + return self; + +} + +// Only override drawRect: if you perform custom drawing. +// An empty implementation adversely affects performance during animation. +- (void)drawRect:(CGRect)rect { + + CGContextRef context = UIGraphicsGetCurrentContext(); + self.backgroundColor = [UIColor clearColor]; +// NSArray *colors = @[@0x00000000, @0x8800FF00, @0xFF00FF00, @0xFF00FF00, @0xFF00FF00, @0xFF00FF00, @0xFF00FF00, @0x8800FF00, @0x00000000]; +// NSLog(@"@0xFF00FF00 = %@", @0xFF00FF00); + UIColor *scannerLineColor = [UIColor colorWithHexString:self.scannerLineColor]; + NSArray *colors = @[scannerLineColor, scannerLineColor, scannerLineColor, scannerLineColor, scannerLineColor, scannerLineColor, scannerLineColor, scannerLineColor, scannerLineColor]; + [self _drawGradientColor:context + rect:rect + options:kCGGradientDrawsBeforeStartLocation + colors:colors]; +} + +/** + * 绘制背景色渐变的矩形,p_colors渐变颜色设置,集合中存储UIColor对象(创建Color时一定用三原色来创建) + **/ +- (void)_drawGradientColor:(CGContextRef)p_context rect:(CGRect)p_clipRect options:(CGGradientDrawingOptions)p_options colors:(NSArray *)p_colors { + CGContextSaveGState(p_context);// 保持住现在的context + CGContextClipToRect(p_context, p_clipRect);// 截取对应的context + long colorCount = p_colors.count; + int numOfComponents = 4; + CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB(); + CGFloat colorComponents[colorCount * numOfComponents]; + CGColorRef temcolorRef = nil; + for (int i = 0; i < colorCount; i++) { + + if(i==0){ + temcolorRef = [UIColor clearColor].CGColor; + }else if (i==colorCount-1) { + temcolorRef = [UIColor clearColor].CGColor; + }else{ +// temcolorRef = UIColorFromRGB([p_colors[i] integerValue]).CGColor; + temcolorRef = ((UIColor *)p_colors[i]).CGColor; + } + + const CGFloat *components = CGColorGetComponents(temcolorRef); + for (int j = 0; j < numOfComponents; ++j) { + colorComponents[i * numOfComponents + j] = components[j]; + } + } + CGGradientRef gradient = CGGradientCreateWithColorComponents(rgb, colorComponents, NULL, colorCount); + CGColorSpaceRelease(rgb); + CGPoint startPoint = CGPointMake(0, 0); + CGPoint endPoint = CGPointMake(p_clipRect.size.width, p_clipRect.size.height); + CGContextDrawLinearGradient(p_context, gradient, startPoint, endPoint, p_options); + + + + CGGradientRelease(gradient); + CGContextRestoreGState(p_context);// 恢复到之前的context +} + +@end diff --git a/ios/RCTBarcode/RCTBarcode/QRCScanner.h b/ios/RCTBarcode/RCTBarcode/QRCScanner.h new file mode 100644 index 0000000..a51c32b --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/QRCScanner.h @@ -0,0 +1,53 @@ + + +#import +#import "LineView.h" + +@protocol QRCodeScanneDelegate +/** + * 扫描成功后返回扫描结果 + * + * @param result 扫描结果 + */ +- (void)didFinshedScanningQRCode:(NSString *)result; + +@end + +@interface QRCScanner : UIView +/** + * 提示语 + */ +@property (nonatomic,strong)UILabel *noticeInfoLable; +/** + * 扫描线的颜色,默认红色 + */ +@property (nonatomic,strong)UIColor *scanningLieColor; +/** + * 扫描框边角的颜色,默认红色 + */ +@property (nonatomic,strong)UIColor *cornerLineColor; +/** + * 扫描框的宽高区域,默认(200,200) + */ +@property (nonatomic,assign)CGSize transparentAreaSize; +/** + * 代理 + */ +@property (nonatomic,assign) iddelegate; +/** + * 初始化方法 + * + * @param QRCScannerView的父view + * + * @return QRCScanner实例 + */ +- (instancetype)initQRCScannerWithView:(UIView *)view; +/** + * 从图片中读取二维码 + * + * @param qrimage 一张二维码图片 + * + * @return 二维码信息 + */ ++ (NSString *)scQRReaderForImage:(UIImage *)qrimage NS_AVAILABLE_IOS(8_0); +@end diff --git a/ios/RCTBarcode/RCTBarcode/QRCScanner.m b/ios/RCTBarcode/RCTBarcode/QRCScanner.m new file mode 100644 index 0000000..fac9856 --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/QRCScanner.m @@ -0,0 +1,352 @@ +// +// QRCScanner.m +// SweepView +// +// Created by chen on 16/8/17. +// Copyright © 2016年 defan. All rights reserved. +// + +#import "QRCScanner.h" +#import + +#define LINE_SCAN_TIME 2.0 // 扫描线从上到下扫描所历时间(s) + +@interface QRCScanner() + +@property (nonatomic,strong)NSTimer *scanLineTimer; +@property (nonatomic,strong)LineView *scanLine; +@property (nonatomic,strong)UIButton *lightButton; + +@property (nonatomic,assign)CGRect clearDrawRect; +@property (nonatomic,assign)BOOL isOn; + +@property (nonatomic,strong)AVCaptureSession *session; +@property (nonatomic,strong)AVCaptureVideoPreviewLayer *preview; +@property (nonatomic,strong)AVCaptureDeviceInput * input; +@property (nonatomic,strong)AVCaptureMetadataOutput * output; +@property (nonatomic,strong)AVCaptureDevice * device; +@property (nonatomic,assign)CGSize parentSize; + +@end +@implementation QRCScanner +#pragma mark - 初始化 +- (instancetype)initQRCScannerWithView:(UIView *)view{ + QRCScanner *qrcView = [[QRCScanner alloc]initWithFrame:view.frame]; + _parentSize = view.frame.size; + [qrcView initDataWithView:view]; + +// UIImagePickerController *picker = [[UIImagePickerController alloc] init]; +// picker.delegate = self; +// picker.sourceType = UIImagePickerControllerSourceTypeCamera; + return qrcView; +} + +- (instancetype)initWithFrame:(CGRect)frame{ + self = [super initWithFrame:frame]; + if(self){ + self.backgroundColor = [UIColor clearColor]; + _transparentAreaSize = CGSizeMake(200, 100); + _cornerLineColor = [UIColor redColor]; + _scanningLieColor = [UIColor redColor]; + } + return self; +} + +- (void)drawRect:(CGRect)rect { + _parentSize = rect.size; + [self updateLayout]; +} + +#pragma mark - 从图片中读取二维码 ++ (NSString *)scQRReaderForImage:(UIImage *)qrimage{ + CIContext *context = [CIContext contextWithOptions:nil]; + CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}]; + CIImage *image = [CIImage imageWithCGImage:qrimage.CGImage]; + NSArray *features = [detector featuresInImage:image]; + CIQRCodeFeature *feature = [features firstObject]; + NSString *result = feature.messageString; + return result; +} +#pragma mark - setter and getter +- (void)setTransparentAreaSize:(CGSize)transparentAreaSize{ + _transparentAreaSize = transparentAreaSize; + [self setNeedsLayout]; + [self setNeedsDisplay]; +} + +- (void)setScanningLieColor:(UIColor *)scanningLieColor{ + _scanningLieColor = scanningLieColor; + [self setNeedsLayout]; + [self setNeedsDisplay]; +} + +- (void)setCornerLineColor:(UIColor *)cornerLineColor{ + _cornerLineColor = cornerLineColor; + [self setNeedsLayout]; + [self setNeedsDisplay]; +} +#pragma mark - UI +#pragma mark 私有方法 +- (void)updateLayout{ + //整个二维码扫描界面的颜色 + CGSize screenSize = _parentSize; + CGRect screenDrawRect =CGRectMake(0, 0, screenSize.width,screenSize.height); + + CGSize transparentArea = _transparentAreaSize; + //中间清空的矩形框 + _clearDrawRect = CGRectMake(screenDrawRect.size.width / 2 - transparentArea.width / 2, + screenDrawRect.size.height / 2 - transparentArea.height / 2, + transparentArea.width,transparentArea.height); + + CGContextRef ctx = UIGraphicsGetCurrentContext(); + [self addScreenFillRect:ctx rect:screenDrawRect]; + [self addCenterClearRect:ctx rect:_clearDrawRect]; + [self addWhiteRect:ctx rect:_clearDrawRect]; + [self addCornerLineWithContext:ctx rect:_clearDrawRect]; + [self addScanLine:_clearDrawRect]; + [self addNoticeInfoLable:_clearDrawRect]; + [self addLightButton:_clearDrawRect]; + if (self.scanLineTimer == nil) { + [self moveUpAndDownLine]; + [self createTimer]; + } +} +#pragma mark 添加提示提心Lable +- (void)addNoticeInfoLable:(CGRect)rect{ + _noticeInfoLable = [[UILabel alloc]initWithFrame:CGRectMake(0, (rect.origin.y + rect.size.height+10), self.bounds.size.width, 20)]; +// [_noticeInfoLable setText:@"将二维码/条形码放入取景框中即可自动扫描"]; + _noticeInfoLable.font = [UIFont systemFontOfSize:15]; + [_noticeInfoLable setTextColor:[UIColor whiteColor]]; + _noticeInfoLable.textAlignment = NSTextAlignmentCenter; + [self addSubview:_noticeInfoLable]; +} +#pragma mark 添加手电筒功能按钮 +- (void)addLightButton:(CGRect)rect{ + _lightButton = [[UIButton alloc]initWithFrame:CGRectMake((self.bounds.size.width - 80)/2, (rect.origin.y + rect.size.height+40), 80, 30)]; + [_lightButton setTitle:@"打开照明" forState:UIControlStateNormal]; + [_lightButton addTarget:self action:@selector(torchSwitch:) forControlEvents:UIControlEventTouchUpInside]; + [_lightButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + _isOn = NO; + [self addSubview:_lightButton]; +} +#pragma mark 画背景 +- (void)addScreenFillRect:(CGContextRef)ctx rect:(CGRect)rect { + CGContextSetRGBFillColor(ctx, 40 / 255.0,40 / 255.0,40 / 255.0,0.5); + CGContextFillRect(ctx, rect); //draw the transparent layer +} +#pragma mark 扣扫描框 +- (void)addCenterClearRect :(CGContextRef)ctx rect:(CGRect)rect { + CGContextClearRect(ctx, rect); //clear the center rect of the layer +} +#pragma mark 画框的白线 +- (void)addWhiteRect:(CGContextRef)ctx rect:(CGRect)rect { + CGContextStrokeRect(ctx, rect); + CGContextSetRGBStrokeColor(ctx, 1, 1, 1, 1); + CGContextSetLineWidth(ctx, 0.8); + CGContextAddRect(ctx, rect); + CGContextStrokePath(ctx); +} +#pragma mark 画扫描线 +- (void)addScanLine:(CGRect)rect{ +// self.scanLine = [[UIImageView alloc]initWithFrame:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 2)]; + + self.scanLine = [[LineView alloc] initWithFrame:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 2)]; + + self.scanLine.backgroundColor = [UIColor clearColor]; +// [self.scanLine setImage:[UIImage imageNamed:@"zbar-line"]]; + [self addSubview:self.scanLine]; +} +#pragma mark 画框的四个角 +- (void)addCornerLineWithContext:(CGContextRef)ctx rect:(CGRect)rect{ + + //画四个边角 + CGContextSetLineWidth(ctx, 2); + [self setStrokeColor:_cornerLineColor withContext:ctx]; + + //左上角 + CGPoint poinsTopLeftA[] = { + CGPointMake(rect.origin.x+0.7, rect.origin.y), + CGPointMake(rect.origin.x+0.7 , rect.origin.y + 15) + }; + + CGPoint poinsTopLeftB[] = {CGPointMake(rect.origin.x, rect.origin.y +0.7),CGPointMake(rect.origin.x + 15, rect.origin.y+0.7)}; + [self addLine:poinsTopLeftA pointB:poinsTopLeftB ctx:ctx]; + + //左下角 + CGPoint poinsBottomLeftA[] = {CGPointMake(rect.origin.x+ 0.7, rect.origin.y + rect.size.height - 15),CGPointMake(rect.origin.x +0.7,rect.origin.y + rect.size.height)}; + CGPoint poinsBottomLeftB[] = {CGPointMake(rect.origin.x , rect.origin.y + rect.size.height - 0.7) ,CGPointMake(rect.origin.x+0.7 +15, rect.origin.y + rect.size.height - 0.7)}; + [self addLine:poinsBottomLeftA pointB:poinsBottomLeftB ctx:ctx]; + + //右上角 + CGPoint poinsTopRightA[] = {CGPointMake(rect.origin.x+ rect.size.width - 15, rect.origin.y+0.7),CGPointMake(rect.origin.x + rect.size.width,rect.origin.y +0.7 )}; + CGPoint poinsTopRightB[] = {CGPointMake(rect.origin.x+ rect.size.width-0.7, rect.origin.y),CGPointMake(rect.origin.x + rect.size.width-0.7,rect.origin.y + 15 +0.7 )}; + [self addLine:poinsTopRightA pointB:poinsTopRightB ctx:ctx]; + + CGPoint poinsBottomRightA[] = {CGPointMake(rect.origin.x+ rect.size.width -0.7 , rect.origin.y+rect.size.height+ -15),CGPointMake(rect.origin.x-0.7 + rect.size.width,rect.origin.y +rect.size.height )}; + CGPoint poinsBottomRightB[] = {CGPointMake(rect.origin.x+ rect.size.width - 15 , rect.origin.y + rect.size.height-0.7),CGPointMake(rect.origin.x + rect.size.width,rect.origin.y + rect.size.height - 0.7 )}; + [self addLine:poinsBottomRightA pointB:poinsBottomRightB ctx:ctx]; + CGContextStrokePath(ctx); +} +- (void)addLine:(CGPoint[])pointA pointB:(CGPoint[])pointB ctx:(CGContextRef)ctx { + CGContextAddLines(ctx, pointA, 2); + CGContextAddLines(ctx, pointB, 2); +} +#pragma mark - 功能方法 +#pragma mark 定时器 +- (void)createTimer { + self.scanLineTimer = + [NSTimer scheduledTimerWithTimeInterval:LINE_SCAN_TIME + target:self + selector:@selector(moveUpAndDownLine) + userInfo:nil + repeats:YES]; +} +#pragma mark 移动扫描线 +- (void)moveUpAndDownLine { + CGRect readerFrame = self.frame; + CGSize viewFinderSize = _clearDrawRect.size; + CGRect scanLineframe = self.scanLine.frame; + scanLineframe.origin.y = (readerFrame.size.height - viewFinderSize.height)/2; + self.scanLine.frame = scanLineframe; + self.scanLine.hidden = NO; + __weak __typeof(self) weakSelf = self; + [UIView animateWithDuration:LINE_SCAN_TIME - 0.05 + animations:^{ + CGRect scanLineframe = weakSelf.scanLine.frame; + scanLineframe.origin.y = + (readerFrame.size.height + viewFinderSize.height)/2 - + weakSelf.scanLine.frame.size.height; + weakSelf.scanLine.frame = scanLineframe; + } + completion:^(BOOL finished) { + weakSelf.scanLine.hidden = YES; + }]; + +} +//设置画笔颜色 +- (void)setStrokeColor:(UIColor *)color withContext:(CGContextRef)ctx{ + NSMutableArray *rgbColorArray = [self changeUIColorToRGB:color]; + CGFloat r = [rgbColorArray[0] floatValue]; + CGFloat g = [rgbColorArray[1] floatValue]; + CGFloat b = [rgbColorArray[2] floatValue]; + CGContextSetRGBStrokeColor(ctx,r,g,b,1); +} +#pragma mark 照明灯切换 +- (void)torchSwitch:(id)sender { + if (!_isOn) { + [_lightButton setTitle:@"关闭照明" forState:UIControlStateNormal]; + _isOn = YES; + }else{ + [_lightButton setTitle:@"打开照明" forState:UIControlStateNormal]; + _isOn = NO; + } + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + NSError *error; + if (device.hasTorch) { // 判断设备是否有闪光灯 + BOOL b = [device lockForConfiguration:&error]; + if (!b) { + if (error) { + NSLog(@"lock torch configuration error:%@", error.localizedDescription); + } + return; + } + device.torchMode = + (device.torchMode == AVCaptureTorchModeOff ? AVCaptureTorchModeOn : AVCaptureTorchModeOff); + [device unlockForConfiguration]; + } +} + + + + +#pragma mark - 扫描 +- (void)initDataWithView:(UIView *)parentView{ + _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + + _input = [AVCaptureDeviceInput deviceInputWithDevice:_device error:nil]; + + _output = [[AVCaptureMetadataOutput alloc]init]; + [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; + CGRect screenDrawRect =CGRectMake(0, 0, parentView.frame.size.width,parentView.frame.size.height); + CGSize transparentArea = _transparentAreaSize; + + + // *设置聚焦区域 + _output.rectOfInterest = CGRectMake((screenDrawRect.size.height / 2 - transparentArea.height / 2)/parentView.frame.size.height, + (screenDrawRect.size.width / 2 - transparentArea.width / 2)/parentView.frame.size.width, + transparentArea.height/parentView.frame.size.height, + transparentArea.width/parentView.frame.size.width); + + + // Session + _session = [[AVCaptureSession alloc]init]; + [_session setSessionPreset:AVCaptureSessionPresetHigh]; + if ([_session canAddInput:_input]) + { + [_session addInput:_input]; + } + + if ([_session canAddOutput:_output]) + { + [_session addOutput:_output]; + } + + // 条码类型 AVMetadataObjectTypeQRCode + _output.metadataObjectTypes =@[AVMetadataObjectTypeQRCode]; + + //增加条形码扫描 + _output.metadataObjectTypes = @[AVMetadataObjectTypeEAN13Code,AVMetadataObjectTypeEAN8Code,AVMetadataObjectTypeCode128Code,AVMetadataObjectTypeQRCode]; + + // Preview + _preview =[AVCaptureVideoPreviewLayer layerWithSession:_session]; + _preview.videoGravity =AVLayerVideoGravityResize; + [_preview setFrame:parentView.bounds]; + [parentView.layer insertSublayer:_preview atIndex:0]; + + [_session startRunning]; +} + +#pragma mark - AVCaptureMetadataOutputObjectsDelegate +- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection +{ +// [self.session stopRunning]; +// [self.preview removeFromSuperlayer]; + + //设置界面显示扫描结果 + if (metadataObjects.count > 0) { + AVMetadataMachineReadableCodeObject *obj = metadataObjects[0]; + if ([self.delegate respondsToSelector:@selector(didFinshedScanningQRCode:)]) { + [self.delegate didFinshedScanningQRCode:obj.stringValue]; + } + else{ + NSLog(@"没有收到扫描结果,看看是不是没有实现协议!"); + } + } +// [self removeFromSuperview]; +} +#pragma mark - 辅助方法 +//将UIColor转换为RGB值 +- (NSMutableArray *) changeUIColorToRGB:(UIColor *)color +{ + NSMutableArray *RGBStrValueArr = [[NSMutableArray alloc] init]; + NSString *RGBStr = nil; + //获得RGB值描述 + NSString *RGBValue = [NSString stringWithFormat:@"%@",color]; + //将RGB值描述分隔成字符串 + NSArray *RGBArr = [RGBValue componentsSeparatedByString:@" "]; + //获取红色值 + float r = [[RGBArr objectAtIndex:1] floatValue] * 255; + RGBStr = [NSString stringWithFormat:@"%f",r]; + [RGBStrValueArr addObject:RGBStr]; + //获取绿色值 + float g = [[RGBArr objectAtIndex:2] intValue] * 255; + RGBStr = [NSString stringWithFormat:@"%f",g]; + [RGBStrValueArr addObject:RGBStr]; + //获取蓝色值 + float b = [[RGBArr objectAtIndex:3] intValue] * 255; + RGBStr = [NSString stringWithFormat:@"%f",b]; + [RGBStrValueArr addObject:RGBStr]; + //返回保存RGB值的数组 + return RGBStrValueArr; +} +@end diff --git a/ios/RCTBarcode/RCTBarcode/RCTBarcode.h b/ios/RCTBarcode/RCTBarcode/RCTBarcode.h new file mode 100644 index 0000000..5afd1af --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/RCTBarcode.h @@ -0,0 +1,25 @@ +#import +#import +#import "RCTBarcodeManager.h" +#import "LineView.h" +#import "RectView.h"; + + +@interface RCTBarcode : UIView + +@property (nonatomic,strong)NSTimer *scanLineTimer; +@property (nonatomic,strong)LineView *scanLine; +@property (nonatomic,assign)CGRect scannerRect; +@property (nonatomic, copy) RCTBubblingEventBlock onBarCodeRead; +@property (nonatomic, assign) NSInteger scannerRectWidth; +@property (nonatomic, assign) NSInteger scannerRectHeight; +@property (nonatomic, assign) NSInteger scannerRectTop; +@property (nonatomic, assign) NSInteger scannerRectLeft; +@property (nonatomic, assign) NSInteger scannerLineInterval; +@property (nonatomic, copy) NSString *scannerRectCornerColor; + +- (id)initWithManager:(RCTBarcodeManager*)manager; +- (void)moveUpAndDownLine; +- (void)createTimer; + +@end diff --git a/ios/RCTBarcode/RCTBarcode/RCTBarcode.m b/ios/RCTBarcode/RCTBarcode/RCTBarcode.m new file mode 100644 index 0000000..5937e48 --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/RCTBarcode.m @@ -0,0 +1,225 @@ + +#import "RCTBarcode.h" +#import "UIView+React.h" +#import "LineView.h" +//#define WIDTH [UIScreen mainScreen].bounds.size.width +//#define HEIGHT [UIScreen mainScreen].bounds.size.height +//#define LINE_SCAN_INTERVAL 3.0 // 扫描线从上到下扫描所历时间(s) + +@interface RCTBarcode() + +@property (nonatomic, weak) RCTBarcodeManager *manager; + +@end + +@implementation RCTBarcode + +- (id)initWithManager:(RCTBarcodeManager*)manager +{ + + if ((self = [super init])) { + self.manager = manager; + [self.manager initializeCaptureSessionInput:AVMediaTypeVideo]; + [self.manager startSession]; + } + return self; + +} + +- (void)drawRect:(CGRect)rect { +// NSLog(@"drawRect--------------"); + + [super drawRect:rect]; + + [self updateLayout]; +} + + +- (void)layoutSubviews +{ +// NSLog(@"layoutSubviews..."); + + [super layoutSubviews]; + self.manager.previewLayer.frame = self.bounds; + self.manager.previewLayer.transform = CATransform3DScale(self.manager.previewLayer.transform, 1.5, 1.5, 1); + self.manager.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + + [self setBackgroundColor:[UIColor blackColor]]; + [self.layer insertSublayer:self.manager.previewLayer atIndex:0]; +} + +- (void)removeFromSuperview +{ +// NSLog(@"removeFromSuperview..."); + + [self.scanLineTimer invalidate]; + self.scanLineTimer = nil; + + [self.manager endSession]; + [super removeFromSuperview]; +} + + +- (void)updateLayout{ + +// NSLog(@"updateLayout..."); + + int scannerRectWidth = self.scannerRectWidth;//300; + int scannerRectHeight = self.scannerRectHeight;//300; + + CGRect cameraRect = self.bounds; + //中间的矩形框 + self.scannerRect = CGRectMake( (cameraRect.size.width - scannerRectWidth) / 2, (cameraRect.size.height - scannerRectHeight) / 2, scannerRectWidth, scannerRectHeight); + + RectView *view = [[RectView alloc] initWithScannerRect:self.scannerRect frame:self.bounds scannerRectCornerColor:self.scannerRectCornerColor]; +// RectView *view = [[RectView alloc] initWithFrame:self.bounds]; + + view.backgroundColor = [UIColor clearColor]; + + [self addSubview:view]; + + [self addScanLine:self.scannerRect]; + + if (self.scanLineTimer == nil) { +// [self moveUpAndDownLine]; + [self createTimer]; + } +} + +#pragma mark 画背景 +- (void)addScreenFillRect:(CGContextRef)ctx rect:(CGRect)rect { + CGContextSetRGBFillColor(ctx, 40 / 255.0,40 / 255.0,40 / 255.0,0.5); + CGContextFillRect(ctx, rect); //draw the transparent layer +} +#pragma mark 扣扫描框 +- (void)addCenterClearRect :(CGContextRef)ctx rect:(CGRect)rect { + CGContextClearRect(ctx, rect); //clear the center rect of the layer +} +#pragma mark 画框的白线 +- (void)addWhiteRect:(CGContextRef)ctx rect:(CGRect)rect { + CGContextStrokeRect(ctx, rect); + CGContextSetRGBStrokeColor(ctx, 1, 1, 1, 1); + CGContextSetLineWidth(ctx, 0.8); + CGContextAddRect(ctx, rect); + CGContextStrokePath(ctx); +} +#pragma mark 画扫描线 +- (void)addScanLine:(CGRect)rect{ +// self.scanLine = [[LineView alloc] initWithFrame:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 1.25)]; + self.scanLine = [[LineView alloc] initWithScannerLineColor:self.scannerRectCornerColor frame:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 1.25)]; + + self.scanLine.backgroundColor = [UIColor clearColor]; + + [self addSubview:self.scanLine]; +} +//#pragma mark 画框的四个角 +//- (void)addCornerLineWithContext:(CGContextRef)ctx rect:(CGRect)rect{ +// +// UIColor *cornerLineColor = [UIColor redColor]; +// +// //画四个边角 +// CGContextSetLineWidth(ctx, 2); +// [self setStrokeColor:cornerLineColor withContext:ctx]; +// +// //左上角 +// CGPoint poinsTopLeftA[] = { +// CGPointMake(rect.origin.x+0.7, rect.origin.y), +// CGPointMake(rect.origin.x+0.7 , rect.origin.y + 15) +// }; +// +// CGPoint poinsTopLeftB[] = {CGPointMake(rect.origin.x, rect.origin.y +0.7),CGPointMake(rect.origin.x + 15, rect.origin.y+0.7)}; +// [self addLine:poinsTopLeftA pointB:poinsTopLeftB ctx:ctx]; +// +// //左下角 +// CGPoint poinsBottomLeftA[] = {CGPointMake(rect.origin.x+ 0.7, rect.origin.y + rect.size.height - 15),CGPointMake(rect.origin.x +0.7,rect.origin.y + rect.size.height)}; +// CGPoint poinsBottomLeftB[] = {CGPointMake(rect.origin.x , rect.origin.y + rect.size.height - 0.7) ,CGPointMake(rect.origin.x+0.7 +15, rect.origin.y + rect.size.height - 0.7)}; +// [self addLine:poinsBottomLeftA pointB:poinsBottomLeftB ctx:ctx]; +// +// //右上角 +// CGPoint poinsTopRightA[] = {CGPointMake(rect.origin.x+ rect.size.width - 15, rect.origin.y+0.7),CGPointMake(rect.origin.x + rect.size.width,rect.origin.y +0.7 )}; +// CGPoint poinsTopRightB[] = {CGPointMake(rect.origin.x+ rect.size.width-0.7, rect.origin.y),CGPointMake(rect.origin.x + rect.size.width-0.7,rect.origin.y + 15 +0.7 )}; +// [self addLine:poinsTopRightA pointB:poinsTopRightB ctx:ctx]; +// +// CGPoint poinsBottomRightA[] = {CGPointMake(rect.origin.x+ rect.size.width -0.7 , rect.origin.y+rect.size.height+ -15),CGPointMake(rect.origin.x-0.7 + rect.size.width,rect.origin.y +rect.size.height )}; +// CGPoint poinsBottomRightB[] = {CGPointMake(rect.origin.x+ rect.size.width - 15 , rect.origin.y + rect.size.height-0.7),CGPointMake(rect.origin.x + rect.size.width,rect.origin.y + rect.size.height - 0.7 )}; +// [self addLine:poinsBottomRightA pointB:poinsBottomRightB ctx:ctx]; +// CGContextStrokePath(ctx); +//} +//- (void)addLine:(CGPoint[])pointA pointB:(CGPoint[])pointB ctx:(CGContextRef)ctx { +// CGContextAddLines(ctx, pointA, 2); +// CGContextAddLines(ctx, pointB, 2); +//} +#pragma mark - 功能方法 +#pragma mark 定时器 +- (void)createTimer { + + self.scanLineTimer = + [NSTimer scheduledTimerWithTimeInterval:self.scannerLineInterval / 1000 + target:self + selector:@selector(moveUpAndDownLine) + userInfo:nil + repeats:YES]; +} +#pragma mark 移动扫描线 +- (void)moveUpAndDownLine { +// NSLog(@"moveUpAndDownLine"); + + CGRect readerFrame = self.frame; + CGSize viewFinderSize = self.scannerRect.size; + CGRect scanLineframe = self.scanLine.frame; + scanLineframe.origin.y = (readerFrame.size.height - viewFinderSize.height)/2; + self.scanLine.frame = scanLineframe; + self.scanLine.hidden = NO; + __weak __typeof(self) weakSelf = self; + [UIView animateWithDuration:self.scannerLineInterval / 1000 - 0.05 + animations:^{ + CGRect scanLineframe = weakSelf.scanLine.frame; + scanLineframe.origin.y = + (readerFrame.size.height + viewFinderSize.height)/2 - + weakSelf.scanLine.frame.size.height; + weakSelf.scanLine.frame = scanLineframe; + } + completion:^(BOOL finished) { + weakSelf.scanLine.hidden = YES; + }]; + +} +////设置画笔颜色 +//- (void)setStrokeColor:(UIColor *)color withContext:(CGContextRef)ctx{ +// NSMutableArray *rgbColorArray = [self changeUIColorToRGB:color]; +// CGFloat r = [rgbColorArray[0] floatValue]; +// CGFloat g = [rgbColorArray[1] floatValue]; +// CGFloat b = [rgbColorArray[2] floatValue]; +// CGContextSetRGBStrokeColor(ctx,r,g,b,1); +//} +//#pragma mark - 辅助方法 +////将UIColor转换为RGB值 +//- (NSMutableArray *) changeUIColorToRGB:(UIColor *)color +//{ +// NSMutableArray *RGBStrValueArr = [[NSMutableArray alloc] init]; +// NSString *RGBStr = nil; +// //获得RGB值描述 +// NSString *RGBValue = [NSString stringWithFormat:@"%@",color]; +// //将RGB值描述分隔成字符串 +// NSArray *RGBArr = [RGBValue componentsSeparatedByString:@" "]; +// //获取红色值 +// float r = [[RGBArr objectAtIndex:1] floatValue] * 255; +// RGBStr = [NSString stringWithFormat:@"%f",r]; +// [RGBStrValueArr addObject:RGBStr]; +// //获取绿色值 +// float g = [[RGBArr objectAtIndex:2] intValue] * 255; +// RGBStr = [NSString stringWithFormat:@"%f",g]; +// [RGBStrValueArr addObject:RGBStr]; +// //获取蓝色值 +// float b = [[RGBArr objectAtIndex:3] intValue] * 255; +// RGBStr = [NSString stringWithFormat:@"%f",b]; +// [RGBStrValueArr addObject:RGBStr]; +// //返回保存RGB值的数组 +// return RGBStrValueArr; +//} + + + +@end + + diff --git a/ios/RCTBarcode/RCTBarcode/RCTBarcodeManager.h b/ios/RCTBarcode/RCTBarcode/RCTBarcodeManager.h new file mode 100644 index 0000000..578564c --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/RCTBarcodeManager.h @@ -0,0 +1,24 @@ + +#import "RCTViewManager.h" +#import + +@class RCTBarcode; + +@interface RCTBarcodeManager : RCTViewManager + +@property (nonatomic, strong) dispatch_queue_t sessionQueue; +@property (nonatomic, strong) AVCaptureSession *session; +@property (nonatomic, strong) AVCaptureDeviceInput *videoCaptureDeviceInput; +@property (nonatomic, strong) AVCaptureMetadataOutput *metadataOutput; +@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer; +@property (nonatomic, strong) RCTBarcode *barcode; +@property (nonatomic, strong) NSArray* barCodeTypes; +@property (nonatomic, assign) SystemSoundID beep_sound_id; + +- (void)initializeCaptureSessionInput:(NSString*)type; + +- (void)startSession; +- (void)stopSession; +- (void)endSession; + +@end diff --git a/ios/RCTBarcode/RCTBarcode/RCTBarcodeManager.m b/ios/RCTBarcode/RCTBarcode/RCTBarcodeManager.m new file mode 100644 index 0000000..c1ff9df --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/RCTBarcodeManager.m @@ -0,0 +1,288 @@ + +#import "RCTBarcode.h" +#import "RCTBarcodeManager.h" + + + +@interface RCTBarcodeManager () + +@end + +@implementation RCTBarcodeManager + +RCT_EXPORT_MODULE(RCTBarcode) + +RCT_EXPORT_VIEW_PROPERTY(scannerRectWidth, NSInteger) + +RCT_EXPORT_VIEW_PROPERTY(scannerRectHeight, NSInteger) + +RCT_EXPORT_VIEW_PROPERTY(scannerRectTop, NSInteger) + +RCT_EXPORT_VIEW_PROPERTY(scannerRectLeft, NSInteger) + +RCT_EXPORT_VIEW_PROPERTY(scannerLineInterval, NSInteger) + +RCT_EXPORT_VIEW_PROPERTY(scannerRectCornerColor, NSString) + +RCT_EXPORT_VIEW_PROPERTY(onBarCodeRead, RCTBubblingEventBlock) + +RCT_CUSTOM_VIEW_PROPERTY(barCodeTypes, NSArray, RCTBarcode) { +// NSLog(@"custom barCodeTypes -> %@", self.barCodeTypes); + self.barCodeTypes = [RCTConvert NSArray:json]; +} + +- (UIView *)view +{ + self.session = [[AVCaptureSession alloc]init]; +#if !(TARGET_IPHONE_SIMULATOR) + self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session]; +// self.previewLayer.needsDisplayOnBoundsChange = YES; + #endif + + if(!self.barcode){ + self.barcode = [[RCTBarcode alloc] initWithManager:self]; + } + + SystemSoundID beep_sound_id; + NSString *path = [[NSBundle mainBundle] pathForResource:@"beep" ofType:@"wav"]; + if (path) { + AudioServicesCreateSystemSoundID((__bridge CFURLRef)[NSURL fileURLWithPath:path],&beep_sound_id); + self.beep_sound_id = beep_sound_id; + } + + return self.barcode; +} + +- (id)init { + if ((self = [super init])) { + self.sessionQueue = dispatch_queue_create("barCodeManagerQueue", DISPATCH_QUEUE_SERIAL); + } + return self; +} + +- (void)initializeCaptureSessionInput:(NSString *)type { + dispatch_async(self.sessionQueue, ^{ + + [self.session beginConfiguration]; + + NSError *error = nil; + + AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + + if (captureDevice == nil) { + return; + } + + AVCaptureDeviceInput *captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error]; + + if (error || captureDeviceInput == nil) { +// NSLog(@"%@", error); + return; + } + + [self.session removeInput:self.videoCaptureDeviceInput]; + + + if ([self.session canAddInput:captureDeviceInput]) { + [self.session addInput:captureDeviceInput]; + + self.videoCaptureDeviceInput = captureDeviceInput; + +// self.metadataOutput.rectOfInterest = self.barcode.bounds; +// [self.metadataOutput setMetadataObjectTypes:self.metadataOutput.availableMetadataObjectTypes]; +// [self.metadataOutput setMetadataObjectTypes:self.barCodeTypes]; + + } + + [self.session commitConfiguration]; + }); +} + +RCT_EXPORT_METHOD(startSession) { + #if TARGET_IPHONE_SIMULATOR + return; + #endif + dispatch_async(self.sessionQueue, ^{ + +// NSLog(@"self.metadataOutput = %@", self.metadataOutput); + + if(self.metadataOutput == nil) { + AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init]; + self.metadataOutput = metadataOutput; + + if ([self.session canAddOutput:self.metadataOutput]) { + [self.metadataOutput setMetadataObjectsDelegate:self queue:self.sessionQueue]; + [self.session addOutput:self.metadataOutput]; +// [metadataOutput setMetadataObjectTypes:self.metadataOutput.availableMetadataObjectTypes]; + [self.metadataOutput setMetadataObjectTypes:self.barCodeTypes]; + } + +// [metadataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeEAN13Code,AVMetadataObjectTypeEAN8Code,AVMetadataObjectTypeCode128Code,AVMetadataObjectTypeQRCode]]; + + // [[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureInputPortFormatDescriptionDidChangeNotification + // object:nil + // queue:[NSOperationQueue currentQueue] + // usingBlock: ^(NSNotification *_Nonnull note) { +// metadataOutput.rectOfInterest = [self.previewLayer metadataOutputRectOfInterestForRect:CGRectMake(80, 80, 160, 160)]; + // }]; + +// NSLog(@"startSession set metadataOutput..."); + } + + [self.session startRunning]; + + if(self.barcode.scanLineTimer != nil) { + //设回当前时间模拟继续效果 + [self.barcode.scanLineTimer setFireDate:[NSDate date]]; + } + + }); +} + +RCT_EXPORT_METHOD(stopSession) { + #if TARGET_IPHONE_SIMULATOR + return; + #endif + dispatch_async(self.sessionQueue, ^{ + + [self.session commitConfiguration]; + [self.session stopRunning]; + + + //设置大时刻来模拟暂停效果 + [self.barcode.scanLineTimer setFireDate:[NSDate distantFuture]]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), + dispatch_get_main_queue(), + ^{ +// NSLog(@"self.barcode.scanLine remove animation"); + [self.barcode.scanLine.layer removeAllAnimations]; + }); + + }); +} + +- (void)endSession { + #if TARGET_IPHONE_SIMULATOR + return; + #endif + dispatch_async(self.sessionQueue, ^{ + self.barcode = nil; + [self.previewLayer removeFromSuperlayer]; + [self.session commitConfiguration]; + [self.session stopRunning]; + [self.barcode.scanLineTimer invalidate]; + for(AVCaptureInput *input in self.session.inputs) { + [self.session removeInput:input]; + } + + for(AVCaptureOutput *output in self.session.outputs) { + [self.session removeOutput:output]; + } + }); +} + +//- (void)startSession { +//#if TARGET_IPHONE_SIMULATOR +// return; +//#endif +// dispatch_async(self.sessionQueue, ^{ +// +// AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init]; +// if ([self.session canAddOutput:metadataOutput]) { +// [metadataOutput setMetadataObjectsDelegate:self queue:self.sessionQueue]; +// [self.session addOutput:metadataOutput]; +//// [metadataOutput setMetadataObjectTypes:self.barCodeTypes]; +// [metadataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeEAN13Code,AVMetadataObjectTypeEAN8Code,AVMetadataObjectTypeCode128Code,AVMetadataObjectTypeQRCode]]; +// +//// [[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureInputPortFormatDescriptionDidChangeNotification +//// object:nil +//// queue:[NSOperationQueue currentQueue] +//// usingBlock: ^(NSNotification *_Nonnull note) { +//// metadataOutput.rectOfInterest = [self.previewLayer metadataOutputRectOfInterestForRect:CGRectMake(80, 80, 160, 160)]; +//// }]; +// +// self.metadataOutput = metadataOutput; +// NSLog(@"startSession set metadataOutput..."); +// } +// +// [self.session startRunning]; +// }); +//} + +//- (void)stopSession { +//#if TARGET_IPHONE_SIMULATOR +// return; +//#endif +// dispatch_async(self.sessionQueue, ^{ +// self.barcode = nil; +// [self.previewLayer removeFromSuperlayer]; +// [self.session commitConfiguration]; +// [self.session stopRunning]; +// for(AVCaptureInput *input in self.session.inputs) { +// [self.session removeInput:input]; +// } +// +// for(AVCaptureOutput *output in self.session.outputs) { +// [self.session removeOutput:output]; +// } +// }); +//} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection { +// NSLog(@"captureOutput!!!"); + for (AVMetadataMachineReadableCodeObject *metadata in metadataObjects) { +// NSLog(@"AVMetadataMachineReadableCodeObject!!!"); +// NSLog(@"type = %@, data = %@", metadata.type, metadata.stringValue); + for (id barcodeType in self.barCodeTypes) { + if ([metadata.type isEqualToString:barcodeType]) { + if (!self.barcode.onBarCodeRead) { + return; + } + + AudioServicesPlaySystemSound(self.beep_sound_id); + +// NSLog(@"type = %@, data = %@", metadata.type, metadata.stringValue); + self.barcode.onBarCodeRead(@{ + @"data": @{ + @"type": metadata.type, + @"code": metadata.stringValue, + }, + }); + } + } + } +} + + + + + +- (NSDictionary *)constantsToExport +{ + return @{ + @"barCodeTypes": @{ + @"upce": AVMetadataObjectTypeUPCECode, + @"code39": AVMetadataObjectTypeCode39Code, + @"code39mod43": AVMetadataObjectTypeCode39Mod43Code, + @"ean13": AVMetadataObjectTypeEAN13Code, + @"ean8": AVMetadataObjectTypeEAN8Code, + @"code93": AVMetadataObjectTypeCode93Code, + @"code128": AVMetadataObjectTypeCode128Code, + @"pdf417": AVMetadataObjectTypePDF417Code, + @"qr": AVMetadataObjectTypeQRCode, + @"aztec": AVMetadataObjectTypeAztecCode + #ifdef AVMetadataObjectTypeInterleaved2of5Code + ,@"interleaved2of5": AVMetadataObjectTypeInterleaved2of5Code + # endif + #ifdef AVMetadataObjectTypeITF14Code + ,@"itf14": AVMetadataObjectTypeITF14Code + # endif + #ifdef AVMetadataObjectTypeDataMatrixCode + ,@"datamatrix": AVMetadataObjectTypeDataMatrixCode + # endif + } + }; +} + + +@end diff --git a/ios/RCTBarcode/RCTBarcode/RectView.h b/ios/RCTBarcode/RCTBarcode/RectView.h new file mode 100644 index 0000000..ba6e080 --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/RectView.h @@ -0,0 +1,11 @@ + +#import + +@interface RectView : UIView + +@property (nonatomic,assign)CGRect scannerRect; +@property (nonatomic, copy) NSString *scannerRectCornerColor; + +- (id)initWithScannerRect:(CGRect)scannerRect frame:(CGRect)frame scannerRectCornerColor:(NSString*)scannerRectCornerColor; + +@end diff --git a/ios/RCTBarcode/RCTBarcode/RectView.m b/ios/RCTBarcode/RCTBarcode/RectView.m new file mode 100644 index 0000000..49e1f0d --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/RectView.m @@ -0,0 +1,152 @@ + +#import "RectView.h" +#import "UIColor+Hex.h" + +@implementation RectView + + +- (id)initWithScannerRect:(CGRect)scannerRect frame:(CGRect)frame scannerRectCornerColor:(NSString*)scannerRectCornerColor +{ + + if ((self = [super initWithFrame:frame])) { + self.scannerRect = scannerRect; + self.scannerRectCornerColor = scannerRectCornerColor; + } + return self; + +} + +- (void)drawRect:(CGRect)rect { + + CGContextRef ctx = UIGraphicsGetCurrentContext(); + [self addScreenFillRect:ctx rect:rect]; + [self addCenterClearRect:ctx rect:self.scannerRect]; + [self addWhiteRect:ctx rect:self.scannerRect]; + [self addCornerLineWithContext:ctx rect:self.scannerRect]; +} + + +#pragma mark 画背景 +- (void)addScreenFillRect:(CGContextRef)ctx rect:(CGRect)rect { + CGContextSetRGBFillColor(ctx, 40 / 255.0,40 / 255.0,40 / 255.0,0.5); + CGContextFillRect(ctx, rect); //draw the transparent layer +} +#pragma mark 扣扫描框 +- (void)addCenterClearRect :(CGContextRef)ctx rect:(CGRect)rect { + CGContextClearRect(ctx, rect); //clear the center rect of the layer +} +#pragma mark 画框的白线 +- (void)addWhiteRect:(CGContextRef)ctx rect:(CGRect)rect { + CGContextStrokeRect(ctx, rect); + CGContextSetRGBStrokeColor(ctx, 1, 1, 1, 1); + CGContextSetLineWidth(ctx, 0.8); + CGContextAddRect(ctx, rect); + CGContextStrokePath(ctx); +} +#pragma mark 画框的四个角 +- (void)addCornerLineWithContext:(CGContextRef)ctx rect:(CGRect)rect{ + +// UIColor *cornerLineColor = [UIColor redColor]; +// NSLog(@"self.scannerRectCornerColor=%@", self.scannerRectCornerColor); + UIColor *cornerLineColor = [UIColor colorWithHexString:self.scannerRectCornerColor]; + + //画四个边角 + CGContextSetLineWidth(ctx, 2); + [self setStrokeColor:cornerLineColor withContext:ctx]; + + //左上角 + CGPoint poinsTopLeftA[] = { + CGPointMake(rect.origin.x+0.7, rect.origin.y), + CGPointMake(rect.origin.x+0.7 , rect.origin.y + 15) + }; + + CGPoint poinsTopLeftB[] = {CGPointMake(rect.origin.x, rect.origin.y +0.7),CGPointMake(rect.origin.x + 15, rect.origin.y+0.7)}; + [self addLine:poinsTopLeftA pointB:poinsTopLeftB ctx:ctx]; + + //左下角 + CGPoint poinsBottomLeftA[] = {CGPointMake(rect.origin.x+ 0.7, rect.origin.y + rect.size.height - 15),CGPointMake(rect.origin.x +0.7,rect.origin.y + rect.size.height)}; + CGPoint poinsBottomLeftB[] = {CGPointMake(rect.origin.x , rect.origin.y + rect.size.height - 0.7) ,CGPointMake(rect.origin.x+0.7 +15, rect.origin.y + rect.size.height - 0.7)}; + [self addLine:poinsBottomLeftA pointB:poinsBottomLeftB ctx:ctx]; + + //右上角 + CGPoint poinsTopRightA[] = {CGPointMake(rect.origin.x+ rect.size.width - 15, rect.origin.y+0.7),CGPointMake(rect.origin.x + rect.size.width,rect.origin.y +0.7 )}; + CGPoint poinsTopRightB[] = {CGPointMake(rect.origin.x+ rect.size.width-0.7, rect.origin.y),CGPointMake(rect.origin.x + rect.size.width-0.7,rect.origin.y + 15 +0.7 )}; + [self addLine:poinsTopRightA pointB:poinsTopRightB ctx:ctx]; + + CGPoint poinsBottomRightA[] = {CGPointMake(rect.origin.x+ rect.size.width -0.7 , rect.origin.y+rect.size.height+ -15),CGPointMake(rect.origin.x-0.7 + rect.size.width,rect.origin.y +rect.size.height )}; + CGPoint poinsBottomRightB[] = {CGPointMake(rect.origin.x+ rect.size.width - 15 , rect.origin.y + rect.size.height-0.7),CGPointMake(rect.origin.x + rect.size.width,rect.origin.y + rect.size.height - 0.7 )}; + [self addLine:poinsBottomRightA pointB:poinsBottomRightB ctx:ctx]; + CGContextStrokePath(ctx); +} +- (void)addLine:(CGPoint[])pointA pointB:(CGPoint[])pointB ctx:(CGContextRef)ctx { + CGContextAddLines(ctx, pointA, 2); + CGContextAddLines(ctx, pointB, 2); +} +#pragma mark - 功能方法 +//#pragma mark 定时器 +//- (void)createTimer { +// self.scanLineTimer = +// [NSTimer scheduledTimerWithTimeInterval:LINE_SCAN_TIME +// target:self +// selector:@selector(moveUpAndDownLine) +// userInfo:nil +// repeats:YES]; +//} +//#pragma mark 移动扫描线 +//- (void)moveUpAndDownLine:(CGRect)rect { +// CGRect readerFrame = self.frame; +// CGSize viewFinderSize = rect.size; +// CGRect scanLineframe = self.scanLine.frame; +// scanLineframe.origin.y = (readerFrame.size.height - viewFinderSize.height)/2; +// self.scanLine.frame = scanLineframe; +// self.scanLine.hidden = NO; +// __weak __typeof(self) weakSelf = self; +// [UIView animateWithDuration:LINE_SCAN_TIME - 0.05 +// animations:^{ +// CGRect scanLineframe = weakSelf.scanLine.frame; +// scanLineframe.origin.y = +// (readerFrame.size.height + viewFinderSize.height)/2 - +// weakSelf.scanLine.frame.size.height; +// weakSelf.scanLine.frame = scanLineframe; +// } +// completion:^(BOOL finished) { +// weakSelf.scanLine.hidden = YES; +// }]; +// +//} +//设置画笔颜色 +- (void)setStrokeColor:(UIColor *)color withContext:(CGContextRef)ctx{ + NSMutableArray *rgbColorArray = [UIColor colorArrayWithHexString:self.scannerRectCornerColor]; + CGFloat r = [rgbColorArray[0] floatValue]; + CGFloat g = [rgbColorArray[1] floatValue]; + CGFloat b = [rgbColorArray[2] floatValue]; + CGContextSetRGBStrokeColor(ctx,r,g,b,1); +} +#pragma mark - 辅助方法 +//将UIColor转换为RGB值 +- (NSMutableArray *) changeUIColorToRGB:(UIColor *)color +{ + NSMutableArray *RGBStrValueArr = [[NSMutableArray alloc] init]; + NSString *RGBStr = nil; + //获得RGB值描述 + NSString *RGBValue = [NSString stringWithFormat:@"%@",color]; + //将RGB值描述分隔成字符串 + NSArray *RGBArr = [RGBValue componentsSeparatedByString:@" "]; + //获取红色值 + float r = [[RGBArr objectAtIndex:1] floatValue] * 255; + RGBStr = [NSString stringWithFormat:@"%f",r]; + [RGBStrValueArr addObject:RGBStr]; + //获取绿色值 + float g = [[RGBArr objectAtIndex:2] intValue] * 255; + RGBStr = [NSString stringWithFormat:@"%f",g]; + [RGBStrValueArr addObject:RGBStr]; + //获取蓝色值 + float b = [[RGBArr objectAtIndex:3] intValue] * 255; + RGBStr = [NSString stringWithFormat:@"%f",b]; + [RGBStrValueArr addObject:RGBStr]; + //返回保存RGB值的数组 + return RGBStrValueArr; +} + + +@end diff --git a/ios/RCTBarcode/RCTBarcode/ScannerRect.h b/ios/RCTBarcode/RCTBarcode/ScannerRect.h new file mode 100644 index 0000000..1d78470 --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/ScannerRect.h @@ -0,0 +1,13 @@ +// +// ScannerRect.h +// RCTBarcode +// +// Created by cyqresig on 16/10/12. +// Copyright © 2016年 react-native-component. All rights reserved. +// + +#import + +@interface ScannerRect : NSObject + +@end diff --git a/ios/RCTBarcode/RCTBarcode/ScannerRect.m b/ios/RCTBarcode/RCTBarcode/ScannerRect.m new file mode 100644 index 0000000..2d36914 --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/ScannerRect.m @@ -0,0 +1,112 @@ + +#import "ScannerRect.h" +#import + +@implementation ScannerRect + +#pragma mark 画背景 +- (void)addScreenFillRect:(CGContextRef)ctx rect:(CGRect)rect { + CGContextSetRGBFillColor(ctx, 40 / 255.0,40 / 255.0,40 / 255.0,0.5); + CGContextFillRect(ctx, rect); //draw the transparent layer +} +#pragma mark 扣扫描框 +- (void)addCenterClearRect :(CGContextRef)ctx rect:(CGRect)rect { + CGContextClearRect(ctx, rect); //clear the center rect of the layer +} +#pragma mark 画框的白线 +- (void)addWhiteRect:(CGContextRef)ctx rect:(CGRect)rect { + CGContextStrokeRect(ctx, rect); + CGContextSetRGBStrokeColor(ctx, 1, 1, 1, 1); + CGContextSetLineWidth(ctx, 0.8); + CGContextAddRect(ctx, rect); + CGContextStrokePath(ctx); +} +#pragma mark 画扫描线 +- (void)addScanLine:(CGRect)rect{ + // self.scanLine = [[UIImageView alloc]initWithFrame:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 2)]; + + self.scanLine = [[LineView alloc] initWithFrame:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 2)]; + + self.scanLine.backgroundColor = [UIColor clearColor]; + // [self.scanLine setImage:[UIImage imageNamed:@"zbar-line"]]; + [self addSubview:self.scanLine]; +} +#pragma mark 画框的四个角 +- (void)addCornerLineWithContext:(CGContextRef)ctx rect:(CGRect)rect{ + + //画四个边角 + CGContextSetLineWidth(ctx, 2); + [self setStrokeColor:_cornerLineColor withContext:ctx]; + + //左上角 + CGPoint poinsTopLeftA[] = { + CGPointMake(rect.origin.x+0.7, rect.origin.y), + CGPointMake(rect.origin.x+0.7 , rect.origin.y + 15) + }; + + CGPoint poinsTopLeftB[] = {CGPointMake(rect.origin.x, rect.origin.y +0.7),CGPointMake(rect.origin.x + 15, rect.origin.y+0.7)}; + [self addLine:poinsTopLeftA pointB:poinsTopLeftB ctx:ctx]; + + //左下角 + CGPoint poinsBottomLeftA[] = {CGPointMake(rect.origin.x+ 0.7, rect.origin.y + rect.size.height - 15),CGPointMake(rect.origin.x +0.7,rect.origin.y + rect.size.height)}; + CGPoint poinsBottomLeftB[] = {CGPointMake(rect.origin.x , rect.origin.y + rect.size.height - 0.7) ,CGPointMake(rect.origin.x+0.7 +15, rect.origin.y + rect.size.height - 0.7)}; + [self addLine:poinsBottomLeftA pointB:poinsBottomLeftB ctx:ctx]; + + //右上角 + CGPoint poinsTopRightA[] = {CGPointMake(rect.origin.x+ rect.size.width - 15, rect.origin.y+0.7),CGPointMake(rect.origin.x + rect.size.width,rect.origin.y +0.7 )}; + CGPoint poinsTopRightB[] = {CGPointMake(rect.origin.x+ rect.size.width-0.7, rect.origin.y),CGPointMake(rect.origin.x + rect.size.width-0.7,rect.origin.y + 15 +0.7 )}; + [self addLine:poinsTopRightA pointB:poinsTopRightB ctx:ctx]; + + CGPoint poinsBottomRightA[] = {CGPointMake(rect.origin.x+ rect.size.width -0.7 , rect.origin.y+rect.size.height+ -15),CGPointMake(rect.origin.x-0.7 + rect.size.width,rect.origin.y +rect.size.height )}; + CGPoint poinsBottomRightB[] = {CGPointMake(rect.origin.x+ rect.size.width - 15 , rect.origin.y + rect.size.height-0.7),CGPointMake(rect.origin.x + rect.size.width,rect.origin.y + rect.size.height - 0.7 )}; + [self addLine:poinsBottomRightA pointB:poinsBottomRightB ctx:ctx]; + CGContextStrokePath(ctx); +} +- (void)addLine:(CGPoint[])pointA pointB:(CGPoint[])pointB ctx:(CGContextRef)ctx { + CGContextAddLines(ctx, pointA, 2); + CGContextAddLines(ctx, pointB, 2); +} +#pragma mark - 功能方法 +#pragma mark 定时器 +- (void)createTimer { + self.scanLineTimer = + [NSTimer scheduledTimerWithTimeInterval:LINE_SCAN_TIME + target:self + selector:@selector(moveUpAndDownLine) + userInfo:nil + repeats:YES]; +} +#pragma mark 移动扫描线 +- (void)moveUpAndDownLine { + CGRect readerFrame = self.frame; + CGSize viewFinderSize = _clearDrawRect.size; + CGRect scanLineframe = self.scanLine.frame; + scanLineframe.origin.y = (readerFrame.size.height - viewFinderSize.height)/2; + self.scanLine.frame = scanLineframe; + self.scanLine.hidden = NO; + __weak __typeof(self) weakSelf = self; + [UIView animateWithDuration:LINE_SCAN_TIME - 0.05 + animations:^{ + CGRect scanLineframe = weakSelf.scanLine.frame; + scanLineframe.origin.y = + (readerFrame.size.height + viewFinderSize.height)/2 - + weakSelf.scanLine.frame.size.height; + weakSelf.scanLine.frame = scanLineframe; + } + completion:^(BOOL finished) { + weakSelf.scanLine.hidden = YES; + }]; + +} +//设置画笔颜色 +- (void)setStrokeColor:(UIColor *)color withContext:(CGContextRef)ctx{ + NSMutableArray *rgbColorArray = [self changeUIColorToRGB:color]; + CGFloat r = [rgbColorArray[0] floatValue]; + CGFloat g = [rgbColorArray[1] floatValue]; + CGFloat b = [rgbColorArray[2] floatValue]; + CGContextSetRGBStrokeColor(ctx,r,g,b,1); +} + + + +@end diff --git a/ios/RCTBarcode/RCTBarcode/UIColor+Hex.h b/ios/RCTBarcode/RCTBarcode/UIColor+Hex.h new file mode 100644 index 0000000..7e23e89 --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/UIColor+Hex.h @@ -0,0 +1,15 @@ +#import + +#define RGBA_COLOR(R, G, B, A) [UIColor colorWithRed:((R) / 255.0f) green:((G) / 255.0f) blue:((B) / 255.0f) alpha:A] +#define RGB_COLOR(R, G, B) [UIColor colorWithRed:((R) / 255.0f) green:((G) / 255.0f) blue:((B) / 255.0f) alpha:1.0f] + +@interface UIColor (Hex) + ++ (UIColor *)colorWithHexString:(NSString *)color; ++ (NSMutableArray *)colorArrayWithHexString:(NSString *)color; + +//从十六进制字符串获取颜色, +//color:支持@“#123456”、 @“0X123456”、 @“123456”三种格式 ++ (UIColor *)colorWithHexString:(NSString *)color alpha:(CGFloat)alpha; ++ (NSMutableArray *)colorArrayWithHexString:(NSString *)color alpha:(CGFloat)alpha; +@end diff --git a/ios/RCTBarcode/RCTBarcode/UIColor+Hex.m b/ios/RCTBarcode/RCTBarcode/UIColor+Hex.m new file mode 100644 index 0000000..405f3a2 --- /dev/null +++ b/ios/RCTBarcode/RCTBarcode/UIColor+Hex.m @@ -0,0 +1,122 @@ +#import "UIColor+Hex.h" + +@implementation UIColor (Hex) + ++ (UIColor *)colorWithHexString:(NSString *)color alpha:(CGFloat)alpha +{ + //删除字符串中的空格 + NSString *cString = [[color stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; + // String should be 6 or 8 characters + if ([cString length] < 6) + { + return [UIColor clearColor]; + } + // strip 0X if it appears + //如果是0x开头的,那么截取字符串,字符串从索引为2的位置开始,一直到末尾 + if ([cString hasPrefix:@"0X"]) + { + cString = [cString substringFromIndex:2]; + } + //如果是#开头的,那么截取字符串,字符串从索引为1的位置开始,一直到末尾 + if ([cString hasPrefix:@"#"]) + { + cString = [cString substringFromIndex:1]; + } + if ([cString length] != 6) + { + return [UIColor clearColor]; + } + + // Separate into r, g, b substrings + NSRange range; + range.location = 0; + range.length = 2; + //r + NSString *rString = [cString substringWithRange:range]; + //g + range.location = 2; + NSString *gString = [cString substringWithRange:range]; + //b + range.location = 4; + NSString *bString = [cString substringWithRange:range]; + + // Scan values + unsigned int r, g, b; + [[NSScanner scannerWithString:rString] scanHexInt:&r]; + [[NSScanner scannerWithString:gString] scanHexInt:&g]; + [[NSScanner scannerWithString:bString] scanHexInt:&b]; + return [UIColor colorWithRed:((float)r / 255.0f) green:((float)g / 255.0f) blue:((float)b / 255.0f) alpha:alpha]; +} + +//默认alpha值为1 ++ (UIColor *)colorWithHexString:(NSString *)color +{ + return [self colorWithHexString:color alpha:1.0f]; +} + ++ (NSMutableArray *)colorArrayWithHexString:(NSString *)color alpha:(CGFloat)alpha +{ + //删除字符串中的空格 + NSString *cString = [[color stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; + // String should be 6 or 8 characters + if ([cString length] < 6) + { + return [UIColor clearColor]; + } + // strip 0X if it appears + //如果是0x开头的,那么截取字符串,字符串从索引为2的位置开始,一直到末尾 + if ([cString hasPrefix:@"0X"]) + { + cString = [cString substringFromIndex:2]; + } + //如果是#开头的,那么截取字符串,字符串从索引为1的位置开始,一直到末尾 + if ([cString hasPrefix:@"#"]) + { + cString = [cString substringFromIndex:1]; + } + if ([cString length] != 6) + { + return [UIColor clearColor]; + } + + // Separate into r, g, b substrings + NSRange range; + range.location = 0; + range.length = 2; + //r + NSString *rString = [cString substringWithRange:range]; + //g + range.location = 2; + NSString *gString = [cString substringWithRange:range]; + //b + range.location = 4; + NSString *bString = [cString substringWithRange:range]; + + // Scan values + unsigned int r, g, b; + [[NSScanner scannerWithString:rString] scanHexInt:&r]; + [[NSScanner scannerWithString:gString] scanHexInt:&g]; + [[NSScanner scannerWithString:bString] scanHexInt:&b]; + NSMutableArray *RGBStrValueArr = [[NSMutableArray alloc] init]; + + NSString *RGBStr = nil; + RGBStr = [NSString stringWithFormat:@"%f",((float)r / 255.0f)]; + [RGBStrValueArr addObject:RGBStr]; + + RGBStr = [NSString stringWithFormat:@"%f",((float)g / 255.0f)]; + [RGBStrValueArr addObject:RGBStr]; + + RGBStr = [NSString stringWithFormat:@"%f",((float)b / 255.0f)]; + [RGBStrValueArr addObject:RGBStr]; + //返回保存RGB值的数组 + return RGBStrValueArr; +// return [UIColor colorWithRed:((float)r / 255.0f) green:((float)g / 255.0f) blue:((float)b / 255.0f) alpha:alpha]; +} + +//默认alpha值为1 ++ (NSMutableArray *)colorArrayWithHexString:(NSString *)color +{ + return [self colorArrayWithHexString:color alpha:1.0f]; +} + +@end diff --git a/ios/raw/beep.wav b/ios/raw/beep.wav new file mode 100644 index 0000000000000000000000000000000000000000..120affda041648c128ac43367e68b1ab35c64043 GIT binary patch literal 20044 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