You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android双Fragment场景下ZXING扫码器嵌入顶部Fragment的实现咨询

如何在Fragment中嵌入ZXing扫码器(而非全屏启动)

我之前也碰到过这个问题——默认的ZXing扫码方式确实会直接拉起全屏的CaptureActivity,完全不符合我们需要嵌入Fragment的需求。其实核心思路是不依赖ZXing自带的全屏Activity,而是直接将扫码预览View集成到你的Fragment布局中。下面是具体的实现步骤,用zxing-android-embedded这个封装库来简化开发:

步骤1:添加依赖

在Module级的build.gradle(或build.gradle.kts)中引入库依赖:

implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
// AndroidX环境下无需额外兼容库,该库已适配

建议使用最新稳定版本,你可以在Maven仓库确认版本号更新。

步骤2:准备顶部Fragment的布局

给顶部Fragment的布局添加一个容器,用来承载扫码预览,让它占据屏幕上半部分:

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

    <!-- 扫码预览容器,占上半屏 -->
    <FrameLayout
        android:id="@+id/scan_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <!-- 可选:添加扫码提示文本 -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="对准二维码/条码"
        android:gravity="center"
        android:padding="8dp"/>

</LinearLayout>

步骤3:在顶部Fragment中初始化扫码功能

在Fragment代码里,初始化扫码View、设置回调,并处理生命周期,确保扫码器持续运行:

class TopScanFragment : Fragment() {
    private lateinit var barcodeView: BarcodeView
    // 用于向底部Fragment传递结果的回调(或用ViewModel)
    private var scanResultListener: ((String) -> Unit)? = null
    private val CAMERA_PERMISSION_REQUEST_CODE = 100

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_top_scan, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 初始化扫码View
        barcodeView = BarcodeView(requireContext())
        // 配置扫码类型:QR码+条码
        val captureConfig = CaptureConfig()
        captureConfig.setBarcodeFormats(BarcodeFormat.QR_CODE, BarcodeFormat.CODE_128)
        barcodeView.captureConfig = captureConfig

        // 将扫码View添加到布局容器
        view.findViewById<FrameLayout>(R.id.scan_container).addView(barcodeView)

        // 设置持续扫码回调
        barcodeView.decodeContinuous(object : BarcodeCallback {
            override fun barcodeResult(result: BarcodeResult) {
                // 传递扫码结果给底部Fragment
                scanResultListener?.invoke(result.text)
                // 注意:不要调用stop(),保持扫码器持续运行
            }

            override fun possibleResultPoints(resultPoints: List<ResultPoint>) {
                // 可选:处理扫描过程中的点位提示
            }
        })

        // 申请相机权限
        checkCameraPermission()
    }

    // 权限检查与申请
    private fun checkCameraPermission() {
        if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST_CODE)
        } else {
            barcodeView.resume()
        }
    }

    // 处理权限申请结果
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                barcodeView.resume()
            } else {
                Toast.makeText(requireContext(), "需要相机权限才能扫码", Toast.LENGTH_SHORT).show()
            }
        }
    }

    // 生命周期绑定:避免相机资源浪费
    override fun onResume() {
        super.onResume()
        if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
            barcodeView.resume()
        }
    }

    override fun onPause() {
        super.onPause()
        barcodeView.pause()
    }

    // 暴露给Activity设置结果回调的方法
    fun setScanResultListener(listener: (String) -> Unit) {
        this.scanResultListener = listener
    }
}

步骤4:实现与底部Fragment的通信

推荐两种解耦的通信方式:

方式1:共享ViewModel(更推荐)

创建一个跨Fragment共享的ViewModel,用来传递扫码结果:

class ScanViewModel : ViewModel() {
    val scanResult = MutableLiveData<String>()
}

顶部Fragment更新结果:

// 在barcodeResult回调中
viewModel.scanResult.postValue(result.text)

底部Fragment观察结果并更新UI:

class BottomDisplayFragment : Fragment() {
    private lateinit var viewModel: ScanViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProvider(requireActivity())[ScanViewModel::class.java]
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.scanResult.observe(viewLifecycleOwner) { result ->
            view.findViewById<TextView>(R.id.tv_scan_result).text = result
        }
    }
}

方式2:接口回调

在Activity中作为中间层传递结果:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val topFragment = supportFragmentManager.findFragmentById(R.id.fragment_top) as TopScanFragment
        val bottomFragment = supportFragmentManager.findFragmentById(R.id.fragment_bottom) as BottomDisplayFragment

        topFragment.setScanResultListener { result ->
            bottomFragment.updateResult(result)
        }
    }
}

底部Fragment添加更新方法:

fun updateResult(result: String) {
    view?.findViewById<TextView>(R.id.tv_scan_result)?.text = result
}

关键注意事项

  • 不要调用barcodeView.stop(),这样扫码器会持续识别新的条码/QR码,符合你的需求。
  • 必须绑定Fragment生命周期,在onResume启动预览、onPause暂停,避免内存泄漏或相机占用冲突。
  • 如果需要自定义扫码界面(比如添加扫描框、闪光灯开关),可以使用库提供的DecoratedBarcodeView,它支持叠加装饰布局。

内容的提问来源于stack exchange,提问作者Solx85

火山引擎 最新活动