Android扫码 有仿微信版

第三方Zxing的GitHub地址:

tip:前面几种是为了更好的理解,也可直接划到第三种看仿微信扫码的那种。

用法:

国际惯例,先上图:
Android扫码 有仿微信版_第1张图片

Step 1 :添加依赖

    //第三方zxing
    implementation 'com.journeyapps:zxing-android-embedded:3.6.0'

Step 2 :添加权限

  <uses-permission android:name="android.permission.CAMERA"/>

Step 3 :activity_main.xml布局 添加 测试用的两个控件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始"
        android:id="@+id/button"/>

    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

Step 4 :MainActivity 代码:

public class MainActivity extends AppCompatActivity {

    private Button button;
    private ImageView ivImage;

    //  Step 1 : 初始化 获取控件 设置监听
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //获取测试的控件
        button = findViewById(R.id.button);//点击跳转到扫码活动
        ivImage = findViewById(R.id.iv_image);//输出二维码图片

        //控件监听
        listenerView();
    }


    private void listenerView() {

        //  Step 2 :跳转到扫描活动
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                //=======设置扫描活动  可根据需求设置以下内容
                IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);

                //  1.扫描成功后的提示音,默认关闭
                intentIntegrator.setBeepEnabled(true);

                //  2.启动后置摄像头扫描,若为 1 为前置摄像头,默认后置
                intentIntegrator.setCameraId(0);

                /*  3.设置扫描的条码的格式:默认为所有类型
                 *   IntentIntegrator.PRODUCT_CODE_TYPES:商品码类型
                 *   IntentIntegrator.ONE_D_CODE_TYPES:一维码类型
                 *   IntentIntegrator.QR_CODE:二维码
                 *   IntentIntegrator.DATA_MATRIX:数据矩阵类型
                 *   IntentIntegrator.ALL_CODE_TYPES:所类有型
                 * */
                intentIntegrator.setDesiredBarcodeFormats(IntentIntegrator.ALL_CODE_TYPES);

                /*  4.方向锁:true为锁定,false反之,默认锁定.
                ps:在AndroidManifest.xml里设置以下属性,则扫码界面完全依赖传感器(tools红色提示,指向它会提示,点击左边蓝色Create...即可)
                
                * */
                intentIntegrator.setOrientationLocked(true);

                //  5.设置扫描界面的提示信息:默认为:请将条码置于取景框内扫描。(ps:设置没提示文字:setPrompt(""))
                intentIntegrator.setPrompt("请选择二维码");

                //  6.设置关闭扫描的时间(单位:毫秒),不设置不关闭
                intentIntegrator.setTimeout(60000);

                //  7.保存二维码图片:在onActivityResult方法里可获取保存的路径,根据需要来是否需要保存
                intentIntegrator.setBarcodeImageEnabled(true);

                //启动扫描
                intentIntegrator.initiateScan();

            }
        });

    }


    //  Step 3 :处理扫码后返回的结果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode,resultCode,data);

        if(result!=null){

            //==是否扫到内容
            if (result.getContents()!=null){
                Toast.makeText(this,"扫描结果:"+result.getContents(),Toast.LENGTH_LONG).show();
            }else{
                Toast.makeText(this,"取消扫码",Toast.LENGTH_LONG).show();
            }

            //==是否有保存照片的路径  在intentIntegrator已设置保存照片
            if(result.getBarcodeImagePath()!=null){
                
                FileInputStream file=null;
                try {
                    file=new FileInputStream(new File(result.getBarcodeImagePath()));
                    ivImage.setImageBitmap(BitmapFactory.decodeStream(file));//显示获取的照片
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }finally {
                    try {
                        file.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }
            
            /*  获取条码种类:在intentIntegrator.setDesiredBarcodeFormats那设置扫码格式后(点击格式可进入查看该格式有多少个类型)

                例如:PRODUCT_CODE_TYPES:商品码类型,它就有 UPC_A, UPC_E, EAN_8, EAN_13, RSS_14 种类
                public static final Collection PRODUCT_CODE_TYPES = list(UPC_A, UPC_E, EAN_8, EAN_13, RSS_14);

                根据getFormatName获取到的种类,就知道是哪个扫码格式,进而根据需求进行相关操作
             */
            if (result.getFormatName()!=null){
                Toast.makeText(this,"图片格式:"+result.getFormatName(),Toast.LENGTH_LONG).show();
            }


        }else{
            super.onActivityResult(requestCode, resultCode, data);
        }

    }
    
    //=========PS:Android6.0以后的版本还需动态申请权限,若需动态申请,请在MainActivity中添加申请权限(调用此方法)再做打开相机扫描
    //暂时未发现需要动态申请(本人手机安卓10),可能不同手机厂商做法
    private void requsetPermission(){
        if (Build.VERSION.SDK_INT>22){
            if (ContextCompat.checkSelfPermission(MainActivity.this,
                    android.Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED){
                //先判断有没有权限 ,没有就在这里进行权限的申请
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{android.Manifest.permission.CAMERA},1);

            }else {

            }
        }else {

        }
    }

    //============PS:申请权限后的方法
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if (grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
                    //已获取权限,写需要做的代码

                }else {
                    //拒绝摄像头权限,可以提示用户给权限
                    Toast.makeText(MainActivity.this,"请手动打开相机权限",Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }

    }
}

拓展:自定义扫描界面

第一种:带闪光灯

国际惯例,先上图:图中白点为闪光灯按钮
Android扫码 有仿微信版_第2张图片

Step 1 :引入依赖:

    //第三方zxing
    implementation 'com.journeyapps:zxing-android-embedded:3.6.0'

Step 2 :申请权限:

    <!--相机-->
    <uses-permission android:name="android.permission.CAMERA"/>
    <!--若需要闪光灯权限 ,请加入此权限(自测不需要)-->
<!--
    <uses-permission android:name="android.permission.FLASHLIGHT" />
-->

Step 3 :准备3个布局

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始"
        android:id="@+id/button"/>

</LinearLayout>

content_scan.xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!--
    layout_width、layout_height:启动扫描界面的布局参数

    zxing_framing_rect_width、zxing_framing_rect_height:
    在扫描界面中,只能扫描二维码的宽高,去掉后会有默认的宽高
    -->
    <com.journeyapps.barcodescanner.BarcodeView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/zxing_barcode_surface"
        app:zxing_framing_rect_width="250dp"
        app:zxing_framing_rect_height="250dp"/>

    <com.journeyapps.barcodescanner.ViewfinderView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/zxing_viewfinder_view"
        app:zxing_possible_result_points="@color/zxing_custom_possible_result_points"
        app:zxing_result_view="@color/zxing_custom_result_view"
        app:zxing_viewfinder_laser="@color/zxing_custom_viewfinder_laser"
        app:zxing_viewfinder_mask="@color/zxing_custom_viewfinder_mask"/>

</merge>

activity_scan.xml

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

    <!--装扫描界面的控件
    @layout/content_scan:为嵌入content_scan.xml的布局
    -->
    <com.journeyapps.barcodescanner.DecoratedBarcodeView
        android:id="@+id/dbv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        app:zxing_scanner_layout="@layout/content_scan">
    </com.journeyapps.barcodescanner.DecoratedBarcodeView>

    <!--闪光灯图片 自行找图片样式
        @drawable/ic_flashlight_close 关闭时的图片
    -->
    <ImageButton
        android:id="@+id/ib_flashlight_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="60dp"
        android:background="@drawable/ic_flashlight_close"/>
</RelativeLayout>

Step 4 :ScanActivity.java

public class ScanActivity extends AppCompatActivity {
    private CaptureManager capture;
    private ImageButton ibFlashlight;
    private DecoratedBarcodeView barcodeScannerView;
    private boolean bTorch = false;

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

        //==设置布局、获取控件
        setContentView(R.layout.activity_scan);
        barcodeScannerView = findViewById(R.id.dbv);
        ibFlashlight= findViewById(R.id.ib_flashlight_close);

        //==监听: 根据barcodeScannerView设置闪光灯ibFlashlight状态
        barcodeScannerView.setTorchListener(new DecoratedBarcodeView.TorchListener() {
            @Override
            public void onTorchOn() {//开灯

                //R.drawable.ic_flashlight_open)  开灯显示的图片 自行找图片样式
                ibFlashlight.setBackground(getResources().getDrawable(R.drawable.ic_flashlight_open));
                bTorch = true;
            }

            @Override
            public void onTorchOff() {//关灯

                //R.drawable.ic_flashlight_close)  关灯显示的图片 自行找图片样式
                ibFlashlight.setBackground(getResources().getDrawable(R.drawable.ic_flashlight_close));
                bTorch = false;
            }
        });

        //==开或关灯
        ibFlashlight.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(bTorch){
                    barcodeScannerView.setTorchOff();
                } else {
                    barcodeScannerView.setTorchOn();
                }

            }
        });

        //==初始化活动
        capture = new CaptureManager(this, barcodeScannerView);

        capture.initializeFromIntent(getIntent(), savedInstanceState);

        capture.decode();
    }


    @Override
    protected void onResume() {
        super.onResume();
        capture.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        capture.onPause();
        barcodeScannerView.setTorchOff();
    }

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

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        capture.onSaveInstanceState(outState);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
        capture.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
    }
}

Step 5 :MainActivity 代码:

public class MainActivity extends AppCompatActivity {

    private Button button;

    //  Step 1 : 初始化 获取控件 设置监听
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //获取测试的控件
        button = findViewById(R.id.button);//点击跳转到扫码活动

        //控件监听
        listenerView();
    }


    private void listenerView() {

        //  Step 2 :跳转到扫描活动
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                //=======设置扫描活动  可根据需求设置以下内容
                IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);

                //启动自定义的扫描活动,不设置则启动默认的活动
                intentIntegrator.setCaptureActivity(ScanActivity.class);

                //启动扫描
                intentIntegrator.initiateScan();

            }
        });

    }


    //  Step 3 :处理扫码后返回的结果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode,resultCode,data);

        if(result!=null){

            //==是否扫到内容
            if (result.getContents()!=null){
                Toast.makeText(this,"扫描结果:"+result.getContents(),Toast.LENGTH_LONG).show();
            }else{
                Toast.makeText(this,"取消扫码",Toast.LENGTH_LONG).show();
            }


        }else{
            super.onActivityResult(requestCode, resultCode, data);
        }

    }
    
    //=========PS:Android6.0以后的版本还需动态申请权限,若需动态申请,请在MainActivity中添加申请权限(调用此方法)再做打开相机扫描
    //暂时未发现需要动态申请(本人手机安卓10),可能不同手机厂商做法
    private void requsetPermission(){
        if (Build.VERSION.SDK_INT>22){
            if (ContextCompat.checkSelfPermission(MainActivity.this,
                    android.Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED){
                //先判断有没有权限 ,没有就在这里进行权限的申请
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{android.Manifest.permission.CAMERA},1);

            }else {

            }
        }else {

        }
    }

    //============PS:申请权限后的方法
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if (grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
                    //已获取权限,写需要做的代码

                }else {
                    //拒绝摄像头权限,可以提示用户给权限
                    Toast.makeText(MainActivity.this,"请手动打开相机权限",Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }

    }
    
}

第二种:带闪光灯 并修改其扫描界面
国际惯例,先上图(ScanWidget代码里有介绍去掉四个角样式)本来是动图的
Android扫码 有仿微信版_第3张图片

tip:上面已经有介绍添加依赖和权限了,这里不多说,

并且,所用到的xml和activity和第一种的相同。

不同之处:
1.新建一个自定义的扫描活动:ScanWidget,代码如下

public class ScanWidget extends ViewfinderView {

    /* ******************************************    边角线相关属性    ************************************************/

    /**
     * "边角线长度/扫描边框长度"的占比 (比例越大,线越长)
     */
    public float mLineRate = 0.1F;

    /**
     * 边角线厚度 (建议使用dp)
     */
    public float mLineDepth =  TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());

    /**
     * 边角线颜色
     */
    public int mLineColor = Color.WHITE;

    /* *******************************************    扫描线相关属性    ************************************************/

    /**
     * 扫描线起始位置
     */
    public int mScanLinePosition = 0;

    /**
     * 扫描线厚度
     */
    public float mScanLineDepth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());

    /**
     * 扫描线每次重绘的移动距离
     */
    public float mScanLineDy = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics());

    /**
     * 线性梯度
     */
    public LinearGradient mLinearGradient;

    /**
     * 线性梯度位置
     */
    public float[] mPositions = new float[]{0f, 0.5f, 1f};

    /**
     * 线性梯度各个位置对应的颜色值
     */
    public int[] mScanLineColor = new int[]{0x00FFFFFF, Color.WHITE, 0x00FFFFFF};



    // This constructor is used when the class is built from an XML resource.
    public ScanWidget(Context context, AttributeSet attrs) {
        super(context, attrs);

    }


    @Override
    public void onDraw(Canvas canvas) {
        refreshSizes();
        if (framingRect == null || previewFramingRect == null) {
            return;
        }

        final Rect frame = framingRect;
        final Rect previewFrame = previewFramingRect;

        //=====绘制4个角  可以注释此段代码,就像微信那样只要扫描线在动的样式了
        paint.setColor(mLineColor); // 定义四个角画笔的颜色(本身整个扫描界面都为此颜色,通过设置四个角距离而被覆盖,进而形成四个角)
        //左上角
        canvas.drawRect(frame.left, frame.top, frame.left + frame.width() * mLineRate, frame.top + mLineDepth, paint);
        canvas.drawRect(frame.left, frame.top, frame.left + mLineDepth, frame.top + frame.height() * mLineRate, paint);

        //右上角
        canvas.drawRect(frame.right - frame.width() * mLineRate, frame.top, frame.right, frame.top + mLineDepth, paint);
        canvas.drawRect(frame.right - mLineDepth, frame.top, frame.right, frame.top + frame.height() * mLineRate, paint);

        //左下角
        canvas.drawRect(frame.left, frame.bottom - mLineDepth, frame.left + frame.width() * mLineRate, frame.bottom, paint);
        canvas.drawRect(frame.left, frame.bottom - frame.height() * mLineRate, frame.left + mLineDepth, frame.bottom, paint);

        //右下角
        canvas.drawRect(frame.right - frame.width() * mLineRate, frame.bottom - mLineDepth, frame.right, frame.bottom, paint);
        canvas.drawRect(frame.right - mLineDepth, frame.bottom - frame.height() * mLineRate, frame.right, frame.bottom, paint);


        //=======扫描框为的颜色,灰色遮罩层,删除则无灰色遮罩层
        /*
        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(CURRENT_POINT_OPACITY);
            canvas.drawBitmap(resultBitmap, null, frame, paint);
        } else {

            // ===绘制扫描线
            mScanLinePosition += mScanLineDy;
            if(mScanLinePosition > frame.height()){
                mScanLinePosition = 0;
            }
            mLinearGradient = new LinearGradient(frame.left, frame.top + mScanLinePosition, frame.right, frame.top + mScanLinePosition, mScanLineColor, mPositions, Shader.TileMode.CLAMP);
            paint.setShader(mLinearGradient);
            canvas.drawRect(frame.left, frame.top + mScanLinePosition, frame.right, frame.top + mScanLinePosition + mScanLineDepth, paint);
            paint.setShader(null);



            final float scaleX = frame.width() / (float) previewFrame.width();
            final float scaleY = frame.height() / (float) previewFrame.height();

            final int frameLeft = frame.left;
            final int frameTop = frame.top;

            /*去掉扫描区域的闪光点
            if (!lastPossibleResultPoints.isEmpty()) {
                paint.setAlpha(CURRENT_POINT_OPACITY / 2);
                paint.setColor(resultPointColor);
                float radius = POINT_SIZE / 2.0f;
                for (final ResultPoint point : lastPossibleResultPoints) {
                    canvas.drawCircle(
                            frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY),
                            radius, paint
                    );
                }
                lastPossibleResultPoints.clear();
            }
            */

            // draw current possible result points
            if (!possibleResultPoints.isEmpty()) {
                paint.setAlpha(CURRENT_POINT_OPACITY);
                paint.setColor(resultPointColor);
                for (final ResultPoint point : possibleResultPoints) {
                    canvas.drawCircle(
                            frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY),
                            POINT_SIZE, paint
                    );
                }

                // swap and clear buffers
                final List<ResultPoint> temp = possibleResultPoints;
                possibleResultPoints = lastPossibleResultPoints;
                lastPossibleResultPoints = temp;
                possibleResultPoints.clear();
            }

            // Request another update at the animation interval, but only repaint the laser line,
            // not the entire viewfinder mask.
            postInvalidateDelayed(ANIMATION_DELAY,
                    frame.left - POINT_SIZE,
                    frame.top - POINT_SIZE,
                    frame.right + POINT_SIZE,
                    frame.bottom + POINT_SIZE);
        }
    }

}

2.content_scan.xml的代码为:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!--
    layout_width、layout_height:启动扫描界面的布局参数

    zxing_framing_rect_width、zxing_framing_rect_height:
    在扫描界面中,只能扫描二维码的宽高,去掉后会有默认的宽高
    -->
    <com.journeyapps.barcodescanner.BarcodeView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/zxing_barcode_surface" />

    <!--使用的是自定义的扫描活动-->
    <com.gx.test.widget.ScanWidget
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/zxing_viewfinder_view" />

</merge>

第三种:改进版,仿微信扫描条(本来是动图的,扫描线会动)
扫描条是图片,利用动画实现扫描条活动,第二种的扫描条是绘制的线,调快移动距离的话会感觉一卡一卡的,效果不好。
国际惯例,先上图:因虚拟机录制,看着扫描条一卡一卡的,实际手机调试不是

Android扫码 有仿微信版_第4张图片
Step 1 :引入依赖:

    //第三方zxing
    implementation 'com.journeyapps:zxing-android-embedded:3.6.0'

Step 2 :申请权限:

    <!--相机-->
    <uses-permission android:name="android.permission.CAMERA"/>
    <!--若需要闪光灯权限 ,请加入此权限(自测不需要)-->
<!--
    <uses-permission android:name="android.permission.FLASHLIGHT" />
-->

Step 3 :自定义 MyApplication

public class MyApplication extends Application {
    
    private View view;

    public View getView() {
        return view;
    }

    public void setView(View view) {
        this.view = view;
    }
}

并在AndroidManifest.xml配置好

    <application
        android:name=".MyApplication"

Step 4 :ScanWidget 自定义扫描活动界面:

//  Step 1 :继承  ViewfinderView 并 加控制器
public class ScanWidget extends ViewfinderView {


    //边角线厚度
    public float mLineDepth =  TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());

    //边角线长度/扫描边框长度"的占比 (比例越大,线越长)
    public float mLineRate = 0.1F;


    public ScanWidget(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    //  Step 2 : 重写此方法:在此方法删除原来的扫描条等等样式,并加入自己的扫描样式
    @Override
    public void onDraw(Canvas canvas) {

        refreshSizes();
        if (framingRect == null || previewFramingRect == null) {
            return;
        }

        final Rect frame = framingRect;
        final Rect previewFrame = previewFramingRect;

        final int width = canvas.getWidth();
        final int height = canvas.getHeight();


        //====================自己加入的扫描条动画在此处(扫描条其实是View控件放了个背景,view加入动画就实现了扫描条运动)↓
        //ps:若是启用下面代码(带有 PS 的注释那段,请看其作用),请在全局定义boolean b=false,然后例:
        //if(!b){这里写这段自己加入的动画代码; b=true;}  否则出现扫描条不运动,也不会因下面那PS提示的代码让这段代码反复执行。

        //=====加入扫描条
        MyApplication myApplication= (MyApplication) this.getContext().getApplicationContext();

        //设置扫描条的参数
        View view=myApplication.getView();
        FrameLayout.LayoutParams params= (FrameLayout.LayoutParams) view.getLayoutParams();
        params.width=frame.right-frame.left;//这是计算扫描框的宽度,进而设置扫描条的宽度
        params.setMargins(frame.left,0,0,0);//设置左边距,让扫描条在横方向在扫描框里
        view.setLayoutParams(params);

        //设置扫描条的动画        注意这个 60 是扫描条的宽度 单位是px
        //参数 3:运动开始的地方:frame.top是扫描框离屏幕顶部的距离,减60是因为 这个扫描条 的高是 60 px,
        // 参数3的单位也是px,所以运动开始的地方就是 frame.top-60;参数4作用同3
        Animation animation = new TranslateAnimation(0, 0, frame.top-70, frame.bottom-70);
        animation.setRepeatMode(Animation.RESTART);
        animation.setRepeatCount(Animation.INFINITE);
        animation.setDuration(2000);
        view.startAnimation(animation);
        
        //清除内存
        myApplication.setView(null);

        //=====为矩形扫描区域四个角加上边框  根据情况可以去掉该段代码,像微信扫码了
        paint.setColor(Color.GREEN); // 定义四个角画笔的颜色(本身整个扫描界面都为此颜色,通过设置四个角距离而被覆盖,进而形成四个角)
        //左上角
        canvas.drawRect(frame.left, frame.top, frame.left + frame.width() * mLineRate, frame.top + mLineDepth, paint);
        canvas.drawRect(frame.left, frame.top, frame.left + mLineDepth, frame.top + frame.height() * mLineRate, paint);

        //右上角
        canvas.drawRect(frame.right - frame.width() * mLineRate, frame.top, frame.right, frame.top + mLineDepth, paint);
        canvas.drawRect(frame.right - mLineDepth, frame.top, frame.right, frame.top + frame.height() * mLineRate, paint);

        //左下角
        canvas.drawRect(frame.left, frame.bottom - mLineDepth, frame.left + frame.width() * mLineRate, frame.bottom, paint);
        canvas.drawRect(frame.left, frame.bottom - frame.height() * mLineRate, frame.left + mLineDepth, frame.bottom, paint);

        //右下角
        canvas.drawRect(frame.right - frame.width() * mLineRate, frame.bottom - mLineDepth, frame.right, frame.bottom, paint);
        canvas.drawRect(frame.right - mLineDepth, frame.bottom - frame.height() * mLineRate, frame.right, frame.bottom, paint);

        //============================自己加入动画↑=========================


        // 灰色遮罩层  可以去掉
        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);

/*

        //===========PS:以下方法,不断执行onDraw方法绘制扫描线等样式进而产生自带的扫描线和闪光点,
        // 若是扫描到了,就会把结果图绘制在矩形框上,根据情况选择是否注释以下代码或部分动画代码

        if (resultBitmap != null) {
            //扫描到后在矩形上绘制不透明的图
            // Draw the opaque result bitmap over the scanning rectangle
            paint.setAlpha(CURRENT_POINT_OPACITY);
            canvas.drawBitmap(resultBitmap, null, frame, paint);
        } else {

            //自带的红色扫描线
            // Draw a red "laser scanner" line through the middle to show decoding is active
            paint.setColor(laserColor);
            paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
            scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
            final int middle = frame.height() / 2 + frame.top;
            canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint);

            final float scaleX = frame.width() / (float) previewFrame.width();
            final float scaleY = frame.height() / (float) previewFrame.height();

            final int frameLeft = frame.left;
            final int frameTop = frame.top;

            // draw the last possible result points
            if (!lastPossibleResultPoints.isEmpty()) {
                paint.setAlpha(CURRENT_POINT_OPACITY / 2);
                paint.setColor(resultPointColor);
                float radius = POINT_SIZE / 2.0f;
                for (final ResultPoint point : lastPossibleResultPoints) {
                    canvas.drawCircle(
                            frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY),
                            radius, paint
                    );
                }
                lastPossibleResultPoints.clear();
            }

            // draw current possible result points
            if (!possibleResultPoints.isEmpty()) {
                paint.setAlpha(CURRENT_POINT_OPACITY);
                paint.setColor(resultPointColor);
                for (final ResultPoint point : possibleResultPoints) {
                    canvas.drawCircle(
                            frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY),
                            POINT_SIZE, paint
                    );
                }

                // swap and clear buffers
                final List temp = possibleResultPoints;
                possibleResultPoints = lastPossibleResultPoints;
                lastPossibleResultPoints = temp;
                possibleResultPoints.clear();
            }

            //不断调用执行绘制该活动界面进出现自动的动画
            // Request another update at the animation interval, but only repaint the laser line,
            // not the entire viewfinder mask.
            postInvalidateDelayed(ANIMATION_DELAY,
                    frame.left - POINT_SIZE,
                    frame.top - POINT_SIZE,
                    frame.right + POINT_SIZE,
                    frame.bottom + POINT_SIZE);
        }

*/



    }


}

Step 5 :准备3个布局:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始"
        android:id="@+id/button"/>

</LinearLayout>

content_scan.xml 其实这个布局,BarcodeView和ScanWidget相当于不在布局里,所以该布局像只有View控件,进而达到扫描条运动

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!--
    layout_width、layout_height:启动扫描界面的布局参数

    zxing_framing_rect_width、zxing_framing_rect_height:
    在扫描界面中,只能扫描二维码的宽高,去掉后会有默认的宽高
    -->
    <com.journeyapps.barcodescanner.BarcodeView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/zxing_barcode_surface" />

    <!--注意自定义的扫描活动路径-->
    <com.gx.qr.ScanWidget
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/zxing_viewfinder_view"
        app:zxing_possible_result_points="@color/zxing_custom_possible_result_points"
        app:zxing_result_view="@color/zxing_custom_result_view"
        app:zxing_viewfinder_laser="@color/zxing_custom_viewfinder_laser"
        app:zxing_viewfinder_mask="@color/zxing_custom_viewfinder_mask"/>

    <!--@drawable/bmt 扫描条图标  请自行找素材-->
    <View
        android:id="@+id/scan_the"
        android:background="@drawable/bmt"
        android:layout_width="wrap_content"
        android:layout_height="70px" />

</merge>

activity_scan.xml 其实这个布局,DecoratedBarcodeView相当于不在布局里,所以该布局像只有ImageButton控件

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

    <!--装扫描界面的控件
    @layout/content_scan:为嵌入content_scan.xml的布局
    -->
    <com.journeyapps.barcodescanner.DecoratedBarcodeView
        android:id="@+id/dbv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        app:zxing_scanner_layout="@layout/content_scan">
    </com.journeyapps.barcodescanner.DecoratedBarcodeView>

    <!--闪光灯图片 自行找图片样式
        @drawable/ic_flashlight_close 关闭时的图片
    -->
    <ImageButton
        android:id="@+id/ib_flashlight_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="60dp"
        android:background="@drawable/ic_flashlight_close"/>
</RelativeLayout>

Step 6 :ScanActivity

public class ScanActivity extends AppCompatActivity {
    private CaptureManager capture;
    private ImageButton ibFlashlight;
    private DecoratedBarcodeView barcodeScannerView;
    private boolean bTorch = false;


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

        //==设置布局、获取控件
        setContentView(R.layout.activity_scan);
        barcodeScannerView = findViewById(R.id.dbv);
        ibFlashlight= findViewById(R.id.ib_flashlight_close);

        //==保存扫描条到Application里
        View view = findViewById(R.id.scan_the);
        MyApplication myApplication= (MyApplication) getApplication();
        myApplication.setView(view);

        //==监听: 根据barcodeScannerView设置闪光灯ibFlashlight状态
        barcodeScannerView.setTorchListener(new DecoratedBarcodeView.TorchListener() {
            @Override
            public void onTorchOn() {//开灯

                //R.drawable.ic_flashlight_open)  开灯显示的图片 自行找图片样式
                ibFlashlight.setBackground(getResources().getDrawable(R.drawable.ic_flashlight_open));
                bTorch = true;
            }

            @Override
            public void onTorchOff() {//关灯

                //R.drawable.ic_flashlight_close)  关灯显示的图片 自行找图片样式
                ibFlashlight.setBackground(getResources().getDrawable(R.drawable.ic_flashlight_close));
                bTorch = false;
            }
        });

        //==开或关灯
        ibFlashlight.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(bTorch){
                    barcodeScannerView.setTorchOff();
                } else {
                    barcodeScannerView.setTorchOn();
                }

            }
        });

        //==初始化活动
        capture = new CaptureManager(this, barcodeScannerView);

        capture.initializeFromIntent(getIntent(), savedInstanceState);

        capture.decode();
    }


    @Override
    protected void onResume() {
        super.onResume();
        capture.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        capture.onPause();
        barcodeScannerView.setTorchOff();
    }

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

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        capture.onSaveInstanceState(outState);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
        capture.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
    }
}

Step 7 :MainActivity

public class MainActivity extends AppCompatActivity {

    private Button button;

    //  Step 1 : 初始化 获取控件 设置监听
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = findViewById(R.id.button);//点击跳转到扫码活动

        //控件监听
        listenerView();
    }


    private void listenerView() {

        //  Step 2 :跳转到扫描活动
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                //=======设置扫描活动  可根据需求设置以下内容
                IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);

                //启动自定义的扫描活动,不设置则启动默认的活动
                intentIntegrator.setCaptureActivity(ScanActivity.class);

                //启动扫描
                intentIntegrator.initiateScan();

            }
        });

    }


    //  Step 3 :处理扫码后返回的结果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode,resultCode,data);

        if(result!=null){

            //==是否扫到内容
            if (result.getContents()!=null){
                Toast.makeText(this,"扫描结果:"+result.getContents(),Toast.LENGTH_LONG).show();
            }else{
                Toast.makeText(this,"取消扫码",Toast.LENGTH_LONG).show();
            }

        }else{
            super.onActivityResult(requestCode, resultCode, data);
        }

    }

    //=========PS:Android6.0以后的版本还需动态申请权限,若需动态申请,请在MainActivity中添加申请权限(调用此方法)再做打开相机扫描
    //暂时未发现需要动态申请(本人手机安卓10),可能不同手机厂商做法
    private void requsetPermission(){
        if (Build.VERSION.SDK_INT>22){
            if (ContextCompat.checkSelfPermission(MainActivity.this,
                    android.Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED){
                //先判断有没有权限 ,没有就在这里进行权限的申请
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{android.Manifest.permission.CAMERA},1);

            }else {

            }
        }else {

        }
    }

    //============PS:申请权限后的方法
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if (grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
                    //已获取权限,写需要做的代码

                }else {
                    //拒绝摄像头权限,可以提示用户给权限
                    Toast.makeText(MainActivity.this,"请手动打开相机权限",Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }

    }

}

OK!打完收工

你可能感兴趣的:(Android,android,扫码)