二维码

This commit is contained in:
2026-05-13 17:46:21 +08:00
parent 80837e39bf
commit 14d1969903
6 changed files with 220 additions and 12 deletions

View File

@@ -1,11 +1,15 @@
package com.example.defenseapplication.personal
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.os.Bundle
import android.os.Bundle
import android.os.Environment
import android.text.TextPaint
import android.view.LayoutInflater
import android.view.View
import android.widget.RadioButton
@@ -21,6 +25,8 @@ import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.gyf.immersionbar.ImmersionBar
import java.io.File
import java.io.FileOutputStream
import java.util.Hashtable
//邀请码页面
@@ -29,6 +35,11 @@ class InvitationCodeActivity : AppCompatActivity() {
private lateinit var binding: InvitationCodeActivityBinding
private var expireTimeInMillis: Long = 24 * 60 * 60 * 1000 // 默认24小时
private var selectedExpireOption: Int = 0 // 0: 24h, 1: 7d默认选中24h
private var generatedInviteCode: String = "" // 生成的邀请码
private var expireTimestamp: Long = 0 // 过期时间戳
private var qrCodeContent: String = "" // 生成的二维码内容
private var lastClickTime: Long = 0 // 上次点击时间,用于防止多次点击
private var expireTimeDialog: BottomSheetDialog? = null // 时效选择弹窗
companion object {
const val EXPIRE_24H = 24 * 60 * 60 * 1000L
@@ -62,6 +73,12 @@ class InvitationCodeActivity : AppCompatActivity() {
}
binding.llWechatShare.setOnClickListener {
// 防止多次点击间隔1秒
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime < 3000) {
return@setOnClickListener
}
lastClickTime = currentTime
// 微信分享功能
shareToWeChat()
}
@@ -70,8 +87,8 @@ class InvitationCodeActivity : AppCompatActivity() {
private fun showExpireTimeDialog() {
val dialogBinding = DialogExpireTimeBinding.inflate(LayoutInflater.from(this))
val dialog = BottomSheetDialog(this, R.style.BottomSheetDialog)
dialog.setContentView(dialogBinding.root)
expireTimeDialog = BottomSheetDialog(this, R.style.BottomSheetDialog)
expireTimeDialog?.setContentView(dialogBinding.root)
// 设置选中状态
when (selectedExpireOption) {
@@ -87,7 +104,7 @@ class InvitationCodeActivity : AppCompatActivity() {
}
dialogBinding.tvCancel.setOnClickListener {
dialog.dismiss()
expireTimeDialog?.dismiss()
}
dialogBinding.tvConfirm.setOnClickListener {
@@ -104,10 +121,10 @@ class InvitationCodeActivity : AppCompatActivity() {
// 选择后显示二维码并生成
showQrCode()
generateQRCode()
dialog.dismiss()
expireTimeDialog?.dismiss()
}
dialog.show()
expireTimeDialog?.show()
}
private fun hideQrCode() {
@@ -133,8 +150,10 @@ class InvitationCodeActivity : AppCompatActivity() {
private fun generateInviteCode(): String {
// 生成包含邀请信息的JSON字符串
val timestamp = System.currentTimeMillis()
val expireTime = timestamp + expireTimeInMillis
return "{\"type\":\"invite\",\"code\":\"INV${timestamp}\",\"expire\":$expireTime}"
expireTimestamp = timestamp + expireTimeInMillis
generatedInviteCode = "INV${timestamp}"
qrCodeContent = "{\"type\":\"invite\",\"code\":\"$generatedInviteCode\",\"expire\":$expireTimestamp}"
return qrCodeContent
}
private fun createQRCodeWithLogo(content: String, width: Int, height: Int): Bitmap? {
@@ -142,6 +161,8 @@ class InvitationCodeActivity : AppCompatActivity() {
val hints = Hashtable<EncodeHintType, Any>()
hints[EncodeHintType.CHARACTER_SET] = "UTF-8"
hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.H
// 设置二维码边距为最小1
hints[EncodeHintType.MARGIN] = 1
val writer = QRCodeWriter()
val bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints)
@@ -173,6 +194,22 @@ class InvitationCodeActivity : AppCompatActivity() {
}
}
// 在二维码中间绘制logo
val logoDrawable = ContextCompat.getDrawable(this, R.mipmap.loge)
if (logoDrawable != null) {
// logo大小为二维码的1/5
val logoSize = width / 5
val logoX = (width - logoSize) / 2
val logoY = (height - logoSize) / 2
val logoBitmap = Bitmap.createBitmap(logoSize, logoSize, Bitmap.Config.ARGB_8888)
val logoCanvas = Canvas(logoBitmap)
logoDrawable.setBounds(0, 0, logoSize, logoSize)
logoDrawable.draw(logoCanvas)
canvas.drawBitmap(logoBitmap, logoX.toFloat(), logoY.toFloat(), null)
}
qrBitmap
} catch (e: Exception) {
e.printStackTrace()
@@ -181,11 +218,160 @@ class InvitationCodeActivity : AppCompatActivity() {
}
private fun shareToWeChat() {
// 微信分享实现
// 这里需要集成微信SDK
// 检查二维码是否过期
if (expireTimestamp == 0L || System.currentTimeMillis() > expireTimestamp) {
ToastUtils.showError(this, getString(R.string.qr_code_expired))
return
}
// 生成分享海报
val shareBitmap = generateSharePoster()
if (shareBitmap == null) {
ToastUtils.showError(this, getString(R.string.share_failed))
return
}
// 保存图片到本地
val imagePath = saveBitmapToFile(shareBitmap)
if (imagePath.isEmpty()) {
ToastUtils.showError(this, getString(R.string.share_failed))
return
}
// 使用系统分享功能
shareImage(imagePath)
}
private fun generateSharePoster(): Bitmap? {
return try {
// 获取空海报背景图
val posterBackground = ContextCompat.getDrawable(this, R.drawable.share_poster_empty)
?: return null
// 创建画布
val width = posterBackground.intrinsicWidth
val height = posterBackground.intrinsicHeight
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
// 绘制背景图
posterBackground.setBounds(0, 0, width, height)
posterBackground.draw(canvas)
// 使用已生成的二维码内容生成二维码
val qrCode = createQRCodeWithLogo(qrCodeContent, 300, 300)
?: return bitmap
// 绘制二维码到海报上
val qrSize = (width * 0.38f).toInt()
val qrX = (width - qrSize) / 2
val qrY = (height * 0.60f).toInt()
val scaledQr = Bitmap.createScaledBitmap(qrCode, qrSize, qrSize, true)
canvas.drawBitmap(scaledQr, qrX.toFloat(), qrY.toFloat(), null)
// 获取时效文本
val expireText = if (expireTimeInMillis == EXPIRE_24H) {
getString(R.string.expire_24h)
} else {
getString(R.string.expire_7d)
}
// 绘制底部提示文字
val tipsText = getString(R.string.share_poster_tips, expireText)
val tipsPaint = android.text.TextPaint().apply {
color = Color.parseColor("#0475F3")
textSize = 36f
isAntiAlias = true
textAlign = Paint.Align.CENTER
isFakeBoldText = false
}
val horizontalPadding = (width * 0.12f).toInt()
val textWidth = width - horizontalPadding * 2 - (width * 0.08f).toInt()
val staticLayout = android.text.StaticLayout.Builder.obtain(
tipsText, 0, tipsText.length, tipsPaint, textWidth
)
.setAlignment(android.text.Layout.Alignment.ALIGN_CENTER)
.setLineSpacing(0f, 1.4f)
.build()
// 计算文字位置:距离二维码底部有固定间距
val qrBottom = qrY + qrSize
val marginBelowQr = (height * 0.04f).toInt() // 二维码下方间距
val textY = qrBottom + marginBelowQr
// 向右偏移
val rightOffset = (width * 0.35f).toInt()
canvas.save()
canvas.translate((horizontalPadding + rightOffset).toFloat(), textY.toFloat())
staticLayout.draw(canvas)
canvas.restore()
bitmap
} catch (e: Exception) {
e.printStackTrace()
null
}
}
private fun saveBitmapToFile(bitmap: Bitmap): String {
return try {
val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
?: return ""
val file = File(storageDir, "share_poster_${System.currentTimeMillis()}.png")
val fos = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
fos.flush()
fos.close()
file.absolutePath
} catch (e: Exception) {
e.printStackTrace()
""
}
}
private fun shareImage(imagePath: String) {
val file = File(imagePath)
if (!file.exists()) {
ToastUtils.showError(this, getString(R.string.share_failed))
return
}
// 使用 FileProvider 获取内容 URI
val authority = "${packageName}.fileprovider"
val contentUri = androidx.core.content.FileProvider.getUriForFile(this, authority, file)
val intent = Intent(Intent.ACTION_SEND).apply {
type = "image/*"
putExtra(Intent.EXTRA_STREAM, contentUri)
putExtra(Intent.EXTRA_TEXT, getString(R.string.invite_friend))
// 授予读取权限
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
// 为所有匹配的应用授予权限
val resInfoList = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
for (resolveInfo in resInfoList) {
val packageName = resolveInfo.activityInfo.packageName
grantUriPermission(packageName, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val chooser = Intent.createChooser(intent, getString(R.string.share_to))
if (intent.resolveActivity(packageManager) != null) {
startActivity(chooser)
} else {
ToastUtils.showError(this, getString(R.string.no_share_app))
}
}
override fun onDestroy() {
super.onDestroy()
// 关闭弹窗,防止内存泄漏
expireTimeDialog?.dismiss()
}
}