乐趣区

关于android:Android-二维码生成框架Zxing

Android 二维码生成框架 Zxing

一:定义
ZXing 是一个开源的,用 Java 实现的多种格局的 1D/2D 条码图像处理库,它蕴含了分割到其余语言的端口。zxing 能够实现应用手机的内置的摄像头实现条形码的扫描及解码。
Android Studio 下增加依赖

  implementation 'com.google.zxing:core:3.3.2'

增加权限
因为扫描二维码须要摄像头权限,把图片保留到本地须要 sdcard 权限,所以须要在 AndroidManifest.xml 中退出相应的权限

 <!-- 相机权限 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- 触动权限 -->
    <uses-permission android:name="android.permission.VIBRATE" />
    <!--<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />-->
<!--<uses-feature 是为了把应用程序所依赖的软硬件个性告知应用程序之外的对象。-->
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

这里咱们看到 <uses-feature/> 标签

<uses-feature 
  android:name="string" 
  android:required=["true" | "false"] 
  android:glEsVersion="integer" /> 

申明一项应用程序须要用到的软、硬件个性。
申明一项 <uses-feature> 的目标,是为了把应用程序所依赖的软硬件个性告知应用程序之外的对象。本元素给出了一个 required 属性,用于指定应用程序是否必须该项个性,也即不申明该项个性的话就无奈失常运行;或者最好是提供该项个性,但没有的话也能运行。因为每种 Android 设施提供的个性各不相同,<uses-feature> 元素施展着重要作用,应用程序能够用它来形容其用到的各种设施个性。
假如应用程序须要应用蓝牙和摄像头设施,则应申明两个元素:

<uses-feature android:name=”android.hardware.bluetooth” />

<uses-feature android:name=”android.hardware.camera” />
通常,应该确保为应用程序须要的所有个性均申明了 <uses-feature> 元素。
<uses-feature> 元素的申明仅仅是告知性质的,这意味着 Android 零碎自身不会在安装程序前查看设施是否反对这些个性。不过,其余服务(如 Google Play)或者其余应用程序能够查看 <uses-feature> 申明来进行相应解决或与本应用程序进行交互。因而,对须要用到的所有个性都进行申明(如下表所示)是十分重要的。

有些设施个性可能会存在一些非凡的属性,用于定义该个性的版本,比方 Open GL 版本(用 glEsVersion 申明)。其余的一些与硬件是否就绪无关的个性,比方摄像头,则通过 name 属性进行申明。

二:生成二维码图片
生成二维码图片调用 CreateQRBitmp.createQRCodeBitmap 办法生成,这个办法是咱们本人封装的,须要传入两个参数,参数 1: 图片内容、参数 2: 二维码图片最两头显示的 logo(Bitmap 对象)。

public class EightTeenActivity extends AppCompatActivity implements View.OnClickListener {
    private EditText etInput;
    private Bitmap qrCodeBitmap;
    private ImageView ivQrImage;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_eight_teen);
        etInput = findViewById(R.id.et_input);
        ivQrImage = findViewById(R.id.iv_qr_image);
        findViewById(R.id.btn_scanning).setOnClickListener(this::onClick);// 扫描点击事件
        findViewById(R.id.btn_select).setOnClickListener(this::onClick);// 抉择图库点击事件
        findViewById(R.id.generate_qr_code).setOnClickListener(this::onClick);// 生成二维码点击事件
        findViewById(R.id.btn_long_press).setOnClickListener(this::onClick);//

    }

    @Override
    public void onClick(View v) {switch (v.getId()) {
            case R.id.btn_scanning:// 扫描
                break;
            case R.id.btn_select:
                // 激活零碎库抉择一张图片
                break;
            case R.id.generate_qr_code:
                // 生成二维码
                String contentString =etInput.getText().toString().trim();// 获取输入框中内容
                if (TextUtils.isEmpty(contentString)){showToast("请输出二维码内容");
                    return;

                }
                Log.i("rocky","输出的内容:"+contentString);
                // 加载资源文件的图片生成 Bitmap
                  // 高版本,这里会呈现一个问题:找不到资源图片为 null
                Bitmap portrait= BitmapFactory.decodeResource(getResources(),R.mipmap.login_icon);
                // 生成二维码工具类 CreateQRBitmp,两个办法,一个不传大小,应用默认
              
                qrCodeBitmap=CreateQRBitmp.createQRCodeBitmap(contentString,portrait);// 生成 BitMap 图片
                //imageView 上设置图片
                ivQrImage.setImageBitmap(qrCodeBitmap);


                break;
            case R.id.btn_long_press:
                break;
        }

    }

    private void showToast(String str){Toast.makeText(EightTeenActivity.this,str,Toast.LENGTH_LONG).show();}
}

高版本 Sdk, 加载资源图片 logo 时候,获取不到图片问题
Bitmap portrait= BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);

批改一下图片命名能够解决
CreateQRBitmp 创立一个二维码图片

public class CreateQRBitmp {

    /**
     * 生成二维码图片大小
     */
    private static int QRCODE_SIZE = 300;
    /**
     * 头像图片大小
     */
    private static int PORTRAIT_SIZE = 55;
    /**
     * 头像图片
     */
    private Bitmap portrait;

    /**
     * 性能: 创立 QR 二维码图片
     * 可设置图片大小和头像图片大小
     *
     * @param portrait 头像 bitmap
     * @param content  生成二维码内容数据
     */
    public static Bitmap createQRCodeBitmap(String content, Bitmap portrait, int widthAndHeight, int portraitSize) {
        QRCODE_SIZE = widthAndHeight;
        PORTRAIT_SIZE = portraitSize;
        // 用于设置 QR 二维码参数
        Hashtable<EncodeHintType, Object> qrParam = new Hashtable<EncodeHintType, Object>();
        // 设置 QR 二维码的纠错级别——这里抉择最高 H 级别
        qrParam.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        // 设置编码方式
        qrParam.put(EncodeHintType.CHARACTER_SET, "UTF-8");

        // 生成 QR 二维码数据——这里只是失去一个由 true 和 false 组成的数组
        // 参数程序别离为:编码内容,编码类型,生成图片宽度,生成图片高度,设置参数
        try {BitMatrix bitMatrix = new MultiFormatWriter().encode(content,
                    BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, qrParam);

            // 开始利用二维码数据创立 Bitmap 图片,别离设为黑白两色
            int w = bitMatrix.getWidth();
            int h = bitMatrix.getHeight();
            int[] data = new int[w * h];

            for (int y = 0; y < h; y++) {for (int x = 0; x < w; x++) {if (bitMatrix.get(x, y))
                        data[y * w + x] = 0xff000000;// 彩色
                    else
                        data[y * w + x] = 0x00ffffff;// -1 相当于 0xffffffff 红色
                }
            }

            // 创立一张 bitmap 图片,采纳最高的图片成果 ARGB_8888
            Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            // 将下面的二维码色彩数组传入,生成图片色彩
            bitmap.setPixels(data, 0, w, 0, 0, w, h);
            if (portrait != null) {createQRCodeBitmapWithPortrait(bitmap, initProtrait(portrait));
            }
            return bitmap;
        } catch (WriterException e) {e.printStackTrace();
        }
        return null;
    }

    /**
     * 性能: 创立 QR 二维码图片
     * 头像图片大小默认
     *
     * @param portrait 头像 bitmap
     * @param content  生成二维码内容数据
     */
    public static Bitmap createQRCodeBitmap(String content, Bitmap portrait) {
        // 用于设置 QR 二维码参数
        Hashtable<EncodeHintType, Object> qrParam = new Hashtable<>();// 应用 HashTable 保留数据
        // 设置 QR 二维码的纠错级别——这里抉择最高 H 级别
        qrParam.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        // 设置编码方式
        qrParam.put(EncodeHintType.CHARACTER_SET, "UTF-8");

        // 生成 QR 二维码数据——这里只是失去一个由 true 和 false 组成的数组
        // 参数程序别离为:编码内容,编码类型,生成图片宽度,生成图片高度,设置参数
        try {BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, qrParam);
            // 开始利用二维码数据创立 Bitmap 图片,别离设为黑白两色
            int w = bitMatrix.getWidth();
            int h = bitMatrix.getHeight();
            int[] data=new int[w*h];
            for (int y = 0; y < h; y++) {for (int x = 0; x < w; x++) {if (bitMatrix.get(x, y))
                        data[y * w + x] = 0xff000000;// 彩色
                    else
                        data[y * w + x] = 0x00ffffff;// -1 相当于 0xffffffff 红色
                }
            }

            // 创立一张 bitmap 图片,采纳最高的图片成果 ARGB_8888
            Bitmap bitmap=Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
            // 将下面的二维码色彩数组传入,生成图片色彩
            bitmap.setPixels(data, 0, w, 0, 0, w, h);
            if(portrait!=null){// 增加最两头的 logo
                createQRCodeBitmapWithPortrait(bitmap,initProtrait(portrait));
            }
            return bitmap;

        } catch (WriterException e) {e.printStackTrace();
        }
        return null;
    }
    /**
     * 初始化头像图片
     */
    public static Bitmap initProtrait(Bitmap portrait) {
        // 对原有图片压缩显示大小
        Matrix mMatrix = new Matrix();
        float width = portrait.getWidth();
        float height = portrait.getHeight();
        mMatrix.setScale(PORTRAIT_SIZE / width, PORTRAIT_SIZE / height);
        return Bitmap.createBitmap(portrait, 0, 0, (int) width,
                (int) height, mMatrix, true);
    }

    /**
     * 在二维码上绘制头像
     */
    public static void createQRCodeBitmapWithPortrait(Bitmap qr, Bitmap portrait) {
        // 头像图片的大小
        int portrait_W = portrait.getWidth();
        int portrait_H = portrait.getHeight();

        // 设置头像要显示的地位,即居中显示
        int left = (QRCODE_SIZE - portrait_W) / 2;
        int top = (QRCODE_SIZE - portrait_H) / 2;
        int right = left + portrait_W;
        int bottom = top + portrait_H;
        Rect rect1 = new Rect(left, top, right, bottom);

        // 获得 qr 二维码图片上的画笔,即要在二维码图片上绘制咱们的头像
        Canvas canvas = new Canvas(qr);

        // 设置咱们要绘制的范畴大小,也就是头像的大小范畴
        Rect rect2 = new Rect(0, 0, portrait_W, portrait_H);
        // 开始绘制
        canvas.drawBitmap(portrait, rect2, rect1, null);
    }
}

三:长按辨认二维码以及保留图片
辨认二维码跟从相册中抉择图片进行辨认性能上很类似,所以就不在做反复介绍了,就介绍一下保留图片性能。

private void longPress() {if (qrCodeBitmap==null){showToast("请学生成二维码图片");
            return ;
        }
        // 这个一个自定义 Dialog 弹窗
        ImageOptDialog imageOptDialog=new ImageOptDialog(EightTeenActivity.this);
        imageOptDialog.setCallback(new ImageOptDialog.ImageOptCallback() {
            // 辨认二维码
            @Override
            public void onIdentifyQrClick() {View view = getWindow().getDecorView().getRootView();// 找到以后页面的根布局
                view.setDrawingCacheEnabled(true);// 禁用绘图缓存
                view.buildDrawingCache();

                Bitmap temBitmap = view.getDrawingCache();
                //String result=BitmapUtil.parseQRcode(temBitmap);
              //  showToast("长按辨认二维码后果:"+result);

                // 禁用 DrawingCahce 否则会影响性能 , 而且不禁止会导致每次截图到保留的是缓存的位图
                view.setDrawingCacheEnabled(false);// 辨认实现之后开启绘图缓存
            }
            // 保留图片到本地

            @Override
            public void onSaveImageClick() {View view = getWindow().getDecorView().getRootView();// 找到以后页面的根布局
                view.setDrawingCacheEnabled(true);// 禁用绘图缓存
                view.buildDrawingCache();

                Bitmap temBitmap = view.getDrawingCache();
                ImageUtil.savePicToLocal(temBitmap,EightTeenActivity.this);
                // 禁用 DrawingCahce 否则会影响性能 , 而且不禁止会导致每次截图到保留的是缓存的位图
                view.setDrawingCacheEnabled(false);// 辨认实现之后开启绘图缓存

                showToast("保留图片到本地胜利");
            }
        });
        imageOptDialog.show();}

图片保留胜利后门路:rocky: filePath:/storage/emulated/0/screen/1627982791618.png

保留图片的办法:

public class ImageUtil {public static void savePicToLocal(Bitmap bitmap, Context context) {
        // 门路是 sdcard 获取 sd 卡目录即:sdcard/screen/ 工夫.png
        String filePath= Environment.getExternalStorageDirectory().getAbsolutePath()+"/screen"+ File.separator+System.currentTimeMillis()+".png";
        if (bitmap!=null){

            try {
                // 图片文件门路
                Log.i("rocky", "filePath:" + filePath);
                File file = new File(filePath);
                if (!file.getParentFile().exists()) {file.getParentFile().mkdirs();}
                FileOutputStream  os = new FileOutputStream(file);
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
                Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                Uri uri = Uri.fromFile(new File(filePath));
                intent.setData(uri);
                context.sendBroadcast(intent);
                os.flush();
                os.close();} catch (Exception e) {e.printStackTrace();
            }

        }
    }
}

存储须要动静增加权限:

//6.0 版本或以上需申请权限
String[] permissions=new String[]{Manifest.permission.
        WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA};
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {requestPermissions(permissions,PERMS_REQUEST_CODE);
}

四:从相册抉择二维码图片进行辨认
首先启动零碎相册,从相册中抉择一张图片。

  // 激活零碎图库,抉择一张图片
                    Intent innerIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                    Intent wrapperIntent = Intent.createChooser(innerIntent, "抉择二维码图片");
                    startActivityForResult(wrapperIntent, SELECT_IMAGE_REQUEST_CODE);

而后在 onActivityResult 中获取抉择图片门路,调用 BitmapUtil.parseQRcode 办法解析二维码图片。

 @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent intent) {super.onActivityResult(requestCode, resultCode, intent);
        if(requestCode==SELECT_IMAGE_REQUEST_CODE){// 从图库抉择图片
            String[] proj = {MediaStore.Images.Media.DATA};
            // 获取选中图片的门路
            Cursor cursor = this.getContentResolver().query(intent.getData(),proj, null, null, null);
            if (cursor.moveToFirst()) {int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
                String photoPath = cursor.getString(columnIndex);
                String result= BitmapUtil.parseQRcode(photoPath);
                if (!TextUtils.isEmpty(result)) {showToast("从图库抉择的图片辨认后果:"+result);
                } else {showToast("从图库抉择的图片不是二维码图片");
                }
            }
            cursor.close();}/*else if (requestCode == SCAN_REQUEST_CODE && resultCode == RESULT_OK) {String input = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
            showToast("扫描后果:"+input);
        }*/
    }

BitmapUtil 解析二维码图片

public class BitmapUtil {
    /**
     * 解析二维码图片
     * @param bitmapPath 文件门路
     * @return
     */
    public static String parseQRcode(String bitmapPath){Bitmap bitmap = BitmapFactory.decodeFile(bitmapPath, null);
        String result=parseQRcode(bitmap);
        return result;
    }

    public static String parseQRcode(Bitmap bmp){bmp=comp(bmp);//bitmap 压缩  如果不压缩的话在低配置的手机上解码很慢

        int width = bmp.getWidth();// 图片宽度
        int height = bmp.getHeight();// 图片高度
        int[] pixels = new int[width * height];
        bmp.getPixels(pixels, 0, width, 0, 0, width, height);

        QRCodeReader reader = new QRCodeReader();
        Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
        hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);// 优化精度
        hints.put(DecodeHintType.CHARACTER_SET,"utf-8");// 解码设置编码方式为:utf-8
        try {Result result = reader.decode(new BinaryBitmap(new HybridBinarizer(new RGBLuminanceSource(width, height, pixels))), hints);
            return result.getText();} catch (NotFoundException e) {Log.i("ansen",""+e.toString());
            e.printStackTrace();} catch (ChecksumException e) {e.printStackTrace();
        } catch (FormatException e) {e.printStackTrace();
        }
        return null;
    }

    // 图片按比例大小压缩办法(依据 Bitmap 图片压缩)private static Bitmap comp(Bitmap image) {ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        if(baos.toByteArray().length / 1024>1024) {// 判断如果图片大于 1M, 进行压缩防止在生成图片(BitmapFactory.decodeStream)时溢出
            baos.reset();// 重置 baos 即清空 baos
            image.compress(Bitmap.CompressFormat.JPEG, 50, baos);// 这里压缩 50%,把压缩后的数据寄存到 baos 中
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 开始读入图片,此时把 options.inJustDecodeBounds 设回 true 了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        // 当初支流手机比拟多是 800*480 分辨率,所以高和宽咱们设置为
        float hh = 400f;// 这里设置高度为 800f
        float ww = 400f;// 这里设置宽度为 480f
        // 缩放比。因为是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;//be= 1 示意不缩放
        if (w > h && w > ww) {// 如果宽度大的话依据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {// 如果高度高的话依据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;// 设置缩放比例
        // 从新读入图片,留神此时曾经把 options.inJustDecodeBounds 设回 false 了
        isBm = new ByteArrayInputStream(baos.toByteArray());
        bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
        return compressImage(bitmap);// 压缩好比例大小后再进行品质压缩
    }

    // 品质压缩办法
    private static Bitmap compressImage(Bitmap image) {ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 品质压缩办法,这里 100 示意不压缩,把压缩后的数据寄存到 baos 中
        int options = 100;
        while (baos.toByteArray().length/1024>100) { // 循环判断如果压缩后图片是否大于 100kb, 大于持续压缩
            baos.reset();// 重置 baos 即清空 baos
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);// 这里压缩 options%,把压缩后的数据寄存到 baos 中
            options -= 10;// 每次都缩小 10
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把压缩后的数据 baos 寄存到 ByteArrayInputStream 中
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把 ByteArrayInputStream 数据生成图片
        return bitmap;
    }
}

五:扫描二维码

Intent intent = new Intent(EightTeenActivity.this,ScanActivity.class);
                startActivityForResult(intent,SCAN_REQUEST_CODE);

重写 onActivityResult 办法,监听扫描后果。

 if (requestCode == SCAN_REQUEST_CODE && resultCode == RESULT_OK) {String input = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
            showToast("扫描后果:"+input);
        }

ScanActivity 类代码比拟多
具体能够参考:https://github.com/ansen666/Z…

END: 藏好本人,做好清理

退出移动版