Home >Java >javaTutorial >Android implements various QR code scanning effects based on google Zxing

Android implements various QR code scanning effects based on google Zxing

高洛峰
高洛峰Original
2017-01-07 10:49:081686browse

With the arrival of WeChat, QR codes are becoming more and more popular. You can see QR codes everywhere, such as in shopping malls, KFC, restaurants, etc. For QR code scanning we use Google’s open source framework Zxing. We can go to http://code.google.com/p/zxing/ to download the source code and Jar package. Before, the QR code scanning function in my project only implemented the scanning function, and its UI was really ugly. A good The UI interface of an application software must be accepted by the public, otherwise people will not use your software. Therefore, application software functions are as important as the interface. For example, WeChat, I believe that the WeChat UI has been imitated by many application software. I also imitated the effect of scanning the QR code on WeChat. Although it is not as refined as WeChat does, the effect is still good, so I will share with you the code for modifying the UI and the code for scanning the QR code. First, I will encounter problems in future projects. Directly copy and use the same function. The second is to give a reference to those who have not added the QR code function. Standing on the shoulders of giants, haha. I also stood on the shoulders of giants before adding this function. Next, follow I implemented this function step by step, removing many unnecessary files

Let’s look at the structure of the project first

Android基于google Zxing实现各类二维码扫描效果

If you want to do the same for your project To add this function, you directly copy the three packages com.mining.app.zxing.camera, com.mining.app.zxing.decoding, and com.mining.app.zxing.view to your project, and then introduce the relevant When entering the corresponding resources, I quoted them directly from my project. The package name has not been changed. Of course, I also need to quote Zxing.jar

com.example.qr_codescan package, which contains a MipcaActivityCapture , it also directly introduces the code of my previous project. This Activity mainly handles the scanning interface classes. For example, if the scan is successful, there will be sounds and vibrations, etc. It mainly focuses on the handleDecode (Result result, Bitmap barcode) method inside. After the scan is completed, the scan will The initial parameters of the result and the bitmap of the QR code are passed to handleDecode (Result result, Bitmap barcode). We only need to write the corresponding processing code in it. There is no need to change other places. I will handle the scanning here. Results and photos taken by scanning

/** 
 * 处理扫描结果 
 * @param result 
 * @param barcode 
 */
public void handleDecode(Result result, Bitmap barcode) { 
  inactivityTimer.onActivity(); 
  playBeepSoundAndVibrate(); 
  String resultString = result.getText(); 
  if (resultString.equals("")) { 
    Toast.makeText(MipcaActivityCapture.this, "Scan failed!", Toast.LENGTH_SHORT).show(); 
  }else { 
    Intent resultIntent = new Intent(); 
    Bundle bundle = new Bundle(); 
    bundle.putString("result", resultString); 
    bundle.putParcelable("bitmap", barcode); 
    resultIntent.putExtras(bundle); 
    this.setResult(RESULT_OK, resultIntent); 
  } 
  MipcaActivityCapture.this.finish(); 
}

I made my own changes to the layout of the MipcaActivityCapture interface. Let’s take a look at the renderings first. I mainly use FrameLayout, with RelativeLayout nested inside.

Android基于google Zxing实现各类二维码扫描效果

The layout code is as follows

<?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" >
  
  <RelativeLayout
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >
  
    <SurfaceView
      android:id="@+id/preview_view"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:layout_gravity="center" />
  
    <com.mining.app.zxing.view.ViewfinderView
      android:id="@+id/viewfinder_view"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content" />
  
    <include
      android:id="@+id/include1"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_alignParentTop="true"
      layout="@layout/activity_title" />
  </RelativeLayout>
  
</FrameLayout>

In it I wrote the upper part of the interface in another layout , and then include it, because this activity_title is also used by other activities in my project, I also copied it directly

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:background="@drawable/mmtitle_bg_alpha" >
  
  <Button
    android:id="@+id/button_back"
    android:layout_width="75.0dip"
    android:text="返回"
    android:background="@drawable/mm_title_back_btn"
    android:textColor="@android:color/white"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_marginLeft="2dip" />
  
  <TextView
    android:id="@+id/textview_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignBaseline="@+id/button_back"
    android:layout_alignBottom="@+id/button_back"
    android:layout_centerHorizontal="true"
    android:gravity="center_vertical"
    android:text="二维码扫描"
    android:textColor="@android:color/white"
    android:textSize="18sp" />
  
</RelativeLayout>

In my demo, There is a main interface MainActivity with a Button, an ImageView and a TextView. Click the Button to enter the QR code scanning interface. When the scan is OK, return to the main interface and display the scanned results to TextView and the image to ImageView. Inside, you don’t need to process pictures. I’ll add pictures here. The layout of the main interface is very simple as follows

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="#ffe1e0de" >
  
  <Button
    android:id="@+id/button1"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true"
    android:text="扫描二维码" />
  
  <TextView
    android:id="@+id/result"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_below="@+id/button1"
    android:lines="2"
    android:gravity="center_horizontal"
    android:textColor="@android:color/black"
    android:textSize="16sp" />
  
  <ImageView
    android:id="@+id/qrcode_bitmap"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_alignParentLeft="true"
    android:layout_below="@+id/result"/>
</RelativeLayout>

The code inside MainActivity is as follows, inside The function has been mentioned above

package com.example.qr_codescan;
  
  
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
  
public class MainActivity extends Activity {
  private final static int SCANNIN_GREQUEST_CODE = 1;
  /**
   * 显示扫描结果
   */
  private TextView mTextView ;
  /**
   * 显示扫描拍的图片
   */
  private ImageView mImageView;
    
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
      
    mTextView = (TextView) findViewById(R.id.result); 
    mImageView = (ImageView) findViewById(R.id.qrcode_bitmap);
      
    //点击按钮跳转到二维码扫描界面,这里用的是startActivityForResult跳转
    //扫描完了之后调到该界面
    Button mButton = (Button) findViewById(R.id.button1);
    mButton.setOnClickListener(new OnClickListener() {
        
      @Override
      public void onClick(View v) {
        Intent intent = new Intent();
        intent.setClass(MainActivity.this, MipcaActivityCapture.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivityForResult(intent, SCANNIN_GREQUEST_CODE);
      }
    });
  }
    
    
  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {
    case SCANNIN_GREQUEST_CODE:
      if(resultCode == RESULT_OK){
        Bundle bundle = data.getExtras();
        //显示扫描到的内容
        mTextView.setText(bundle.getString("result"));
        //显示
        mImageView.setImageBitmap((Bitmap) data.getParcelableExtra("bitmap"));
      }
      break;
    }
  }  
  
}

The above code is still relatively simple, but if you want to make a scanning box like WeChat, follow the above The code does not have that effect. We must rewrite the ViewfinderView class under the com.mining.app.zxing.view package. The pictures in WeChat are all used. I drew them myself. The code comments are relatively clear. Everyone Just look at the code, I believe you can understand it. If you want to modify the size of the scanning frame, go to the CameraManager class and modify

/*
 * 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
 *
 *   *
 * 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.mining.app.zxing.view;
  
import java.util.Collection;
import java.util.HashSet;
  
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;
  
import com.example.qr_codescan.R;
import com.google.zxing.ResultPoint;
import com.mining.app.zxing.camera.CameraManager;
  
/**
 * 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.
 *
 */
public final class ViewfinderView extends View {
  private static final String TAG = "log";
  /**
   * 刷新界面的时间
   */
  private static final long ANIMATION_DELAY = 10L;
  private static final int OPAQUE = 0xFF;
  
  /**
   * 四个绿色边角对应的长度
   */
  private int ScreenRate;
    
  /**
   * 四个绿色边角对应的宽度
   */
  private static final int CORNER_WIDTH = 10;
  /**
   * 扫描框中的中间线的宽度
   */
  private static final int MIDDLE_LINE_WIDTH = 6;
    
  /**
   * 扫描框中的中间线的与扫描框左右的间隙
   */
  private static final int MIDDLE_LINE_PADDING = 5;
    
  /**
   * 中间那条线每次刷新移动的距离
   */
  private static final int SPEEN_DISTANCE = 5;
    
  /**
   * 手机的屏幕密度
   */
  private static float density;
  /**
   * 字体大小
   */
  private static final int TEXT_SIZE = 16;
  /**
   * 字体距离扫描框下面的距离
   */
  private static final int TEXT_PADDING_TOP = 30;
    
  /**
   * 画笔对象的引用
   */
  private Paint paint;
    
  /**
   * 中间滑动线的最顶端位置
   */
  private int slideTop;
    
  /**
   * 中间滑动线的最底端位置
   */
  private int slideBottom;
    
  private Bitmap resultBitmap;
  private final int maskColor;
  private final int resultColor;
    
  private final int resultPointColor;
  private Collection<ResultPoint> possibleResultPoints;
  private Collection<ResultPoint> lastPossibleResultPoints;
  
  boolean isFirst;
    
  public ViewfinderView(Context context, AttributeSet attrs) {
    super(context, attrs);
      
    density = context.getResources().getDisplayMetrics().density;
    //将像素转换成dp
    ScreenRate = (int)(20 * density);
  
    paint = new Paint();
    Resources resources = getResources();
    maskColor = resources.getColor(R.color.viewfinder_mask);
    resultColor = resources.getColor(R.color.result_view);
  
    resultPointColor = resources.getColor(R.color.possible_result_points);
    possibleResultPoints = new HashSet<ResultPoint>(5);
  }
  
  @Override
  public void onDraw(Canvas canvas) {
    //中间的扫描框,你要修改扫描框的大小,去CameraManager里面修改
    Rect frame = CameraManager.get().getFramingRect();
    if (frame == null) {
      return;
    }
      
    //初始化中间线滑动的最上边和最下边
    if(!isFirst){
      isFirst = true;
      slideTop = frame.top;
      slideBottom = frame.bottom;
    }
      
    //获取屏幕的宽和高
    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 + 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 {
  
      //画扫描框边上的角,总共8个部分
      paint.setColor(Color.GREEN);
      canvas.drawRect(frame.left, frame.top, frame.left + ScreenRate,
          frame.top + CORNER_WIDTH, paint);
      canvas.drawRect(frame.left, frame.top, frame.left + CORNER_WIDTH, frame.top
          + ScreenRate, paint);
      canvas.drawRect(frame.right - ScreenRate, frame.top, frame.right,
          frame.top + CORNER_WIDTH, paint);
      canvas.drawRect(frame.right - CORNER_WIDTH, frame.top, frame.right, frame.top
          + ScreenRate, paint);
      canvas.drawRect(frame.left, frame.bottom - CORNER_WIDTH, frame.left
          + ScreenRate, frame.bottom, paint);
      canvas.drawRect(frame.left, frame.bottom - ScreenRate,
          frame.left + CORNER_WIDTH, frame.bottom, paint);
      canvas.drawRect(frame.right - ScreenRate, frame.bottom - CORNER_WIDTH,
          frame.right, frame.bottom, paint);
      canvas.drawRect(frame.right - CORNER_WIDTH, frame.bottom - ScreenRate,
          frame.right, frame.bottom, paint);
  
        
      //绘制中间的线,每次刷新界面,中间的线往下移动SPEEN_DISTANCE
      slideTop += SPEEN_DISTANCE;
      if(slideTop >= frame.bottom){
        slideTop = frame.top;
      }
      canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop - MIDDLE_LINE_WIDTH/2, frame.right - MIDDLE_LINE_PADDING,slideTop + MIDDLE_LINE_WIDTH/2, paint);
        
        
      //画扫描框下面的字
      paint.setColor(Color.WHITE);
      paint.setTextSize(TEXT_SIZE * density);
      paint.setAlpha(0x40);
      paint.setTypeface(Typeface.create("System", Typeface.BOLD));
      canvas.drawText(getResources().getString(R.string.scan_text), frame.left, (float) (frame.bottom + (float)TEXT_PADDING_TOP *density), paint);
        
        
  
      Collection<ResultPoint> currentPossible = possibleResultPoints;
      Collection<ResultPoint> currentLast = lastPossibleResultPoints;
      if (currentPossible.isEmpty()) {
        lastPossibleResultPoints = null;
      } else {
        possibleResultPoints = new HashSet<ResultPoint>(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);
        }
      }
  
        
      //只刷新扫描框的内容,其他地方不刷新
      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);
  }
  
}

In the above code, in the middle That line is the picture used by WeChat. I drew it here. If you want to be more simulated, change the following code

canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop - MIDDLE_LINE_WIDTH/2, frame.right - MIDDLE_LINE_PADDING,slideTop + MIDDLE_LINE_WIDTH/2, paint);

to

Rect lineRect = new Rect();
      lineRect.left = frame.left;
      lineRect.right = frame.right;
      lineRect.top = slideTop;
      lineRect.bottom = slideTop + 18;
      canvas.drawBitmap(((BitmapDrawable)(getResources().getDrawable(R.drawable.qrcode_scan_line))).getBitmap(), null, lineRect, paint);

Go and find the scan line in WeChat yourself. The one I posted is distorted. Download the WeChat apk, change the suffix name to zip, and then unzip it
The code for drawing the font under the scanning box needs to be modified so that it can be automatically arranged in the middle according to the font. If the word is too long, I have not processed it. It needs to be automatically wrapped. You can handle it yourself

paint.setColor(Color.WHITE); 
paint.setTextSize(TEXT_SIZE * density); 
paint.setAlpha(0x40); 
paint.setTypeface(Typeface.DEFAULT_BOLD); 
String text = getResources().getString(R.string.R.string.scan_text);
float textWidth = paint.measureText(text);
  
canvas.drawText(text, (width - textWidth)/2, (float) (frame.bottom + (float)TEXT_PADDING_TOP *density), paint)

Screenshot of the running interface. The green line in the middle will move up and down, which is similar to the effect of WeChat. Of course, you also need the corresponding permissions to run it.

Android基于google Zxing实现各类二维码扫描效果

Android基于google Zxing实现各类二维码扫描效果

Android基于google Zxing实现各类二维码扫描效果

The above is the entire content of this article. I hope it will be helpful for everyone to learn Android software programming.

For more Android-based articles on realizing various QR code scanning effects based on google Zxing, please pay attention to the PHP Chinese website!


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn