android 长按识别二维码

2016-06-01 13:45:20 shihuiyun 阅读数 7090

最近项目用到了二维码的生成与识别,之前没有接触这块,然后就上网搜了搜,发现有好多这方面的资源,特别是google Zxing对二维码的封装,实现的已经不错了,可以直接拿过来引用,下载了他们的源码后,只做了少少的改动,就是在Demo中增加了长按识别的功能,网上虽然也有长按识别的Demo,但好多下载下来却无法运行,然后总结了一下,加在了下面的Demo中。   



如图所示,引用时直接把用红色圈起来的包放在你项目所对应的文件夹下,当然一些资源文件,比如string.xml里项目的引用你自己添加上就是 当然别忘了放架包


下面来介绍这个Demo的主类

public class BarCodeTestActivity extends Activity {
   
private TextView resultTextView;
private EditText qrStrEditText;
private ImageView qrImgImageView;
private String time;
    private File file = null; 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);        
        resultTextView = (TextView) this.findViewById(R.id.tv_scan_result);
        qrStrEditText = (EditText) this.findViewById(R.id.et_qr_string);
        qrImgImageView = (ImageView) this.findViewById(R.id.iv_qr_image);
        
        Button scanBarCodeButton = (Button) this.findViewById(R.id.btn_scan_barcode);
        scanBarCodeButton.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
//打开扫描界面扫描条形码或二维码
Intent openCameraIntent = new Intent(BarCodeTestActivity.this,CaptureActivity.class);
startActivityForResult(openCameraIntent, 0);
}
});
        
        qrImgImageView.setOnLongClickListener(new OnLongClickListener() {

@Override
public boolean onLongClick(View v) {
// 长按识别二维码

  saveCurrentImage();
return true;
}
});
        
        


        
        Button generateQRCodeButton = (Button) this.findViewById(R.id.btn_add_qrcode);
        generateQRCodeButton.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
try {
String contentString = qrStrEditText.getText().toString();
if (!contentString.equals("")) {
//根据字符串生成二维码图片并显示在界面上,第二个参数为图片的大小(350*350)
Bitmap qrCodeBitmap = EncodingHandler.createQRCode(contentString, 350);
qrImgImageView.setImageBitmap(qrCodeBitmap);
}else {
//提示文本不能是空的
Toast.makeText(BarCodeTestActivity.this, "Text can not be empty", Toast.LENGTH_SHORT).show();
}

} catch (WriterException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
    }
 
    //这种方法状态栏是空白,显示不了状态栏的信息
    private void saveCurrentImage()
    {
        //获取当前屏幕的大小
        int width = getWindow().getDecorView().getRootView().getWidth();
        int height = getWindow().getDecorView().getRootView().getHeight();
        //生成相同大小的图片
        Bitmap temBitmap = Bitmap.createBitmap( width, height, Bitmap.Config.ARGB_8888 );
        //找到当前页面的根布局
        View view =  getWindow().getDecorView().getRootView();
        //设置缓存
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();
        //从缓存中获取当前屏幕的图片,创建一个DrawingCache的拷贝,因为DrawingCache得到的位图在禁用后会被回收
        temBitmap = view.getDrawingCache();
        SimpleDateFormat df = new SimpleDateFormat("yyyymmddhhmmss");
        time = df.format(new Date());
        if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
            file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/screen",time + ".png");
            if(!file.exists()){
                file.getParentFile().mkdirs();
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(file);
                temBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
                fos.flush();
                fos.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/screen/" + time + ".png";
                    final Result result = parseQRcodeBitmap(path);
                    runOnUiThread(new Runnable() {
                        public void run() {
                        if(null!=result){
                        resultTextView.setText(result.toString());
                        }else{
                            Toast.makeText(BarCodeTestActivity.this, "无法识别", Toast.LENGTH_LONG).show();
                        }
                        }
                    });
                }
            }).start();
            //禁用DrawingCahce否则会影响性能 ,而且不禁止会导致每次截图到保存的是缓存的位图
            view.setDrawingCacheEnabled(false);
        }
    }
    
    
    
    
    
    //解析二维码图片,返回结果封装在Result对象中  
    private com.google.zxing.Result  parseQRcodeBitmap(String bitmapPath){  
        //解析转换类型UTF-8  
        Hashtable<DecodeHintType, String> hints = new Hashtable<DecodeHintType, String>();  
        hints.put(DecodeHintType.CHARACTER_SET, "utf-8");  
        //获取到待解析的图片  
        BitmapFactory.Options options = new BitmapFactory.Options();   
        //如果我们把inJustDecodeBounds设为true,那么BitmapFactory.decodeFile(String path, Options opt)  
        //并不会真的返回一个Bitmap给你,它仅仅会把它的宽,高取回来给你  
        options.inJustDecodeBounds = true;  
        //此时的bitmap是null,这段代码之后,options.outWidth 和 options.outHeight就是我们想要的宽和高了  
        Bitmap bitmap = BitmapFactory.decodeFile(bitmapPath,options);  
        //我们现在想取出来的图片的边长(二维码图片是正方形的)设置为400像素  
        /** 
            options.outHeight = 400; 
            options.outWidth = 400; 
            options.inJustDecodeBounds = false; 
            bitmap = BitmapFactory.decodeFile(bitmapPath, options); 
        */  
        //以上这种做法,虽然把bitmap限定到了我们要的大小,但是并没有节约内存,如果要节约内存,我们还需要使用inSimpleSize这个属性  
        options.inSampleSize = options.outHeight / 400;  
        if(options.inSampleSize <= 0){  
            options.inSampleSize = 1; //防止其值小于或等于0  
        }  
        /** 
         * 辅助节约内存设置 
         *  
         * options.inPreferredConfig = Bitmap.Config.ARGB_4444;    // 默认是Bitmap.Config.ARGB_8888 
         * options.inPurgeable = true;  
         * options.inInputShareable = true;  
         */  
        options.inJustDecodeBounds = false;  
        bitmap = BitmapFactory.decodeFile(bitmapPath, options);   
        //新建一个RGBLuminanceSource对象,将bitmap图片传给此对象  
        RGBLuminanceSource rgbLuminanceSource = new RGBLuminanceSource(bitmap);  
        //将图片转换成二进制图片  
        BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(rgbLuminanceSource));  
        //初始化解析对象  
        QRCodeReader reader = new QRCodeReader();  
        //开始解析  
        Result result = null;  
        try {  
            result = reader.decode(binaryBitmap, hints);  
        } catch (Exception e) {  
            // TODO: handle exception  
        }  
          
        return result;  
    }  
  
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//处理扫描结果(在界面上显示)
if (resultCode == RESULT_OK) {
Bundle bundle = data.getExtras();
String scanResult = bundle.getString("result");
resultTextView.setText(scanResult);
}
}
}


然后长按识别二维码调用了RGBLuminanceSource这个类

public class RGBLuminanceSource  extends LuminanceSource {


private byte bitmapPixels[];


protected RGBLuminanceSource(Bitmap bitmap) {
super(bitmap.getWidth(), bitmap.getHeight());


// 首先,要取得该图片的像素数组内容
int[] data = new int[bitmap.getWidth() * bitmap.getHeight()];
this.bitmapPixels = new byte[bitmap.getWidth() * bitmap.getHeight()];
bitmap.getPixels(data, 0, getWidth(), 0, 0, getWidth(), getHeight());


// 将int数组转换为byte数组,也就是取像素值中蓝色值部分作为辨析内容
for (int i = 0; i < data.length; i++) {
this.bitmapPixels[i] = (byte) data[i];
}
}


@Override
public byte[] getMatrix() {
// 返回我们生成好的像素数据
return bitmapPixels;
}


@Override
public byte[] getRow(int y, byte[] row) {
// 这里要得到指定行的像素数据
System.arraycopy(bitmapPixels, y * getWidth(), row, 0, getWidth());
return row;
}
}
相机识别二维码调用了CaptureActivity这个类
public class CaptureActivity extends Activity implements Callback {


private CaptureActivityHandler handler;
private ViewfinderView viewfinderView;
private boolean hasSurface;
private Vector<BarcodeFormat> decodeFormats;
private String characterSet;
private InactivityTimer inactivityTimer;
private MediaPlayer mediaPlayer;
private boolean playBeep;
private static final float BEEP_VOLUME = 0.10f;
private boolean vibrate;
private Button cancelScanButton;


/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera);

CameraManager.init(getApplication());
viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
cancelScanButton = (Button) this.findViewById(R.id.btn_cancel_scan);
hasSurface = false;
inactivityTimer = new InactivityTimer(this);
}


@Override
protected void onResume() {
super.onResume();
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
SurfaceHolder surfaceHolder = surfaceView.getHolder();
if (hasSurface) {
initCamera(surfaceHolder);
} else {
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
decodeFormats = null;
characterSet = null;


playBeep = true;
AudioManager audioService = (AudioManager) getSystemService(AUDIO_SERVICE);
if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
playBeep = false;
}
initBeepSound();
vibrate = true;

//quit the scan view
cancelScanButton.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
CaptureActivity.this.finish();
}
});
}


@Override
protected void onPause() {
super.onPause();
if (handler != null) {
handler.quitSynchronously();
handler = null;
}
CameraManager.get().closeDriver();
}


@Override
protected void onDestroy() {
inactivityTimer.shutdown();
super.onDestroy();
}

/**
* Handler scan result
* @param result
* @param barcode
*/
public void handleDecode(Result result, Bitmap barcode) {
inactivityTimer.onActivity();
playBeepSoundAndVibrate();
String resultString = result.getText();
//FIXME
if (resultString.equals("")) {
//扫描失败
Toast.makeText(CaptureActivity.this, "Scan failed!", Toast.LENGTH_SHORT).show();
}else {
// System.out.println("Result:"+resultString);
Intent resultIntent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("result", resultString);
resultIntent.putExtras(bundle);
this.setResult(RESULT_OK, resultIntent);
}
CaptureActivity.this.finish();
}

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


@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {


}


@Override
public void surfaceCreated(SurfaceHolder holder) {
if (!hasSurface) {
hasSurface = true;
initCamera(holder);
}


}


@Override
public void surfaceDestroyed(SurfaceHolder holder) {
hasSurface = false;


}


public ViewfinderView getViewfinderView() {
return viewfinderView;
}


public Handler getHandler() {
return handler;
}


public void drawViewfinder() {
viewfinderView.drawViewfinder();


}


private void initBeepSound() {
if (playBeep && mediaPlayer == null) {
// The volume on STREAM_SYSTEM is not adjustable, and users found it
// too loud,
// so we now play on the music stream.
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) getSystemService(VIBRATOR_SERVICE);
vibrator.vibrate(VIBRATE_DURATION);
}
}


/**
* When the beep has finished playing, rewind to queue up another one.
*/
private final OnCompletionListener beepListener = new OnCompletionListener() {
public void onCompletion(MediaPlayer mediaPlayer) {
mediaPlayer.seekTo(0);
}
};


}


下面是主布局mian文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@android:color/white"
    android:orientation="vertical" >


    <Button
        android:id="@+id/btn_scan_barcode"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="Open camera" />
    
    <LinearLayout 
        android:orientation="horizontal"
        android:layout_marginTop="10dp"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        
        <TextView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/black"
        android:textSize="18sp"
        android:text="Scan result:" />
        
        <TextView 
        android:id="@+id/tv_scan_result"
        android:layout_width="fill_parent"
        android:textSize="18sp"
        android:textColor="@android:color/black"
        android:layout_height="wrap_content" />
    </LinearLayout>
    
    <EditText 
        android:id="@+id/et_qr_string"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:hint="Input the text"/>
    
    <Button
        android:id="@+id/btn_add_qrcode"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Generate QRcode" />
    
    <ImageView 
        android:id="@+id/iv_qr_image"
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:scaleType="fitXY"
        android:layout_marginTop="10dp"
        android:layout_gravity="center"/>


</LinearLayout>


详细了解 的请下载demo自己看,Demo中解决了在竖拍解码时二维码被拉伸的现象。

不过我遇到了一个问题是 二维码的扫描框调大后,扫描的灵敏度降低了,希望知道的朋友给指导下

有兴趣的可以下载Demo看一看

http://download.csdn.net/detail/shihuiyun/9537714

2016-05-20 11:00:44 eric19920720 阅读数 4491

基于 Zing, 初学android  代码质量可能不高

           bigImage.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View viewm) {
                    Bitmap obmp = ((BitmapDrawable) (bigImage).getDrawable()).getBitmap();
                    int width = obmp.getWidth();
                    int height = obmp.getHeight();
                    int[] data = new int[width * height];
                    obmp.getPixels(data, 0, width, 0, 0, width, height);
                    RGBLuminanceSource source = new RGBLuminanceSource(width, height, data);
                    BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
                    QRCodeReader reader = new QRCodeReader();
                    Result re = null;
                    try {
                        re = reader.decode(bitmap1);
                    } catch (NotFoundException e) {
                        e.printStackTrace();
                    } catch (ChecksumException e) {
                        e.printStackTrace();
                    } catch (FormatException e) {
                        e.printStackTrace();
                    }
                    if (re == null) {
                        showAlert(obmp);
                    } else {
                        showSelectAlert(obmp, re.getText());
                    }
                    return false;
                }
            });


    private void showAlert(final Bitmap bitmap) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage("保存图片")
                .setCancelable(false)
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterfacem, int i) {
                        saveImageToGallery(bitmap);
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterfacem, int i) {
                    }
                });
        builder.show();
    }

    private void showSelectAlert(final Bitmap bitmap, final String url) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("请选择");
        String str[] = {"保存图片", "扫二维码"};
        builder.setItems(str, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterfacem, int i) {
                switch (i) {
                    case 0: {
                        saveImageToGallery(bitmap);
                    }
                    break;
                    case 1: {
                        Intent n = new Intent(EnlargeimagevActivity.this, DetailActivity.class);
                        n.putExtra(DetailActivity.BUNDLE_KEY_DISPLAY_TYPE, DetailActivity.WEBVIEW_DETAIL);
                        n.putExtra(DetailwebFragment.WEB_URL, url);
                        startActivity(n);
                    }
                    break;
                }
            }
        });
        builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterfacem, int i) {

            }
        });
        builder.show();
    }



2017-01-10 09:27:16 yhz61010 阅读数 451
[b]前提:[/b]
[color=red]本文使用了 ButterKnife 依赖库[/color]

开始环境: Android Studio

在 Module 文件夹下的 build.gradle 文件中追加 zxing 依赖:
compile 'com.google.zxing:core:3.3.0'

之后添加图片长按事件(此处使用了 ButterKnife 的注解),并实现识别二维码的功能:
@OnLongClick({R.id.imageView})
public boolean btnLongClick(final ImageView iv) {
Logger.info(TAG, "Long click on image");

new AlertDialog.Builder(getActivity())
.setPositiveButton(R.string.recognize_qr_code, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
progressBar.setVisibility(View.VISIBLE);

Bitmap obmp = ((BitmapDrawable) (iv.getDrawable())).getBitmap();
int width = obmp.getWidth();
int height = obmp.getHeight();
int[] data = new int[width * height];
obmp.getPixels(data, 0, width, 0, 0, width, height);
RGBLuminanceSource source = new RGBLuminanceSource(width, height, data);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

new QrCodeAsyncTask().execute(bitmap);
}
}).show();

return true;
}

class QrCodeAsyncTask extends AsyncTask<BinaryBitmap, Void, Result> {

@Override
protected Result doInBackground(BinaryBitmap... params) {
QRCodeReader reader = new QRCodeReader();
Result result = null;
try {
result = reader.decode(params[0]);
} catch (NotFoundException e) {
e.printStackTrace();
} catch (ChecksumException e) {
e.printStackTrace();
} catch (FormatException e) {
e.printStackTrace();
}
return result;
}

@Override
protected void onPostExecute(Result result) {
super.onPostExecute(result);
progressBar.setVisibility(View.GONE);

String text = result.getText();
Logger.info(TAG, "QR CODE: " + text);
Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
}
}
2017-09-11 17:58:00 weixin_34270865 阅读数 77

#基于 Zxing, 初学Android  代码质量不高

//长按,通过zxing读取图片,判断是否有二维码
bigImage.setOnLongClickListener(new View.OnLongClickListener() {  
     @Override  
     public boolean onLongClick(View viewm) {  
         Bitmap obmp = ((BitmapDrawable) (bigImage).getDrawable()).getBitmap();  
         int width = obmp.getWidth();  
         int height = obmp.getHeight();  
         int[] data = new int[width * height];  
         obmp.getPixels(data, 0, width, 0, 0, width, height);  
         RGBLuminanceSource source = new RGBLuminanceSource(width, height, data);  
         BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));  
         QRCodeReader reader = new QRCodeReader();  
         Result re = null;  
         try {  
             re = reader.decode(bitmap1);  
         } catch (NotFoundException e) {  
             e.printStackTrace();  
         } catch (ChecksumException e) {  
             e.printStackTrace();  
         } catch (FormatException e) {  
             e.printStackTrace();  
         }  
         if (re == null) {  
             showAlert(obmp);  
         } else {  
             showSelectAlert(obmp, re.getText());  
         }  
         return false;  
     }  
 });  


private void showAlert(final Bitmap bitmap) {  
    AlertDialog.Builder builder = new AlertDialog.Builder(this);  
    builder.setMessage("保存图片")  
            .setCancelable(false)  
            .setPositiveButton("确定", new DialogInterface.OnClickListener() {  
                @Override  
                public void onClick(DialogInterface dialogInterfacem, int i) {  
                    saveImageToGallery(bitmap);  
                }  
            })  
            .setNegativeButton("取消", new DialogInterface.OnClickListener() {  
                @Override  
                public void onClick(DialogInterface dialogInterfacem, int i) {  
                }  
            });  
    builder.show();  
}  

private void showSelectAlert(final Bitmap bitmap, final String url) {  
    AlertDialog.Builder builder = new AlertDialog.Builder(this);  
    builder.setTitle("请选择");  
    String str[] = {"保存图片", "扫二维码"};  
    builder.setItems(str, new DialogInterface.OnClickListener() {  
        @Override  
        public void onClick(DialogInterface dialogInterfacem, int i) {  
            switch (i) {  
                case 0: {  
                    saveImageToGallery(bitmap);  
                }  
                break;  
                case 1: {  
                    Intent n = new Intent(EnlargeimagevActivity.this, DetailActivity.class);  
                    n.putExtra(DetailActivity.BUNDLE_KEY_DISPLAY_TYPE, DetailActivity.WEBVIEW_DETAIL);  
                    n.putExtra(DetailwebFragment.WEB_URL, url);  
                    startActivity(n);  
                }  
                break;  
            }  
        }  
    });  
    builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {  
        @Override  
        public void onClick(DialogInterface dialogInterfacem, int i) {  

        }  
    });  
    builder.show();  
}
zxing 下载  [http://download.csdn.net/detail/eric19920720/9709305]
2017-12-18 19:41:40 lanqi_x 阅读数 12191

这篇文章就整理下移动端长按识别二维码的实现吧!实现方式可以分为三种

一、长按原生控件,直接获取控件中的图片数据(src或background)

二、长按原生控件,截图识别

三、长按web中的图片,app识别其中的二维码(js互调)


第一二种好像没多少可以说的,但还是按照顺序来吧!首先先说下使用的库,ios使用原生二维码识别库(好像是ios7之后才有的),然后说是WKWebView比UIWebView优化了很多 东西,也解决了内存泄漏的问题那么js交互的部分我们就用WKWebView吧(说到这里必须吐槽下android的webView内存泄漏问题,一个字坑)。android没原生的,我了解的比较大众的就zxing和zbar,经过测试发现在二维码占图片的比例较小识别时zbar的识别比zxing好一些,而且zxing使用截图的方案实现时当二维码放在屏幕的底部时识别不出来,所以这里就直接只贴zbar的代码吧!

然后呢因为是写的demo,代码是没优化过的,怎么方便怎么来,实际使用还是得根据自己的需要优化一下,个人觉得重要的是实现方案和思路。

一、长按原生控件,直接获取控件中的图片数据(src或background)

这里基本上等于在介绍,二维码识别的使用了。

(1)android

获取图片android就比较简单了。长按事件就不说了,图片通常会用ImageView,直接获取src就行了,特殊点的放background,那么就获取background就好了。直接贴代码吧!

//src
Bitmap mBitmap=((BitmapDrawable) imageView.getDrawable()).getBitmap();

//background
mBitmap=((BitmapDrawable) imageView.getBackground()).getBitmap();

下面就是关键zbar 识别图片中二维码的代码了

-----------------------------------------------
public String parseRQ(Bitmap bitmap) {
        String text = null;
        ImageScanner scanner=new ImageScanner();
        scanner.setConfig(0, Config.X_DENSITY,3);
        scanner.setConfig(0, Config.Y_DENSITY, 3);
        //设置扫描的图片
        Image barcode = new Image(bitmap.getWidth(), bitmap.getHeight(), "Y800");
        //设置扫描的图片的区域,因为我们不知道二维码在哪,所以直接设置整张图片
        barcode.setCrop(0, 0, bitmap.getWidth(), bitmap.getHeight());
        int[] data = new int[bitmap.getWidth() * bitmap.getHeight()];
        byte[] bitmapPixels =
                new byte[bitmap.getWidth() * bitmap.getHeight()];
        bitmap.getPixels(data, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());

        for (int i = 0; i < data.length; i++) {
            bitmapPixels[i] = (byte) data[i];
        }
        barcode.setData(bitmapPixels);
        //识别图片中的二维码,result是二维码的个数(这点比zxing好,zxing只获取从左上开始找到的第一个,不过也有可能是我调用的api不对也不一定)
        int result = scanner.scanImage(barcode);
        if (result != 0)
        {
            SymbolSet syms = scanner.getResults();
            for (Symbol sym : syms)
            {
                text=sym.getData().trim();
                //我们只获取第一个非空二维码,习惯性判空,没测过几个空字符串可不可以生成二维码
                if(!text.isEmpty())
                {
                    break;
                }
            }
        }

        return text;

    }

--------------------------------------------------

拿到解码后的数据,就可以根据需求取实现功能了。

(2)ios

ios获取UIImageView的图片更容易直接就是imageView.image就可以了,原生识别二维码的操作也简单,个人觉得设置长按事件比这两个加起来都麻烦点。所以这里主要就是设置长按事件的代码了。贴码。

//创建长按,imageLongClick即为长按响应的函数
UILongPressGestureRecognizer *longClick=[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(imageLongClick:)];
    //触摸点数,即多少手指点击
    longClick.numberOfTouchesRequired=1;
    //开启触发事件处理
    imageView.userInteractionEnabled=YES;
    //imageView添加长按事件
    [imageView addGestureRecognizer:longClick];

ios原生识别二维码,比zxing和zbar都简单多了,当然这没做优化策略的,android zbar那那个代码也一样

----------------------------------------------
-(void)imageLongClick:(UILongPressGestureRecognizer *)sender{
    //按下时
    if ([sender state]==UIGestureRecognizerStateBegan) {
        NSLog(@"image long click....");
        //创建识别器
        CIDetector *detector=[CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:nil];
        //image转为CGImage进行识别,结果为所有二维码结果的对象数组
        NSArray *results=[detector featuresInImage:[CIImage imageWithCGImage:[self snapshotView].CGImage]];
        if (results.count>0) {
            //这里只拿第一个
            CIQRCodeFeature *feature=[results firstObject];
            //feature.messageString即为解码后的字符串,这里直接打开浏览器
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:feature.messageString]];
        }else{
            NSLog(@"找不到二维码");
        }
    }
}
--------------------------------------------


在此直接获取控件的图片直接识别的就这样结束了,如果要获取相册的也一样,只需将从相册获取到的图片转为对应的Bitmap(ios UIImage),其他的都不变就可。

这里需注意的是背景色如果是透明色是无法识别出来的,所以如果二维码的来源是自己app的这种方式就很好了,如果是用户上传的建议用第二种方式,不可保证不会有哪个坑上传个透明背景的图片或上传个长图。


二、长按原生控件,截图识别

长按事件和识别二维码的代码是一样的,就不重复,即获取到截屏的图片后调用识别的方法就可以了,所以这里就只剩截屏功能的代码了,一样直接上代码

(1)android

-----------------------------------------------------
//其实这里直接传个View进来也是可以的,比如第三种的长按网页的就可以将WebView传就来就可以了
public Bitmap snapshotView(Window window) {
        if (window != null) {
            //找到当前页面的根布局
            View view = window.getDecorView().getRootView();
            //获取当前屏幕的大小
            int width = view.getWidth();
            int height = view.getHeight();

            //设置缓存
            view.setDrawingCacheEnabled(true);
            view.buildDrawingCache();
            /*1、从缓存中获取当前屏幕的图片,创建一个DrawingCache的拷贝,因为DrawingCache得到的位图在禁用后会被回收
             *2、这里的88是去掉无用的部分即你确定是不会有二维码的部分(当然不做任何操作也是可以的),这里直接写死是状态栏的高度,
             *实际真正使用不会这么写,而是是去获取状态栏的高度(这里懒就不写了),我记得如果直接是控件调用buildDrawingCache
             *是该控件当前显示在屏幕上的部分就不用减去状态栏的高度了
             */
            Bitmap temBitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, 88, width, height - 88);
            //禁用DrawingCahce否则会影响性能 ,而且不禁止会导致每次截图到保存的是缓存的位图
            view.destroyDrawingCache();
            view.setDrawingCacheEnabled(false);

            return temBitmap;
        }
        return null;
    }
---------------------------------------------------

(2)ios

---------------------------------------------
//跟android一样这里的UIWindow也可以改成UIView,函数接受UIView的参数,外部就可以直接调用截取指定控件显示在屏幕的部分截图了
- (UIImage *)snapshotView {
    UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
    //这里只获取大小,所以bounds还是frame都是一样的
    CGRect rect = [keyWindow bounds];
    if(UIGraphicsBeginImageContextWithOptions != NULL)
    {
        //iphone4之后采用Retina屏幕调用这个(不知道有没有记反,也有其他的截图方式,只是我就记得这种)
        UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
    } else {
        UIGraphicsBeginImageContext(rect.size);
    }
    CGContextRef context = UIGraphicsGetCurrentContext();
    [keyWindow.layer renderInContext:context];
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return img;
}
-----------------------------------------------------------------------------------------------


三、长按web中的图片,app识别其中的二维码

这里也是截图实现,为什么不是拿原始图片识别,1是上面说的有可能是长图或背景透明,2是截图免下载,在速度体验上好点。我记得微信也是这样实现的,在哪提过我忘了。

既然涉及js,那我们就必须先来段js呀,js长按图片功能代码(本来是想用jquery的,但想想网页不一定是自己的,有可能是别人的静态网页,根本没导入jquery库,而直接 注入<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>的形式是会有跨域的问题的,所以不用jquery,直接写好了,代码量其实也差不多):

-----------------------------------------------------
//闭包,这里个人当成跟java的匿名对象差不多记忆
(function () {
        //获取所有图片标签
        var allImage = document.getElementsByTagName("img");
        var img;
        for (var i = 0; i < allImage.length; i++) {
            img = allImage[i];
            //添加触摸事件
            img.addEventListener('touchstart', function(event) {
                touch = event.touches[0];
                startevent = event;
            //保存触摸点的x,y轴
                startX = Number(touch.pageX);
                startY = Number(touch.pageY);
            //设置定时器,js没长按事件,就是使用定时器实现的,800毫秒后触发,img.src为图片地址,这里可以拿到后做保存图片发大图等功能
                timeout = setTimeout('longClick('+img.src+');', 800);
            });
            //移动事件
            img.addEventListener('touchmove', function(event) {
                touch = event.touches[0];
                scx = Math.abs(Number(touch.pageX) - startX);
                scy = Math.abs(Number(touch.pageY) - startY);
                //过滤掉移动事件,不这样做,当你手指放在这个图片往上或往下划的时候,800毫秒后也会触发长按事件,精确度可以自己调
                if (scx > 10 || scy > 10) { 
                   //取消定时器
                    clearTimeout(timeout);
                } else {
                    //相当android的拦截分发
                    event.preventDefault();
                }
            });
            //手指放开时取消定时器
            img.addEventListener('touchend', function(event) {
                clearTimeout(timeout);
            });
        }
    })();//立即执行
-------------------------------------------------------

app要做的就是两件事,1、将截图识别二维码对象注入js中。2、网页加载结束后,加载执行上面那个js函数代码即可。

(1)android

android相对简单点,创建注入对象

---------------------------------------------------
public class DemoJSBridge{
      @JavascriptInterface
       public void longClickImage(String imgSrc){
              //调用截图识别二维码代码
       }
}

//注入
wb.addJavascriptInterface(new DemoJSBridge(), "demoJSBridge");

---------------------------------------------------

这样就可以注入代码了,其他功能直接在类中加方法即可,而前端js调用则是

//对象是注入到window对象中的,所以是window.对象.方法
window.demoJSBridge.longClickImage(img.src);

所以上面的js函数中的'longClick('+img.src+');'改掉,然后页面加载完成后注入执行即可。即

------------------------------------------------------
        wb.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
               //加载js,而我们那个js函数是立即执行的,所以一加载就会自动执行,getQRJs()即为上面js函数的String格式
                wb.loadUrl("javascript:" + getQRJs());
            }
        });
------------------------------------------------------


(2)ios个人觉得麻烦点,但安全点

注入js对象

-------------------------------------------------------------------------
//构建script对象,配置页面加载完成后加载上面js函数的并执行,getJSString即为上面js函数的字符串,
//WKUserScriptInjectionTimeAtDocumentEnd页面加载结束后注入
WKUserScript *script=[[WKUserScript alloc] initWithSource:[self getJSString] injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
    //WKWebView配置对象
    WKWebViewConfiguration *config=[[WKWebViewConfiguration alloc] init];
    config.preferences=[WKPreferences new];
    //允许执行javaScript
    config.preferences.javaScriptEnabled=YES;
    //WKWebView自带长按事件,会拦截掉我们添加的事件,所以屏蔽掉
    NSMutableString *javascript = [NSMutableString string];
    //禁止webkitTouchCallout
    [javascript appendString:@"document.documentElement.style.webkitTouchCallout='none';"];
    [javascript appendString:@"document.documentElement.style.webkitUserSelect='none';"];//禁止选择
    WKUserScript *noneSelectScript = [[WKUserScript alloc] initWithSource:javascript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];

    [config.userContentController addUserScript:noneSelectScript];
    [config.userContentController addUserScript: script];
    //注入对象addScriptMessageHandler响应处理者self
    [config.userContentController addScriptMessageHandler:self name:@"demoJSBridge"];
    WKWebView *webView=[[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
---------------------------------------------------------------------------------

然后注意

1、script即我们上面代码中加载js函数的形式也可以用第二种方式,跟android的差不多,即

-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
    [self.webView evaluateJavaScript:[self getJSString] completionHandler:nil];
}
 

2、实现协议WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler,第一个页面加载进度等事件的回调。第二个js对话框的回调,WKWebView把js的对话框就是alert()这些给屏蔽了,我们需实现WKUIDelegate协议自己去弹窗。第三个js调用原生的方法。


3、WKWebView注入的对象跟android和UIWebView都不一样了,他放在了window.webkit.messageHandlers里,所以前端js调用时为

window.webkit.messageHandlers.对象名.postMessage(参数);
所以所以上面的js函数中的'longClick('+img.src+');'改掉,参数直接传js对象,如{functionName:"longClickImage",data:img.src},实现,如果为减少与android的差异性,android也可以改为只有postMessage(String msg)方法,然后根据functionName去执行对应的功能。js对象传到app后,ios会自动转为字典,android为json字符串,自己转成json就可以了。

个人理解是WKWebView取消了直接注入对象了,即没有将self注入到js中,而是于注入一个假对象,里面只有postMessage函数,当js调用这个函数时他对应的再去调用原生的回调。


4、响应js调用事件,即实现WKScriptMessageHandler协议

------------------------------------------------------------
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
     //message中的name即为注入的对象名,body为传过来的数据
    if ([message.name isEqualToString:@"demoJSBridge"]) {
        //js对象传过来后会自动转为字典
        NSDictionary *d=message.body;
        if ([@"longClickImage" isEqualToString:[d objectForKey:@"functionName"]]) {
            //调用截屏,识别二维码方法
        }
    }
}
-------------------------------------------------------------------

理论上js与原生的互调就这样可以了,但为了保险一点的话,原生接到js的调用以后再回调一下js比较好一点,因为有可能有些功能js需要app回传数据做下一步操作。相当于待人接物而言你叫我做一件事,做好了还是做不了,我得告知你一下,做个有交代有责任的人。


这篇好像有点长,写得不好的地方还多请见谅,也可在评论指导一下。