安卓应用开发学习:生成带Logo的二维码(使用华为统一扫描服务 Scan Kit)

一、前言

去年,我开发一个安卓应用,需要实现生成二维码功能,当时通过学习,使用华为统一扫码服务Scan Kit实现了生产二维码功能,相关内容见我的博文《Android 应用开发学习-生成二维码(使用华为统一扫描服务 Scan Kit)》。最近想实现生成带Logo的二维码,就再对 华为统一扫描服务 Scan Kit进行了学习,并在自己的应用中予以实现。

二、实现方法

通过学习我发现,华为统一扫描服务是支持在二维码中设置Logo的。只需要在创建HmsBuildBitmapOption对象的语句中添加setQRLogoBitmap方法即可。

HmsBuildBitmapOption options = new HmsBuildBitmapOption.Creator()
                    .setQRLogoBitmap(logoImage).create();   // setQRLogoBitmap方法往二维码里添加图片
try {
     qrBitmap = ScanUtil.buildBitmap(qrCodeText,type,width,height, options);
     iv_code2.setImageBitmap(qrBitmap);  // 将二维码显示在ImageView中
     } catch (WriterException e) {
         Log.w("buildBitmap", e.getClass().getSimpleName());
     }

这一点,在官网上没有看到说明,我是在网上搜到的一篇文章里(文章链接)找到的答案。那篇文章里是生成的圆形Logo,我不打算照抄,决定要实现一个圆角正方形的Logo图片。通过对Bitmap的相关知识点的学习,我自己写了一个生成圆角正方形的Logo图片的方法。

    // 获得缩小到指定尺寸的位图对象(length为缩放后的长边)
    public static Bitmap getZoomSizeImage(Context ctx, Uri uri, int length) {
        // Log.d(TAG, "getZoomSizeImage uri="+uri.toString());
        Bitmap zoomBitmap = null;
        // 打开指定uri获得输入流对象
        try (InputStream is = ctx.getContentResolver().openInputStream(uri)) {
            // 从输入流解码得到原始的位图对象
            Bitmap originBitmap = BitmapFactory.decodeStream(is);
            int w = originBitmap.getWidth();
            int h = originBitmap.getHeight();
            // Log.d(TAG, "原图尺寸w=" + w + ", h=" + h);
            if (w <= length || h <= length) {
                return originBitmap;
            }
            double ratio;
            if (h > w) {
                ratio = (float)w / length;
            } else {
                ratio = (float)h / length;
            }
            // Log.d(TAG, "压缩比ratio=" + ratio);
            // 获得比例缩放之后的位图对象
            zoomBitmap = getScaleBitmap(originBitmap, 1.0/ratio);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return zoomBitmap;
    }

    
    // 生成指定边长带边框的圆角图像(length为指定长边,radius是圆角半径,border是边框宽度)
    public static Bitmap getLogoImage(Context ctx, Uri uri, int length, int radius, int border) {
        Bitmap image = getZoomSizeImage(ctx, uri, length);
        // 将图像裁切为正方形
        int w = image.getWidth();
        int h = image.getHeight();
        int wh = Math.min(w, h);
        Log.d(TAG, "压缩后的图像短边=" + wh);
        if (w != h) {
            int retX = w > h ? (w-h)/2 : 0;
            int retY = w > h ? 0 : (h-w)/2;
            image = Bitmap.createBitmap(image, retX, retY, wh, wh, null , false);
        }
        Bitmap rCornerImage = Bitmap.createBitmap(wh, wh, Bitmap.Config.ARGB_8888);
        // 创建画笔对象
        Paint paint = new Paint();
        //paint.setStyle(Paint.Style.STROKE); // 设置边框风格为实线
        paint.setStrokeWidth(border); // 设置边框粗细
        paint.setColor(Color.WHITE); // 边框颜色
        paint.setAntiAlias(true);// 设置抗锯齿
        // 创建一个位图着色器,CLAMP表示边缘拉伸
        BitmapShader shader = new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        paint.setShader(shader);  // 设置画笔的着色器对象
        int roundRadius = Utils.dip2px(ctx, radius);  // 转换圆角半径
        RectF rect = new RectF(0, 0, image.getWidth(), image.getHeight());
        // 在画布上绘制圆角矩形
        Canvas canvas = new Canvas(rCornerImage);
        canvas.drawRoundRect(rect, roundRadius, roundRadius, paint);
        return rCornerImage;
    }

有了上面的方法,就可以生成一个指定边长带圆角和边框的图像了。图像的路径采用Uri,在主程序中添加从手机存储空间获取图片的功能代码,获取到图片后,执行上面的方法后返回生成的圆角正方形图像。然后将这个图像传给最上面的那个语句,就可以获得一个带Logo的二维码了。

我在原有代码上做了一点小修改,最终效果如下:

图1 不带logo的二维码

 

图2 带logo的二维码

三、代码展示

1.CreateQRCodeActivity.java文件

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.bahamutjapp.util.BitmapUtil;
import com.bahamutjapp.util.DateUtil;
import com.bahamutjapp.util.FileUtil;
import com.huawei.hms.hmsscankit.ScanUtil;
import com.huawei.hms.hmsscankit.WriterException;
import com.huawei.hms.ml.scan.HmsBuildBitmapOption;
import com.huawei.hms.ml.scan.HmsScan;

import java.io.File;
import java.util.Locale;

public class CreateQRCodeActivity extends AppCompatActivity implements View.OnClickListener {

    private final static String TAG = "CreateQRCodeActivity";
    private EditText et_content;
    private TextView tv_filePath;
    private ImageView iv_code1, iv_code2;
    private Bitmap logoImage;
    private ActivityResultLauncher<Intent> activityResultLauncher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_create_qrcode);

        et_content = findViewById(R.id.et_content);  // 要生成二维码的内容
        tv_filePath = findViewById(R.id.tv_filePath);  // 显示二维码图片的路径
        iv_code1 = findViewById(R.id.iv_code1);  // 显示二维码
        iv_code2 = findViewById(R.id.iv_code2);  // 显示带LOGO的二维码
        findViewById(R.id.btn_encode1).setOnClickListener(this);  // 生成二维码-无logo
        findViewById(R.id.btn_savePic1).setOnClickListener(this);  // 生成二维码1
        findViewById(R.id.btn_selectPic).setOnClickListener(this);  // 选择图片
        findViewById(R.id.btn_encode2).setOnClickListener(this);  // 生成带LOGO的二维码
        findViewById(R.id.btn_savePic2).setOnClickListener(this);  // 生成二维码2

        // 选择图片 - 注册StartActivityForResult
        activityResultLauncher = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(), result -> {
                    if (result.getResultCode() == RESULT_OK) {
                        // 操作成功的处理逻辑
                        Intent intent = result.getData();   //获取上一个活动返回的Intent
                        if (intent != null && intent.getData() != null) { // 从相册选择一张照片
                            Uri uri = intent.getData(); // 获得已选择照片的路径对象
                            // 根据指定图片的uri,获得按指定尺寸缩小后的位图对象
                            logoImage = BitmapUtil.getLogoImage(this, uri, 128, 15, 2);
                        }
                    } else {
                        // 操作失败的处理逻辑
                        Log.d(TAG,"返回结果异常");
                    }
                });

    }  // onCreate-end

    @Override
    public void onClick(View v) {
        Bitmap qrBitmap;
        if (v.getId() == R.id.btn_encode1) {  // 生成二维码
            String qrCodeText = et_content.getText().toString();  // 从EditText中获取输入的内容
            int type = HmsScan.QRCODE_SCAN_TYPE;     // 3   HmsScan.QRCODE_SCAN_TYPE 的值为1
            Log.d(TAG, "HmsScan.QRCODE_SCAN_TYPE的值为:" + type);
            int width = 600;
            int height = 600;
            HmsBuildBitmapOption options = new HmsBuildBitmapOption.Creator().create();
            try {
                qrBitmap = ScanUtil.buildBitmap(qrCodeText,type,width,height, options);
                iv_code1.setImageBitmap(qrBitmap);  // 将二维码显示在ImageView中
                tv_filePath.setText("");
            } catch (WriterException e) {
                Log.w("buildBitmap", e);
            }
        } else if (v.getId() == R.id.btn_savePic1) {  // 将二维码保存为图片1
            this.savePic(iv_code1);

        } else if(v.getId() == R.id.btn_selectPic) {  // 选择图片
            Intent albumIntent = new Intent(Intent.ACTION_GET_CONTENT);
            albumIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); // 是否允许多选
            albumIntent.setType("image/*"); // 类型为图像
            activityResultLauncher.launch(albumIntent);  // 打开系统相册
        } else if (v.getId() == R.id.btn_encode2) {  // 生成带logo二维码
            String qrCodeText = et_content.getText().toString();  // 从EditText中获取输入的内容
            int type = HmsScan.QRCODE_SCAN_TYPE;     // HmsScan.QRCODE_SCAN_TYPE 的值为1
            int width = 600;
            int height = 600;
            HmsBuildBitmapOption options = new HmsBuildBitmapOption.Creator()
                    .setQRLogoBitmap(logoImage).create();
            try {
                qrBitmap = ScanUtil.buildBitmap(qrCodeText,type,width,height, options);
                iv_code2.setImageBitmap(qrBitmap);  // 将二维码显示在ImageView中
                tv_filePath.setText("");
            } catch (WriterException e) {
                Log.w("buildBitmap", e.getClass().getSimpleName());
            }
        } else if (v.getId() == R.id.btn_savePic2) {  // 将二维码保存为图片2
            this.savePic(iv_code2);
        }

    } // onClick-end

    private void savePic(ImageView iv) {
// 从ImageView对象获取图像之前,一定要调用setDrawingCacheEnabled(true)方法
        iv.setDrawingCacheEnabled(true);
        if (iv.getDrawable() == null) {  // 检查是否已生成二维码
            Toast.makeText(this, "还未生成二维码", Toast.LENGTH_SHORT).show();
            return;
        }
        Bitmap bitmap = Bitmap.createBitmap(iv.getDrawingCache());
        // 从ImageView对象获取图像之后,一定要调用setDrawingCacheEnabled(false)方法
        iv.setDrawingCacheEnabled(false);
        // 设置图像的保存路径
        // 保存路径在/Documents/bahamutjapp/ 文件夹下       DIRECTORY_DOCUMENTS
        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "bahamutjapp");
        if (!file.exists()) {
            boolean success = file.mkdirs();
            if (success) {
                Log.d(TAG, "文件夹创建成功");
            } else {
                Log.e(TAG, "文件夹已存在");
            }
        }
        String fileName = "QR" + DateUtil.getNowDateTime() + ".jpeg";
        String filePath = file + File.separator + fileName;
        FileUtil.saveImage(filePath, bitmap);
        Log.d(TAG, "二维码图片路径为:" + filePath);  // 完整路径
        // 在TextView中显示的路径是通过手机的文件管理app看到的路径
        tv_filePath.setText(String.format(Locale.CHINESE, "%s%s", "图片路径:/Documents/bahamutjapp/", fileName));
        Toast.makeText(this, "二维码图片已生成", Toast.LENGTH_SHORT).show();
    }

}

2.activity_create_qrcode.xml文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".CreateQRCodeActivity">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:layout_weight="1"
        android:text="生成二维码"
        android:textSize="24sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_title">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <EditText
                android:id="@+id/et_content"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="20dp"
                android:layout_marginTop="10dp"
                android:layout_marginEnd="20dp"
                android:autofillHints=""
                android:hint="输入要生成二位码的内容"
                android:inputType="none"
                android:imeOptions="actionDone"
                android:minHeight="48dp"
                android:textSize="17sp" />

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="10dp">

                <Button
                    android:id="@+id/btn_encode1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="10dp"
                    android:text="生成二维码"
                    tools:ignore="ButtonStyle" />

                <Button
                    android:id="@+id/btn_savePic1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="10dp"
                    android:text="保存为图片"
                    tools:ignore="ButtonStyle" />

            </LinearLayout>

            <ImageView
                android:id="@+id/iv_code1"
                android:layout_width="300dp"
                android:layout_height="300dp"
                android:layout_gravity="center"
                android:layout_marginTop="5dp"
                tools:ignore="ContentDescription" />

            <TextView
                android:id="@+id/tv_filePath"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                android:layout_marginStart="10dp"
                android:layout_marginEnd="10dp" />

            <TextView
                android:id="@+id/tv_line"
                android:layout_width="match_parent"
                android:layout_height="2dp"
                android:layout_marginTop="5dp"
                android:background="@color/gray_220"
                android:text="TextView" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="10dp"
                android:text="生成带Logo的二维码"
                android:textSize="24sp"
                android:textStyle="bold" />

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal" >
                <Button
                    android:id="@+id/btn_selectPic"
                    android:layout_width="100dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_marginEnd="7dp"
                    android:text="选择图片"
                    tools:ignore="ButtonStyle" />

                <Button
                    android:id="@+id/btn_encode2"
                    android:layout_width="110dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_marginStart="7dp"
                    android:layout_marginEnd="7dp"
                    android:text="生成二维码"
                    tools:ignore="ButtonStyle" />

                <Button
                    android:id="@+id/btn_savePic2"
                    android:layout_width="100dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_marginStart="7dp"
                    android:text="保存图片"
                    tools:ignore="ButtonStyle" />

            </LinearLayout>

            <ImageView
                android:id="@+id/iv_code2"
                android:layout_width="300dp"
                android:layout_height="300dp"
                android:layout_gravity="center"
                android:layout_marginTop="5dp"
                tools:ignore="ContentDescription" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="60dp" />
        </LinearLayout>
    </ScrollView>

</androidx.constraintlayout.widget.ConstraintLayout>

3.BitmapUtil.java文件部分内容

import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;

import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;

import java.io.FileOutputStream;
import java.io.InputStream;

public class BitmapUtil {
    private final static String TAG = "BitmapUtil";


    // 获得缩小到指定尺寸的位图对象(length为缩放后的长边)
    public static Bitmap getZoomSizeImage(Context ctx, Uri uri, int length) {
        // Log.d(TAG, "getZoomSizeImage uri="+uri.toString());
        Bitmap zoomBitmap = null;
        // 打开指定uri获得输入流对象
        try (InputStream is = ctx.getContentResolver().openInputStream(uri)) {
            // 从输入流解码得到原始的位图对象
            Bitmap originBitmap = BitmapFactory.decodeStream(is);
            int w = originBitmap.getWidth();
            int h = originBitmap.getHeight();
            // Log.d(TAG, "原图尺寸w=" + w + ", h=" + h);
            if (w <= length || h <= length) {
                return originBitmap;
            }
            double ratio;
            if (h > w) {
                ratio = (float)w / length;
            } else {
                ratio = (float)h / length;
            }
            // Log.d(TAG, "压缩比ratio=" + ratio);
            // 获得比例缩放之后的位图对象
            zoomBitmap = getScaleBitmap(originBitmap, 1.0/ratio);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return zoomBitmap;
    }


    // 生成指定边长带边框的圆角图像(length为指定长边,radius是圆角半径,border是边框宽度)
    public static Bitmap getLogoImage(Context ctx, Uri uri, int length, int radius, int border) {
        Bitmap image = getZoomSizeImage(ctx, uri, length);
        // 将图像裁切为正方形
        int w = image.getWidth();
        int h = image.getHeight();
        int wh = Math.min(w, h);
        Log.d(TAG, "压缩后的图像短边=" + wh);
        if (w != h) {
            int retX = w > h ? (w-h)/2 : 0;
            int retY = w > h ? 0 : (h-w)/2;
            image = Bitmap.createBitmap(image, retX, retY, wh, wh, null , false);
        }
        Bitmap rCornerImage = Bitmap.createBitmap(wh, wh, Bitmap.Config.ARGB_8888);
        // 创建画笔对象
        Paint paint = new Paint();
        //paint.setStyle(Paint.Style.STROKE); // 设置边框风格为实线
        paint.setStrokeWidth(border); // 设置边框粗细
        paint.setColor(Color.WHITE); // 边框颜色
        paint.setAntiAlias(true);// 设置抗锯齿
        // 创建一个位图着色器,CLAMP表示边缘拉伸
        BitmapShader shader = new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        paint.setShader(shader);  // 设置画笔的着色器对象
        int roundRadius = Utils.dip2px(ctx, radius);  // 转换圆角半径
        RectF rect = new RectF(0, 0, image.getWidth(), image.getHeight());
        // 在画布上绘制圆角矩形
        Canvas canvas = new Canvas(rCornerImage);
        canvas.drawRoundRect(rect, roundRadius, roundRadius, paint);
        return rCornerImage;
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

武陵悭臾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值