WebView 下实现二维码扫描摄像头

1、前言

        解决Android WebView运行h5中的二维码组件无法展示摄像头内容

2、效果图

3、前端代码

 3.1 创建一个组件文件

<template>
  <div class="containers">
    <div class="card">
      <div class="scanner-wrapper item">
        <div class="scanner-bg">
          <div class="corner-1 common"></div>
          <div class="corner-2 common"></div>
          <div class="corner-3 common"></div>
          <div class="corner-4 common"></div>
          <!-- <div id="reader" class="scanner-reader"></div> -->
          <div class="sweep-img">
            <img class="img" src="../../src/assets/operate_img_sweep.png" />
          </div>
          <div class="ab"></div>
        </div>
      </div>
      <!-- 摄像头 -->
      <div class="camare">
        <qrcode-stream @error="onError" @detect="onDetect" />
      </div>
    </div>
    <div class="scanner-tip item">
      <span>请对准二维码扫码</span>
      <p>扫码结果:{{ textString }}</p>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const textString = ref("")
interface QRtype {
  [key: string]: any;
}
/**
 * 扫码结果回调方法
 * @param detectedCodes
 */
const onDetect = (detectedCodes: QRtype) => {
  console.log(detectedCodes);
  const decodedText = detectedCodes.map((code: QRtype) => code.rawValue);
  (async () => {
    textString.value = decodedText[0];
    console.log(textString.value);
  })();
};

const error = ref("");
/**
 *
 * @param err
 */
const onError = (err: QRtype) => {
  error.value = `[${err.name}]: `;

  if (err.name === "NotAllowedError") {
    error.value += "you need to grant camera access permission";
  } else if (err.name === "NotFoundError") {
    error.value += "no camera on this device";
  } else if (err.name === "NotSupportedError") {
    error.value += "secure context required (HTTPS, localhost)";
  } else if (err.name === "NotReadableError") {
    error.value += "is the camera already in use?";
  } else if (err.name === "OverconstrainedError") {
    error.value += "installed cameras are not suitable";
  } else if (err.name === "StreamApiNotSupportedError") {
    error.value += "Stream API is not supported in this browser";
  } else if (err.name === "InsecureContextError") {
    error.value +=
      "Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.";
  } else {
    error.value += err.message;
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->

<style lang="scss" scoped>
// @media only screen and (min-device-height: 669px) and (max-device-height: 1000px) {
$scannerSize: 300px;
$reader: 280px;

.containers {
  background-color: #0b3de0;
  overflow: auto;
  // padding-bottom: 60px;
  display: flex;
  flex-direction: column;
  height: 100ch;
  position: relative;
  z-index: 10;
  overflow-x: hidden;

  .item {
    flex: 1;
  }

  .card {
    margin-top: 40px;
    width: auto;
    height: 340px;
    position: relative;

    .camare {
      position: absolute;
      top: 0;
      left: 0;
      width: auto;
      height: 320px;
      z-index: 1;
      // display: none;
    }

    .scanner-wrapper {
      // padding-top: 21px;
      display: flex;
      justify-content: center;
      z-index: 99;
      height: 318px;
      position: relative;

      .ab {
        width: $scannerSize;
        height: $scannerSize;
        background-color: transparent;
        position: relative;
        display: flex;
        justify-content: center;
        align-items: center;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        // border: 1px solid rgb(255, 0, 242);
      }

      .ab::before {
        content: "";
        position: absolute;
        top: -10px;
        left: -100%;
        width: 100%;
        height: 110%;
        background-color: #0b3de0;
        z-index: 99;
      }

      .ab::after {
        content: "";
        position: absolute;
        top: -10px;
        right: -100%;
        width: 100%;
        height: 110%;
        background-color: #0b3de0;
        z-index: 99;
      }

      .scanner-bg {
        width: $scannerSize;
        height: $scannerSize;
        background-color: transparent;
        position: relative;
        display: flex;
        justify-content: center;
        align-items: center;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);

        .common {
          position: absolute;
          width: 13.33px;
          height: 13.33px;
        }

        .corner-1 {
          left: 8.89px;
          top: 8.89px;
          border-left: 2.22px solid #0b3de0;
          border-top: 2.22px solid #0b3de0;
        }

        .corner-2 {
          right: 8.89px;
          top: 8.89px;
          border-right: 2.22px solid #0b3de0;
          border-top: 2.22px solid #0b3de0;
        }

        .corner-3 {
          right: 8.89px;
          bottom: 8.89px;
          border-right: 2.22px solid #0b3de0;
          border-bottom: 2.22px solid #0b3de0;
        }

        .corner-4 {
          left: 8.89px;
          bottom: 8.89px;
          border-left: 2.22px solid #0b3de0;
          border-bottom: 2.22px solid #0b3de0;
        }

        .sweep-img {
          position: absolute;
          animation: moveAnimation 1.6s infinite alternate;
          margin: 0 8.89px;

          .img {
            width: 100%;
          }
        }

        .scanner-reader {
          width: $reader;
          height: $reader;
          overflow: hidden;

          #qr-canvas-visible {
            width: 200px;
            height: 200px;
          }
        }
      }
    }
  }

  .scanner-tip {

    padding-top: 18px;
    text-align: center;
    font-size: 16px;
    font-family: Source Han Sans CN-Regular, Source Han Sans CN;
    font-weight: 400;
    color: #fff;
    line-height: 27px;
    p{
      color: red;
    }
  }
}

@keyframes moveAnimation {
  0% {
    top: 0px;
  }

  100% {
    top: 88%;
  }
}
</style>

4、Android 代码

       4.1、权限配置

  <!--关键权限配置-->
    <uses-feature
        android:name="android.hardware.camera"
        android:required="false" />

    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.webkit.PermissionRequest" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

      4.2、代码

package com.example.qrwebview

import android.os.Bundle
import android.webkit.PermissionRequest
import android.webkit.WebChromeClient
import android.webkit.WebSettings
import android.webkit.WebView
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView
import com.example.qrwebview.ui.theme.QRWebViewTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            QRWebViewTheme {
                // A surface container using the 'background' color from the theme
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    Greeting()
                }
            }
        }

    }
}

@Composable
fun Greeting() {
    AndroidView(factory = { context -> WebView(context).apply {
        //h5代码文件放入assets 文件夹下面
        settings.setJavaScriptEnabled(true)
        settings.setUseWideViewPort(true)
        settings.setLoadWithOverviewMode(true)
        settings.setSupportZoom(false)
        settings.setBuiltInZoomControls(false)
        settings.setDisplayZoomControls(false)
        settings.setCacheMode(WebSettings.LOAD_NO_CACHE)
        settings.setAllowFileAccess(true)
        settings.setAllowUniversalAccessFromFileURLs(true)
        settings.setAllowFileAccessFromFileURLs(true)
        settings.setJavaScriptCanOpenWindowsAutomatically(true)
        settings.setDefaultTextEncodingName("utf-8")
        settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW)
        settings.setDomStorageEnabled(true)
        settings.setUserAgentString(settings.getUserAgentString() + "ZDAN")
        settings.mediaPlaybackRequiresUserGesture = false //设置音频自动播放
        WebView.setWebContentsDebuggingEnabled(true)
        webChromeClient = myWebChromeClient()
        loadUrl("file:///android_asset/dist/index.html")

    } })
}

class myWebChromeClient:WebChromeClient(){
    //重写当前方法,关键部分
    override fun onPermissionRequest(request: PermissionRequest?) {
        request?.grant(request.resources)
    }
}

@Preview
@Composable
fun show(){
    Greeting()
}


5、gitee地址:

https://gitee.com/zqmyself/qrweb-view.git

注意:记得动态申请权限(相机相关)

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小曾老师

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

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

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

打赏作者

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

抵扣说明:

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

余额充值