Kotlin高仿微信-第54篇-扫一扫
创始人
2024-03-14 19:06:04
0

  Kotlin高仿微信-项目实践58篇详细讲解了各个功能点,包括:注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。

Kotlin高仿微信-项目实践58篇,点击查看详情

效果图:

实现代码:





/*** Author : wangning* Email : maoning20080809@163.com* Date : 2022/5/19 18:01* Description :*/
class ViewfinderView : View {/*** 刷新界面的时间*/private val ANIMATION_DELAY = 10Lprivate val OPAQUE = 0xFF/*** 四个绿色边角对应的长度*/private var ScreenRate = 0/*** 四个绿色边角对应的宽度*/private val CORNER_WIDTH = 5/*** 扫描框中的中间线的宽度*/private val MIDDLE_LINE_WIDTH = 6/*** 扫描框中的中间线的与扫描框左右的间隙*/private val MIDDLE_LINE_PADDING = 5/*** 中间那条线每次刷新移动的距离*/private val SPEEN_DISTANCE = 5/*** 手机的屏幕密度*/private var density = 0f/*** 字体大小*/private val TEXT_SIZE = 16/*** 字体距离扫描框下面的距离*/private val TEXT_PADDING_TOP = 30/*** 画笔对象的引用*/private var paint: Paint? = null/*** 中间滑动线的最顶端位置*/private var slideTop = 0/*** 中间滑动线的最底端位置*/private var slideBottom = 0/*** 将扫描的二维码拍下来,这里没有这个功能,暂时不考虑*/private var resultBitmap: Bitmap? = nullprivate var maskColor = 0private var resultColor = 0private var resultPointColor = 0private var possibleResultPoints: MutableCollection? = nullprivate var lastPossibleResultPoints: Collection? = nullvar isFirst = falseconstructor(context: Context, attrs: AttributeSet?) : super(context, attrs){density = context.resources.displayMetrics.density//将像素转换成dpScreenRate = (15 * density).toInt()paint = Paint()val resources = resourcesmaskColor = resources.getColor(R.color.viewfinder_mask)resultColor = resources.getColor(R.color.result_view)resultPointColor = resources.getColor(R.color.possible_result_points)possibleResultPoints = HashSet(5)}override fun onDraw(canvas: Canvas) {//中间的扫描框,你要修改扫描框的大小,去CameraManager里面修改val frame: Rect = CameraManager.get()?.getFramingRect() ?: return//初始化中间线滑动的最上边和最下边if (!isFirst) {isFirst = trueslideTop = frame.topslideBottom = frame.bottom}//获取屏幕的宽和高val width = canvas.widthval height = canvas.heightpaint!!.color = if (resultBitmap != null) resultColor else maskColor//画出扫描框外面的阴影部分,共四个部分,扫描框的上面到屏幕上面,扫描框的下面到屏幕下面//扫描框的左边面到屏幕左边,扫描框的右边到屏幕右边canvas.drawRect(0f, 0f, width.toFloat(), frame.top.toFloat(), paint!!)canvas.drawRect(0f, frame.top.toFloat(), frame.left.toFloat(), (frame.bottom + 1).toFloat(),paint!!)canvas.drawRect((frame.right + 1).toFloat(),frame.top.toFloat(),width.toFloat(),(frame.bottom + 1).toFloat(),paint!!)canvas.drawRect(0f,(frame.bottom + 1).toFloat(),width.toFloat(),height.toFloat(),paint!!)if (resultBitmap != null) {// Draw the opaque result bitmap over the scanning rectanglepaint!!.alpha = OPAQUEcanvas.drawBitmap(resultBitmap!!, frame.left.toFloat(), frame.top.toFloat(), paint)} else {//画扫描框边上的角,总共8个部分paint!!.color = Color.GREENcanvas.drawRect(frame.left.toFloat(), frame.top.toFloat(), (frame.left + ScreenRate).toFloat(), (frame.top + CORNER_WIDTH).toFloat(),paint!!)canvas.drawRect(frame.left.toFloat(),frame.top.toFloat(),(frame.left + CORNER_WIDTH).toFloat(),(frame.top+ ScreenRate).toFloat(),paint!!)canvas.drawRect((frame.right - ScreenRate).toFloat(), frame.top.toFloat(), frame.right.toFloat(), (frame.top + CORNER_WIDTH).toFloat(),paint!!)canvas.drawRect((frame.right - CORNER_WIDTH).toFloat(),frame.top.toFloat(),frame.right.toFloat(),(frame.top+ ScreenRate).toFloat(),paint!!)canvas.drawRect(frame.left.toFloat(), (frame.bottom - CORNER_WIDTH).toFloat(), (frame.left+ ScreenRate).toFloat(), frame.bottom.toFloat(), paint!!)canvas.drawRect(frame.left.toFloat(), (frame.bottom - ScreenRate).toFloat(), (frame.left + CORNER_WIDTH).toFloat(), frame.bottom.toFloat(), paint!!)canvas.drawRect((frame.right - ScreenRate).toFloat(), (frame.bottom - CORNER_WIDTH).toFloat(),frame.right.toFloat(), frame.bottom.toFloat(), paint!!)canvas.drawRect((frame.right - CORNER_WIDTH).toFloat(), (frame.bottom - ScreenRate).toFloat(),frame.right.toFloat(), frame.bottom.toFloat(), paint!!)//绘制中间的线,每次刷新界面,中间的线往下移动SPEEN_DISTANCEslideTop += SPEEN_DISTANCEif (slideTop >= frame.bottom) {slideTop = frame.top}val lineRect = Rect()lineRect.left = frame.leftlineRect.right = frame.rightlineRect.top = slideToplineRect.bottom = slideTop + 18canvas.drawBitmap((resources.getDrawable(R.drawable.qrcode_scan_line) as BitmapDrawable).bitmap,null,lineRect,paint)//画扫描框下面的字paint!!.color = Color.WHITEpaint!!.textSize = TEXT_SIZE * densitypaint!!.alpha = 0x40paint!!.typeface = Typeface.create("System", Typeface.BOLD)val text = resources.getString(R.string.scan_text)val textWidth = paint!!.measureText(text)canvas.drawText(text, (width - textWidth) / 2, frame.bottom + TEXT_PADDING_TOP.toFloat() * density,paint!!)val currentPossible: Collection? = possibleResultPointsval currentLast = lastPossibleResultPointsif (currentPossible!!.isEmpty()) {lastPossibleResultPoints = null} else {possibleResultPoints = HashSet(5)lastPossibleResultPoints = currentPossiblepaint!!.alpha = OPAQUEpaint!!.color = resultPointColorfor (point in currentPossible) {canvas.drawCircle(frame.left + point.x, frame.top+ point.y, 6.0f, paint!!)}}if (currentLast != null) {paint!!.alpha = OPAQUE / 2paint!!.color = resultPointColorfor (point in currentLast) {canvas.drawCircle(frame.left + point.x, frame.top+ point.y, 3.0f, paint!!)}}//只刷新扫描框的内容,其他地方不刷新postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top,frame.right, frame.bottom)}}fun drawViewfinder() {resultBitmap = nullinvalidate()}/*** Draw a bitmap with the result points highlighted instead of the live* scanning display.** @param barcode* An image of the decoded barcode.*/fun drawResultBitmap(barcode: Bitmap?) {resultBitmap = barcodeinvalidate()}fun addPossibleResultPoint(point: ResultPoint) {possibleResultPoints!!.add(point)}}

/*** Author : wangning* Email : maoning20080809@163.com* Date : 2022/5/19 18:10* Description : 扫一扫*/
class QRCodeScanFragment : BaseDataBindingFragment(), Callback{private var handler: CaptureActivityHandler? = nullprivate var viewfinderView: ViewfinderView? = nullprivate var hasPermission = falseprivate var hasSurface = falseprivate var decodeFormats: Vector? = nullprivate var characterSet: String? = nullprivate var inactivityTimer: InactivityTimer? = nullprivate var mediaPlayer: MediaPlayer? = nullprivate var playBeep = falseprivate val BEEP_VOLUME = 0.10fprivate var vibrate = falseprivate val REQUEST_CODE = 100private val REQUEST_CAMERA_CODE = 101private val PARSE_BARCODE_SUC = 300private val PARSE_BARCODE_FAIL = 303private var mProgress: ProgressDialog? = nullprivate var photo_path: String? = nullprivate var scanBitmap: Bitmap? = nullprivate var navCollection : NavController? = nulloverride fun getLayoutRes() = R.layout.qrcode_activity_captureoverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)TagUtils.d("扫描 onViewCreated ")navCollection = findNavController()handlePermission()}fun init() {TagUtils.d("扫描 init ")CameraManager.init(requireActivity())viewfinderView = viewfinder_viewhasSurface = falseinactivityTimer = InactivityTimer(requireActivity())}companion object {fun onOpen(activity: Activity){var intent = Intent(activity, QRCodeScanFragment::class.java)intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)WcApp.getContext().startActivity(intent)}}private fun handlePermission() {if(ContextCompat.checkSelfPermission(requireActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_CODE)} else {hasPermission = trueinit()}}override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, grantResults: IntArray ) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)if(requestCode == REQUEST_CAMERA_CODE && grantResults != null && grantResults.size > 0){if(grantResults[0] == PackageManager.PERMISSION_GRANTED){TagUtils.d("扫描 onRequestPermissionsResult ")hasPermission = trueinit()val surfaceHolder = preview_view.holderinitCamera(surfaceHolder)}}}//    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
//        /**
//         * 此方法用于初始化菜单,其中menu参数就是即将要显示的Menu实例。 返回true则显示该menu,false 则不显示;
//         * (只会在第一次初始化菜单时调用) Inflate the menu; this adds items to the action bar
//         * if it is present.
//         */
//        menuInflater.inflate(R.menu.qrcode_activity_main, menu)
//        return true
//    }//    override fun onOptionsItemSelected(item: MenuItem): Boolean {
//        /**
//         * 菜单项被点击时调用,也就是菜单项的监听方法。
//         * 通过这几个方法,可以得知,对于Activity,同一时间只能显示和监听一个Menu 对象。 TODO Auto-generated
//         * method stub
//         */
//        when (item.itemId) {
//            R.id.menu_settings -> {
//                //打开手机中的相册
//                val innerIntent =
//                    Intent(Intent.ACTION_GET_CONTENT) //"android.intent.action.GET_CONTENT"
//                innerIntent.type = "image/*"
//                val wrapperIntent = Intent.createChooser(innerIntent, "选择二维码图片")
//                this.startActivityForResult(wrapperIntent, REQUEST_CODE)
//            }
//        }
//        return super.onOptionsItemSelected(item)
//    }private val mHandler: Handler = MyHandler(this)inner class MyHandler(activity: QRCodeScanFragment) : Handler() {private val activityReference: WeakReferenceoverride fun handleMessage(msg: Message) {val activity = activityReference.get()activity!!.mProgress!!.dismiss()when (msg.what) {PARSE_BARCODE_SUC -> activity.onResultHandler(msg.obj as String,activity.scanBitmap)PARSE_BARCODE_FAIL -> Toast.makeText(requireActivity(),msg.obj as String,Toast.LENGTH_LONG).show()}super.handleMessage(msg)}init {activityReference = WeakReference(activity)}}/*override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {super.onActivityResult(requestCode, resultCode, data)if (resultCode == RESULT_OK) {when (requestCode) {REQUEST_CODE -> {//获取选中图片的路径val cursor = contentResolver.query(data.data!!, null, null, null, null)if (cursor!!.moveToFirst()) {photo_path =cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))}cursor.close()mProgress = ProgressDialog(this@MipcaActivityCapture)mProgress!!.setMessage("正在扫描...")mProgress!!.setCancelable(false)mProgress!!.show()Thread {val result = scanningImage(photo_path)if (result != null) {val m = mHandler.obtainMessage()m.what = PARSE_BARCODE_SUCm.obj = result.textmHandler.sendMessage(m)} else {val m = mHandler.obtainMessage()m.what = PARSE_BARCODE_FAILm.obj = "Scan failed!"mHandler.sendMessage(m)}}.start()}}}}*//*** 扫描二维码图片的方法* @param path* @return*/fun scanningImage(path: String?): Result? {if (TextUtils.isEmpty(path)) {return null}val hints = Hashtable()hints[DecodeHintType.CHARACTER_SET] = "UTF8" //设置二维码内容的编码val options = BitmapFactory.Options()options.inJustDecodeBounds = true // 先获取原大小scanBitmap = BitmapFactory.decodeFile(path, options)options.inJustDecodeBounds = false // 获取新的大小var sampleSize = (options.outHeight / 200f).toInt()if (sampleSize <= 0) sampleSize = 1options.inSampleSize = sampleSizescanBitmap = BitmapFactory.decodeFile(path, options)val source = RGBLuminanceSource(scanBitmap!!)val bitmap1 = BinaryBitmap(HybridBinarizer(source))val reader = QRCodeReader()try {return reader.decode(bitmap1, hints)} catch (e: NotFoundException) {e.printStackTrace()} catch (e: ChecksumException) {e.printStackTrace()} catch (e: FormatException) {e.printStackTrace()}return null}override fun onResume() {super.onResume()TagUtils.d("扫描 onResume ${hasSurface} , ${hasPermission}")//没有权限直接返回if(!hasPermission){return}//val surfaceView = findViewById(R.id.preview_view) as SurfaceViewval surfaceView = preview_viewval surfaceHolder = surfaceView.holderif (hasSurface) {initCamera(surfaceHolder)} else {surfaceHolder.addCallback(this)surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)}decodeFormats = nullcharacterSet = nullplayBeep = trueval audioService = requireActivity().getSystemService(AUDIO_SERVICE) as AudioManagerif (audioService.ringerMode != AudioManager.RINGER_MODE_NORMAL) {playBeep = false}initBeepSound()vibrate = true}override fun onPause() {super.onPause()TagUtils.d("扫描 onPause ${hasPermission}")if(!hasPermission){return}handler?.quitSynchronously()handler = nullCameraManager.get()?.closeDriver()}override fun onDestroy() {TagUtils.d("扫描 onDestroy ")inactivityTimer?.shutdown()super.onDestroy()}/*** 处理扫描结果* @param result* @param barcode*/fun handleDecode(result: Result, barcode: Bitmap?) {inactivityTimer!!.onActivity()playBeepSoundAndVibrate()val resultString = result.textonResultHandler(resultString, barcode)}/*** 跳转到上一个页面* @param resultString* @param bitmap*/private fun onResultHandler(resultString: String, bitmap: Bitmap?) {if (TextUtils.isEmpty(resultString)) {Toast.makeText(requireActivity(), "Scan failed!", Toast.LENGTH_SHORT).show()return}TagUtils.d("二维码返回字符串:${resultString}")Navigation.findNavController(preview_view).popBackStack()if(resultString.startsWith(CommonUtils.QRCommon.QR_RECEIVE_CODE)){//向个人付款TagUtils.d("跳转向个人付款 ")var tempResult = resultString.substring(CommonUtils.QRCommon.QR_RECEIVE_CODE.length, resultString.length)var toUser = tempResult.split(":")[1]var balance = tempResult.split(":")[0]var bundle = bundleOf(CommonUtils.QRCommon.BALANCE to balance.toFloat(), CommonUtils.QRCommon.TO_USER to toUser)navCollection?.navigate(R.id.action_personal_payment, bundle)} else if(resultString.startsWith(CommonUtils.QRCommon.QR_PAYMENT_CODE)){//向商家付款var toUser = resultString.substring(CommonUtils.QRCommon.QR_PAYMENT_CODE.length, resultString.length)TagUtils.d("跳转向商家付款 ${toUser}")var bundle = bundleOf(CommonUtils.QRCommon.TO_USER to toUser)navCollection?.navigate(R.id.action_merchant_payment, bundle)} else if(resultString.startsWith("http") || resultString.startsWith("https")){//跳转到网站CommonUtils.Base.goWebsite(resultString)} else if(resultString.startsWith(CommonUtils.QRCommon.QR_ADD_FRIEND)){var userInfo = resultString.substring(CommonUtils.QRCommon.QR_ADD_FRIEND.length, resultString.length)//account+"###"+it.nickName+"###"+it.avatarvar userInfos = userInfo.split("###")var toUser = userInfos[0]var nickName = userInfos[1]var avatar = userInfos[2]var contactsBean = ContactsRepository.getContactsLocalAsync(toUser)if(contactsBean != null){//如果是好友,直接打开聊天var bundle = bundleOf(ChatFragment.USER_ID to toUser)navCollection?.navigate(R.id.nav_chat, bundle)} else {var userBean = UserBean(toUser)userBean.avatar = avataruserBean.nickName = nickName//跳转到添加好友页面val bundle = bundleOf("userbean" to userBean)navCollection?.navigate(R.id.action_contacts_search_friends_details, bundle)}} else {TagUtils.d("跳转空")}}private fun initCamera(surfaceHolder: SurfaceHolder) {TagUtils.d("扫描 initCamera ")try {CameraManager.get()?.openDriver(surfaceHolder)} catch (ioe: IOException) {return} catch (e: RuntimeException) {return}if (handler == null) {handler = CaptureActivityHandler( this, decodeFormats, characterSet )}}override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int,height: Int) {}override fun surfaceCreated(holder: SurfaceHolder) {TagUtils.d("扫描 surfaceCreated ")if (!hasSurface) {hasSurface = trueinitCamera(holder)}}override fun surfaceDestroyed(holder: SurfaceHolder) {hasSurface = false}fun getViewfinderView(): ViewfinderView? {return viewfinderView}fun getHandler(): Handler? {return handler}fun drawViewfinder() {viewfinderView!!.drawViewfinder()}private fun initBeepSound() {if (playBeep && mediaPlayer == null) {// The volume on STREAM_SYSTEM is not adjustable, and users found it// too loud,// so we now play on the music stream.requireActivity().volumeControlStream = AudioManager.STREAM_MUSICmediaPlayer = MediaPlayer()mediaPlayer!!.setAudioStreamType(AudioManager.STREAM_MUSIC)mediaPlayer!!.setOnCompletionListener(beepListener)val file = resources.openRawResourceFd(R.raw.beep)try {mediaPlayer!!.setDataSource(file.fileDescriptor,file.startOffset, file.length)file.close()mediaPlayer!!.setVolume(BEEP_VOLUME, BEEP_VOLUME)mediaPlayer!!.prepare()} catch (e: IOException) {mediaPlayer = null}}}private val VIBRATE_DURATION = 200Lprivate fun playBeepSoundAndVibrate() {if (playBeep && mediaPlayer != null) {mediaPlayer!!.start()}if (vibrate) {val vibrator = requireActivity().getSystemService(VIBRATOR_SERVICE) as Vibratorvibrator.vibrate(VIBRATE_DURATION)}}/*** When the beep has finished playing, rewind to queue up another one.*/private val beepListener =OnCompletionListener { mediaPlayer -> mediaPlayer.seekTo(0) }}

相关内容

热门资讯

聚杰微纤(300819)披露制... 截至2025年12月25日收盘,聚杰微纤(300819)报收于28.81元,较前一交易日上涨3.0%...
康曼德资本董事长丁楹:A股将进... 2025年A股在政策、估值、盈利、资金四重支撑下走出了牛市行情,但市场细分赛道的分化却愈发明显。20...
缅甸妙瓦底KK园区等已被强力拆... 视频来源:公安部微信公众号 记者12月25日从公安部获悉,近日,公安部派出工作组会同缅甸、泰国执法部...
盐田港(000088)披露公司... 截至2025年12月25日收盘,盐田港(000088)报收于4.55元,较前一交易日上涨0.66%,...
952名缅甸妙瓦底地区涉电诈犯... 来源:人民日报客户端 中缅泰联合开展清剿缅甸妙瓦底地区 赌诈园区行动 952名缅甸妙瓦底地区涉电诈犯...
原创 新... 最近几个赛季,孙铭徽一直都被视为广厦的“小外援”,距离他上一次场均得分不到两位数,还要追溯到2018...
意大利要求Meta暂停禁止竞争... 意大利已下令Meta公司暂停其禁止企业在WhatsApp上使用商业工具提供自家AI聊天机器人的政策。...
山西证券(002500)披露现... 截至2025年12月25日收盘,山西证券(002500)报收于6.11元,较前一交易日上涨0.33%...