zxing扫描添加闪光灯和自定义扫描识别框,修改识别区域

之前在开发一个项目的时候,项目需求有二维码扫描识别,扫描界面类似微信扫描,但是扫描框要偏上,还要有闪光灯功能。

我找了很多的资料,都没有一步到位的,后来也是一点点修改BUG,慢慢的找代码,还要跳出各种坑,所以首先写这个博客为了以后自己开发方便,同时也方便大家使用。技术水平有限,各位大牛大神请轻喷,谢谢。


最开始我找了很多精简的zxing,里面代码都差不多,选了其中一个,在此基础上修改的,这是我修改后的:

点击打开链接


1.修改预览变形问题,com.zxing.camera.CameraConfigurationManager这个类里修改一行代码,在第146行改为:

float newDiff = Math.abs(screenResolution.x * 1.0f / newY - screenResolution.y * 1.0f / newX);

2.修改扫描动画,就是自定义view,在com.zxing.view.ViewfinderView修改代码(之前是带机器人和点的),修改扫描线和四个角的颜色:

 //扫描线的颜色渐变
  public int[] colors = new int[]{0x3303d5fb, 0xff03d5fb, 0x3303d5fb};
  public float[] position = new float[]{0f, 0.5f, 1f};
  //四个角的颜色
  public int conerColor = 0xff03d5fb;

主要是在onDraw里做修改:

  @Override
  public void onDraw(Canvas canvas) {
    Rect frame = CameraManager.get().getFramingRect();
    if (frame == null) {
      return;
    }
    int width = canvas.getWidth();
    int height = canvas.getHeight();

    //描绘四周半透明
    paint.setColor(resultBitmap != null ? resultColor : maskColor);
    canvas.drawRect(0, 0, width, frame.top, paint);
    canvas.drawRect(0, frame.top, frame.left, frame.bottom, paint);
    canvas.drawRect(frame.right, frame.top, width, frame.bottom, paint);
    canvas.drawRect(0, frame.bottom, 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 {


      //绘制4个角
      paint.setColor(conerColor);//定义画笔的颜色
      
      ///为了适配屏幕修改的/
      int lineLong = 70 * widthPixels/1080;
      int lineWidth = 10 * widthPixels/1080;
      canvas.drawRect(frame.left, frame.top, frame.left + lineLong, frame.top + lineWidth, paint);
      canvas.drawRect(frame.left, frame.top, frame.left + lineWidth, frame.top + lineLong, paint);

      canvas.drawRect(frame.right - lineLong, frame.top, frame.right, frame.top + lineWidth, paint);
      canvas.drawRect(frame.right - lineWidth, frame.top, frame.right, frame.top + lineLong, paint);

      canvas.drawRect(frame.left, frame.bottom - lineWidth, frame.left + lineLong, frame.bottom, paint);
      canvas.drawRect(frame.left, frame.bottom - lineLong, frame.left + lineWidth, frame.bottom, paint);

      canvas.drawRect(frame.right - lineLong, frame.bottom - lineWidth, frame.right, frame.bottom, paint);
      canvas.drawRect(frame.right - lineWidth, frame.bottom - lineLong, frame.right, frame.bottom, paint);



      if (resultBitmap != null) {
        // Draw the opaque result bitmap over the scanning rectangle
        paint.setAlpha(CURRENT_POINT_OPACITY);
        canvas.drawBitmap(resultBitmap, null, frame, paint);
      } else {
        //  paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
        //  scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
        int middle = frame.height() / 2 + frame.top;
        laserLinePosition = laserLinePosition + 5*widthPixels/1080;
        if (laserLinePosition > frame.height()) {
          laserLinePosition = 0;
        }
        linearGradient = new LinearGradient(frame.left + 1, frame.top + laserLinePosition, frame.right - 1, frame.top + 10 + laserLinePosition, colors, position, Shader.TileMode.CLAMP);
        // Draw a red "laser scanner" line through the middle to show decoding is active

        //paint.setColor(laserColor);
        paint.setShader(linearGradient);

        //绘制扫描线
        canvas.drawRect(frame.left + 1, frame.top + laserLinePosition, frame.right - 1, frame.top + lineWidth + laserLinePosition, paint);
        paint.setShader(null);
        float scaleX = frame.width() / 50.0f;
        float scaleY = frame.height() / 50.0f;

        Collection<ResultPoint> currentPossible = possibleResultPoints;
        Collection<ResultPoint> currentLast = lastPossibleResultPoints;
        int frameLeft = frame.left;
        int frameTop = frame.top;
        if (currentPossible.isEmpty()) {
          lastPossibleResultPoints = null;
        } else {
          possibleResultPoints = new ArrayList<>(5);
          lastPossibleResultPoints = currentPossible;
          paint.setAlpha(CURRENT_POINT_OPACITY);
          paint.setColor(resultPointColor);
          for (ResultPoint point : currentPossible) {
            canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                    frameTop + (int) (point.getY() * scaleY),
                    POINT_SIZE, paint);
          }
        }
        if (currentLast != null) {
          paint.setAlpha(CURRENT_POINT_OPACITY / 2);
          paint.setColor(resultPointColor);
          float radius = POINT_SIZE / 2.0f;
          for (ResultPoint point : currentLast) {
            canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                    frameTop + (int) (point.getY() * scaleY),
                    radius, paint);
          }
        }
      }


      //刷新控制
      postInvalidateDelayed(10, frame.left, frame.top, frame.right, frame.bottom);
    }
  }

3.修改com.zxing.camera.CameraManager这个类,这个类修改最多,这个类里添加闪光灯需要的camera对象的获取方法,还有控制识别框大小和位置:

控制识别框大小和位置的关键代码,适配系数还有Rect的位置根据自己的需求修改:

  public Rect getFramingRect() {
    Point screenResolution = configManager.getScreenResolution();
    if (framingRect == null) {
      if (camera == null) {
        return null;
      }

      //屏幕适配控制系数screenResolution.x/800
      int width = screenResolution.x * 8 / 13;
      int leftOffset = (screenResolution.x - width) / 2;
      int topOffset = screenResolution.y * 5 / 13 - width / 2;
      framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + width);
      Log.d(TAG, "Calculated framing rect: " + framingRect);
    }
    return framingRect;
  }

因为要改的很多,直接贴CameraManager类的代码:

package com.zxing.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;

public final class CameraManager {

  private static final String TAG = CameraManager.class.getSimpleName();

  private static CameraManager cameraManager;

  static final int SDK_INT; // Later we can use Build.VERSION.SDK_INT
  static {
    int sdkInt;
    try {
      sdkInt = Integer.parseInt(Build.VERSION.SDK);
    } catch (NumberFormatException nfe) {
      // Just to be safe
      sdkInt = 10000;
    }
    SDK_INT = sdkInt;
  }

  private final Context context;
  private final CameraConfigurationManager configManager;
  private static Camera camera;
  private Rect framingRect;
  private Rect framingRectInPreview;
  private boolean initialized;
  private static boolean previewing;
  private static boolean useOneShotPreviewCallback;

  private static PreviewCallback previewCallback;

  private static AutoFocusCallback autoFocusCallback;


  public static void init(Context context) {
    if (cameraManager == null) {
      cameraManager = new CameraManager(context);
    }
  }

  /**
   * 获取相机,用于打开闪光灯
   * @return
   */
  public static Camera getCamera(){
    return camera;
  }

  public static CameraManager get() {
    return cameraManager;
  }

  private CameraManager(Context context) {

    this.context = context;
    this.configManager = new CameraConfigurationManager(context);

  
    useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > 3; // 3 = Cupcake

    previewCallback = new PreviewCallback(configManager, useOneShotPreviewCallback);
    autoFocusCallback = new AutoFocusCallback();
  }


  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);

      FlashlightManager.enableFlashlight();
    } else {
      camera.setPreviewDisplay(holder);
    }
  }


  public void closeDriver() {
    if (camera != null) {
      FlashlightManager.disableFlashlight();
      if (previewing){
        camera.release();
      }
      camera.release();
      camera = null;
      previewing = false;
    }
  }


  public void startPreview() {
    if (camera != null && !previewing) {
      camera.startPreview();
      previewing = true;
    }
  }


  public static void stopPreview() {
    if (camera != null && previewing) {
      if (!useOneShotPreviewCallback) {
        camera.setPreviewCallback(null);
      }
      camera.stopPreview();
      previewCallback.setHandler(null, 0);
      autoFocusCallback.setHandler(null, 0);
      previewing = false;
    }
  }


  public void requestPreviewFrame(Handler handler, int message) {
    if (camera != null && previewing) {
      previewCallback.setHandler(handler, message);
      if (useOneShotPreviewCallback) {
        camera.setOneShotPreviewCallback(previewCallback);
      } else {
        camera.setPreviewCallback(previewCallback);
      }
    }
  }


  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);
    }
  }


  public Rect getFramingRect() {
    Point screenResolution = configManager.getScreenResolution();
    if (framingRect == null) {
      if (camera == null) {
        return null;
      }

      //屏幕适配控制系数screenResolution.x/800
      int width = screenResolution.x * 8 / 13;
      int leftOffset = (screenResolution.x - width) / 2;
      int topOffset = screenResolution.y * 5 / 13 - width / 2;
      framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + width);
      Log.d(TAG, "Calculated framing rect: " + framingRect);
    }
    return framingRect;
  }

 
  public Rect getFramingRectInPreview() {
    if (framingRectInPreview == null) {
      Rect rect = new Rect(getFramingRect());
      Point cameraResolution = configManager.getCameraResolution();
      Point screenResolution = configManager.getScreenResolution();
      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;
  }

  public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
    Rect rect = getFramingRectInPreview();
    int previewFormat = configManager.getPreviewFormat();
    String previewFormatString = configManager.getPreviewFormatString();
    switch (previewFormat) {
   
      case PixelFormat.YCbCr_420_SP:
 
      case PixelFormat.YCbCr_422_SP:
        return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
            rect.width(), rect.height());
      default:
      
        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 Context getContext() {
		return context;
	}

}

到这一步,已经算修改好我的项目需求了,如果想想偷懒,可以直接在com.zxing.activity.CaptureActivity类里做修改,把布局改成需要的样子就可以了。

但是,这个类里代码又太多,又不想改动多了会乱,我的方法是自己写个类来继承CaptureActivity,在这个自定义类里自己写都不会怕改错CaptureActivity类,所以,首先对CaptureActivity稍做修改:

布局:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <SurfaceView
        android:id="@+id/preview_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

    <com.zxing.view.ViewfinderView
        android:id="@+id/viewfinder_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <LinearLayout
        android:id="@+id/child_container_ll"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" />

</FrameLayout>

在CaptureActivity里的onCreate方法修改一下(有些需要用到的方法或者对象,要改为public才能给子类调用):

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.capture);
		//ViewUtil.addTopView(getApplicationContext(), this, R.string.scan_card);
		CameraManager.init(getApplication());
		viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
		hasSurface = false;
		inactivityTimer = new InactivityTimer(this);

		childConrainer = (LinearLayout)findViewById(R.id.child_container_ll);
		camera = CameraManager.getCamera();
	}

下面是我自定义的类MyZxingActivity,主要是将自定义的布局插入到父类的布局里:

public class MyZxingActivity extends CaptureActivity {
    private CheckBox flash;
    private Camera camera;
    private Camera.Parameters parameter;
    private RadioGroup radioGroup;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        View view = View.inflate(this, R.layout.activity_my_zxing, null);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        childContainer.addView(view, 0, layoutParams);

        initView();
    }

    private void initView() {

        //控制闪光灯
        flash = (CheckBox) findViewById(R.id.cb_cancel_scan);
        flash.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                camera = CameraManager.getCamera();
                parameter = camera.getParameters();
                // TODO 开灯
                if (b) {
                    parameter.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                    camera.setParameters(parameter);
                } else {  // 关灯
                    parameter.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                    camera.setParameters(parameter);
                }
            }
        });
         
    }
}
自定义类的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/dimen_50_dip"
        android:background="@color/yuto_black_style">

        <ImageView
            android:id="@+id/finish_capture_iv"
            android:layout_width="@dimen/dimen_66_dip"
            android:layout_height="match_parent"
            android:paddingTop="@dimen/dimen_12_dip"
            android:src="@drawable/back"
            android:paddingBottom="@dimen/dimen_12_dip"
            android:paddingRight="@dimen/dimen_35_dip"
            android:layout_marginLeft="@dimen/dimen_15_dip"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="@string/scan_top_title"
            android:textColor="#fff"
            android:textSize="@dimen/dimen_23_dip" />
        <ImageView
            android:id="@+id/set_iv"
            android:layout_width="@dimen/dimen_66_dip"
            android:layout_height="match_parent"
            android:paddingTop="@dimen/dimen_12_dip"
            android:paddingBottom="@dimen/dimen_12_dip"
            android:paddingLeft="@dimen/dimen_35_dip"
            android:src="@mipmap/set"
            android:layout_centerVertical="true"
            android:layout_alignParentRight="true"
            android:layout_marginRight="10dp"/>
    </RelativeLayout>

    <TextView
        android:id="@+id/tip_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/scan_center_tip"
        android:textColor="#fff"
        android:layout_centerInParent="true"
        android:textSize="@dimen/dimen_18_dip"
        android:gravity="center"/>

    <CheckBox
        android:id="@+id/cb_cancel_scan"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/tip_tv"
        android:layout_marginTop="@dimen/dimen_20_dip"
        android:layout_centerHorizontal="true"
        android:text="@string/scan_center_flash"
        android:gravity="center_horizontal"
        android:textSize="@dimen/dimen_18_dip"
        android:textColor="@color/selector_color_main_text"
        android:button="@null"
        android:drawableTop="@drawable/selector_scan_flash"
        android:drawablePadding="5dp"/>

    <RadioGroup
        android:id="@+id/capture_rg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="@dimen/dimen_60_dip"
        android:orientation="horizontal">
        <RadioButton
            android:id="@+id/capture_ar_rb"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/scan_bottom_ar"
            android:textSize="@dimen/dimen_18_dip"
            android:textColor="@color/selector_color_main_text"
            android:gravity="center"
            android:button="@null"
            android:drawableTop="@drawable/selector_scan_ar"
            android:drawablePadding="@dimen/dimen_10_dip"/>
        <RadioButton
            android:id="@+id/capture_qr_rb"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/scan_bottom_qr"
            android:textSize="@dimen/dimen_18_dip"
            android:textColor="@color/selector_color_main_text"
            android:checked="true"
            android:gravity="center"
            android:button="@null"
            android:drawableTop="@drawable/selector_scan_qr"
            android:drawablePadding="@dimen/dimen_10_dip"/>
        <RadioButton
            android:id="@+id/capture_water_rb"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/scan_bottom_water"
            android:textSize="@dimen/dimen_18_dip"
            android:textColor="@color/selector_color_main_text"
            android:gravity="center"
            android:button="@null"
            android:drawableTop="@drawable/selector_scan_water"
            android:drawablePadding="@dimen/dimen_10_dip"/>
    </RadioGroup>
</RelativeLayout>

如果想自己处理二维码扫描到的信息,重写handleDecode()方法,记得去掉super就行。


这是我修改的zxing,是一个module,import进去:



再在“app”这个module依赖这个zxing即可。

链接在这里→→修改好的zxing

本人水平有限,有不足之处欢迎批评指正,谢谢。



  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值