From 85d11d6cd12abdd1e1f5f7516a7fb53a4826633f Mon Sep 17 00:00:00 2001 From: cloudroam <cloudroam> Date: 星期二, 15 四月 2025 09:17:18 +0800 Subject: [PATCH] add: 消息提醒 --- app/src/main/java/com/example/firstapp/ui/invitation/InvitationActivity.kt | 384 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 files changed, 334 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/com/example/firstapp/ui/invitation/InvitationActivity.kt b/app/src/main/java/com/example/firstapp/ui/invitation/InvitationActivity.kt index 51fc5fc..353707d 100644 --- a/app/src/main/java/com/example/firstapp/ui/invitation/InvitationActivity.kt +++ b/app/src/main/java/com/example/firstapp/ui/invitation/InvitationActivity.kt @@ -1,98 +1,382 @@ package com.example.firstapp.ui.invitation +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.net.Uri import android.os.Bundle import android.os.Handler import android.os.Looper -import android.util.DisplayMetrics +import android.text.Html +import android.util.TypedValue +import android.view.View +import android.widget.Button +import android.widget.TextView +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.RecyclerView import com.example.firstapp.R import com.example.firstapp.adapter.InvitationAdapter +import com.example.firstapp.adapter.InvitationRecordAdapter import com.example.firstapp.entity.InvitationRecord -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.Executors +import com.example.firstapp.utils.PreferencesManager +import kotlin.math.abs class InvitationActivity : AppCompatActivity() { - private lateinit var recyclerView: RecyclerView + private lateinit var recyclerSuccessView: RecyclerView + private lateinit var recyclerRecordView: RecyclerView private lateinit var adapter: InvitationAdapter - private var currentPosition = 0 + private lateinit var recordadapter: InvitationRecordAdapter + private val data = mutableListOf<InvitationRecord>() + private val recorddata = mutableListOf<InvitationRecord>() private val handler = Handler(Looper.getMainLooper()) - private lateinit var scrollRunnable: Runnable + private val scrollInterval = 3000L + private var currentScrollPosition = 0 + private var currentRecordScrollPosition = 0 + private var itemHeight = 0 // 动态存储item高度 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_invitation_main) - setupRecyclerView() + // 初始化视图 + initViews() + + //初始化Adapter + initSuccessAdapter() + + initRecorddapter() + + //加载数据 loadData() - setupAutoScroll() + + loadRecordData() + + //启动轮播 + startAutoScroll() + + //分享 + val btnInvite = findViewById<Button>(R.id.btnInvite) + btnInvite.setOnClickListener { + shareImageToWechat() + } + + //邀请码 + val invitationCodeText = findViewById<TextView>(R.id.invitationCodeText) + invitationCodeText.text = "A1B2" + //invitationCodeText.text = formatInvitationCode(PreferencesManager.getInviteCode()); + findViewById<Button>(R.id.copyButton).setOnClickListener { + val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText( + "邀请码", + invitationCodeText.text.toString().replace(" ", "") // 复制时去掉空格 + ) + clipboard.setPrimaryClip(clip) + Toast.makeText(this, "已复制邀请码", Toast.LENGTH_SHORT).show() + } } - private fun setupRecyclerView() { - recyclerView = findViewById(R.id.invitationsuccessRecyclerView) - recyclerView.layoutManager = LinearLayoutManager(this).apply { - stackFromEnd = false // 从顶部开始布局 + private fun initViews() { + findViewById<TextView>(R.id.tv_notic).apply { + text = Html.fromHtml(getString(R.string.invite_reward_text), Html.FROM_HTML_MODE_LEGACY) } - adapter = InvitationAdapter() - recyclerView.adapter = adapter + recyclerSuccessView = findViewById(R.id.invitationsuccessRecyclerView) + addSuccessListener() + + recyclerRecordView = findViewById(R.id.invitationrecordRecyclerView) + addRecordListener() + } + + private fun addSuccessListener() { + recyclerSuccessView.viewTreeObserver.addOnGlobalLayoutListener { + if (recyclerSuccessView.childCount > 0) { + // 计算预期高度(60dp转px) + val expectedHeight = dpToPx(60f) + if (itemHeight != expectedHeight) { + // 修正所有item的高度 + for (i in 0 until recyclerSuccessView.childCount) { + recyclerSuccessView.getChildAt(i).layoutParams.height = expectedHeight + } + // 请求重新布局 + recyclerSuccessView.requestLayout() + itemHeight = expectedHeight + } + } + } + recyclerSuccessView.layoutManager = object : LinearLayoutManager(this@InvitationActivity) { + override fun onMeasure( + recycler: RecyclerView.Recycler, state: RecyclerView.State, + widthSpec: Int, heightSpec: Int + ) { + if (itemHeight > 0) { + // 使用实际测量的高度 + setMeasuredDimension( + View.resolveSize(widthSpec, width), + itemHeight + ) + } else { + // 默认高度60dp + val defaultHeight = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 60f, + resources.displayMetrics + ).toInt() + setMeasuredDimension( + View.resolveSize(widthSpec, width), + defaultHeight + ) + } + } + } + } + + private fun addRecordListener() { + recyclerRecordView.viewTreeObserver.addOnGlobalLayoutListener { + if (recyclerSuccessView.childCount > 0) { + // 计算预期高度(60dp转px) + val expectedHeight = dpToPx(60f) + if (itemHeight != expectedHeight) { + // 修正所有item的高度 + for (i in 0 until recyclerSuccessView.childCount) { + recyclerSuccessView.getChildAt(i).layoutParams.height = expectedHeight + } + // 请求重新布局 + recyclerSuccessView.requestLayout() + itemHeight = expectedHeight + } + } + } + recyclerRecordView.layoutManager = object : LinearLayoutManager(this@InvitationActivity) { + override fun onMeasure( + recycler: RecyclerView.Recycler, state: RecyclerView.State, + widthSpec: Int, heightSpec: Int + ) { + if (itemHeight > 0) { + // 使用实际测量的高度 + setMeasuredDimension( + View.resolveSize(widthSpec, width), + itemHeight + ) + } else { + // 默认高度60dp + val defaultHeight = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 60f, + resources.displayMetrics + ).toInt() + setMeasuredDimension( + View.resolveSize(widthSpec, width), + defaultHeight + ) + } + } + } + } + + private fun initSuccessAdapter() { + adapter = InvitationAdapter(this, data).apply { + registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + // 数据插入时检查高度 + recyclerSuccessView.post { + if (recyclerSuccessView.childCount > 0) { + itemHeight = recyclerSuccessView.getChildAt(0).height + } + } + } + }) + } + recyclerSuccessView.adapter = adapter + } + + private fun initRecorddapter() { + recordadapter = InvitationRecordAdapter(this, recorddata).apply { + registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + // 数据插入时检查高度 + recyclerRecordView.post { + if (recyclerRecordView.childCount > 0) { + itemHeight = recyclerRecordView.getChildAt(0).height + } + } + } + }) + } + recyclerRecordView.adapter = recordadapter } private fun loadData() { - val mockData = listOf( - InvitationRecord("H****e", "获得了3天会员"), - InvitationRecord("U****r", "获得了7天会员"), - InvitationRecord("A****e", "获得了免广告特权"), - InvitationRecord("B****d", "获得了3天会员"), - InvitationRecord("C****o", "获得了7天会员") + data.clear() + data.addAll( + listOf( + InvitationRecord("H****e", "获得了1天会员", "已注册"), + InvitationRecord("U****r", "获得了2天会员", "已注册"), + InvitationRecord("A****e", "获得了免广告特权", "已注册"), + InvitationRecord("B****e", "获得了3天会员", "已注册"), + InvitationRecord("C****o", "获得了4天会员", "已注册") + ) ) - adapter.submitList(mockData) + adapter.notifyDataSetChanged() } - private fun setupAutoScroll() { - scrollRunnable = object : Runnable { - override fun run() { - if (currentPosition < adapter.itemCount - 1) { - currentPosition++ - smoothScrollToPosition(currentPosition) - } else { - // 滚动到底部后回到顶部 - currentPosition = 0 - recyclerView.scrollToPosition(0) + private fun loadRecordData() { + recorddata.clear() + recorddata.addAll( + listOf( + InvitationRecord("M****e", "", "未注册"), + InvitationRecord("Q****r", "", "已注册"), + InvitationRecord("W****e", "", "未注册"), + InvitationRecord("E****e", "", "未注册"), + InvitationRecord("R****o", "", "已注册") + ) + ) + recordadapter.notifyDataSetChanged() + } + + // 分享资源图片到微信 + private fun shareImageToWechat() { + try { + // 获取资源图片的URI + val imageUri = Uri.parse("android.resource://${packageName}/${R.drawable.location}") + + val intent = Intent().apply { + action = Intent.ACTION_SEND + type = "image/*" + putExtra(Intent.EXTRA_STREAM, imageUri) + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + setPackage("com.tencent.mm") // 指定微信包名 + } + + // 创建选择器,即使微信不可用也能选择其他应用 + val chooserIntent = Intent.createChooser(intent, "分享邀请图片") + + // 检查是否有应用能处理这个Intent + if (intent.resolveActivity(packageManager) != null) { + startActivity(chooserIntent) + } else { + Toast.makeText(this, "未找到可分享的应用", Toast.LENGTH_SHORT).show() + } + } catch (e: Exception) { + Toast.makeText(this, "分享失败: ${e.message}", Toast.LENGTH_SHORT).show() + } + } + + private fun formatInvitationCode(code: String): String { + return if (code.length > 2) { + code.chunked(2).joinToString(" ") + } else { + code + } + } + + private val scrollRunnable = object : Runnable { + override fun run() { + if (data.isEmpty() || itemHeight <= 0) { + handler.postDelayed(this, scrollInterval) + return + } + + // 使用取模运算确保位置在有效范围内 + currentScrollPosition++ + currentRecordScrollPosition++ + + // 创建自定义平滑滚动器 + val smoothScroller = object : LinearSmoothScroller(this@InvitationActivity) { + override fun getVerticalSnapPreference(): Int = SNAP_TO_START + + override fun calculateDyToMakeVisible(view: View, snapPreference: Int): Int { + // 计算需要滚动的距离,确保完整显示下一个item + val top = view.top + val height = view.height + return when { + snapPreference == SNAP_TO_START -> -top + snapPreference == SNAP_TO_END -> -(top - (recyclerSuccessView.height - height)) + else -> -(top - (recyclerSuccessView.height / 2 - height / 2)) + } } - handler.postDelayed(this, 2000) + + override fun calculateTimeForScrolling(dx: Int): Int { + // 根据滚动距离动态计算时间,保持匀速 + return maxOf((500f * abs(dx) / itemHeight).toInt(), 200) + } + }.apply { + targetPosition = currentScrollPosition } + + val smoothRecordScroller = object : LinearSmoothScroller(this@InvitationActivity) { + override fun getVerticalSnapPreference(): Int = SNAP_TO_START + + override fun calculateDyToMakeVisible(view: View, snapPreference: Int): Int { + // 计算需要滚动的距离,确保完整显示下一个item + val top = view.top + val height = view.height + return when { + snapPreference == SNAP_TO_START -> -top + snapPreference == SNAP_TO_END -> -(top - (recyclerRecordView.height - height)) + else -> -(top - (recyclerRecordView.height / 2 - height / 2)) + } + } + + override fun calculateTimeForScrolling(dx: Int): Int { + // 根据滚动距离动态计算时间,保持匀速 + return maxOf((500f * abs(dx) / itemHeight).toInt(), 200) + } + }.apply { + targetPosition = currentRecordScrollPosition + } + + // 启动平滑滚动 + recyclerSuccessView.layoutManager?.startSmoothScroll(smoothScroller) + + recyclerRecordView.layoutManager?.startSmoothScroll(smoothRecordScroller) + + // 更智能的边界检测 + (recyclerSuccessView.layoutManager as? LinearLayoutManager)?.let { lm -> + val lastVisible = lm.findLastVisibleItemPosition() + val totalItems = adapter.itemCount + + // 当接近"虚拟列表"末尾时,跳转到中间位置 + if (lastVisible >= totalItems - 3) { + val jumpPosition = (totalItems / 2) * (Int.MAX_VALUE / totalItems) + currentScrollPosition = jumpPosition + recyclerSuccessView.scrollToPosition(jumpPosition) + } + } + + (recyclerRecordView.layoutManager as? LinearLayoutManager)?.let { lm -> + val lastVisible = lm.findLastVisibleItemPosition() + val totalItems = recordadapter.itemCount + + // 当接近"虚拟列表"末尾时,跳转到中间位置 + if (lastVisible >= totalItems - 3) { + val jumpPosition = (totalItems / 2) * (Int.MAX_VALUE / totalItems) + currentRecordScrollPosition = jumpPosition + recyclerRecordView.scrollToPosition(jumpPosition) + } + } + + handler.postDelayed(this, scrollInterval) } - startAutoScroll() } - private fun smoothScrollToPosition(position: Int) { - val layoutManager = recyclerView.layoutManager as LinearLayoutManager - val smoothScroller = object : LinearSmoothScroller(this) { - override fun getVerticalSnapPreference(): Int = SNAP_TO_START + private fun pxToDp(px: Int, context: Context): Int { + return (px / (context.resources.displayMetrics.density)).toInt() + } - override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float { - return 100f / displayMetrics.densityDpi // 控制滚动速度 - } - } - smoothScroller.targetPosition = position - layoutManager.startSmoothScroll(smoothScroller) + private fun dpToPx(dp: Float): Int { + return (dp * resources.displayMetrics.density).toInt() } private fun startAutoScroll() { - handler.postDelayed(scrollRunnable, 2000) - } - - private fun stopAutoScroll() { - handler.removeCallbacks(scrollRunnable) + handler.removeCallbacks(scrollRunnable) // 先移除之前的回调 + handler.postDelayed(scrollRunnable, scrollInterval) } override fun onPause() { + handler.removeCallbacks(scrollRunnable) super.onPause() - stopAutoScroll() } override fun onResume() { -- Gitblit v1.9.3