| | |
| | | package com.example.firstapp.ui.invitation |
| | | |
| | | import android.content.Context |
| | | 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.TextView |
| | | 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 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() |
| | | } |
| | | |
| | | private fun setupRecyclerView() { |
| | | recyclerView = findViewById(R.id.invitationsuccessRecyclerView) |
| | | recyclerView.layoutManager = LinearLayoutManager(this).apply { |
| | | stackFromEnd = false // 从顶部开始布局 |
| | | } |
| | | adapter = InvitationAdapter() |
| | | recyclerView.adapter = adapter |
| | | } |
| | | loadRecordData() |
| | | |
| | | 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天会员") |
| | | ) |
| | | adapter.submitList(mockData) |
| | | } |
| | | |
| | | private fun setupAutoScroll() { |
| | | scrollRunnable = object : Runnable { |
| | | override fun run() { |
| | | if (currentPosition < adapter.itemCount - 1) { |
| | | currentPosition++ |
| | | smoothScrollToPosition(currentPosition) |
| | | } else { |
| | | // 滚动到底部后回到顶部 |
| | | currentPosition = 0 |
| | | recyclerView.scrollToPosition(0) |
| | | } |
| | | handler.postDelayed(this, 2000) |
| | | } |
| | | } |
| | | //启动轮播 |
| | | 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 initViews() { |
| | | findViewById<TextView>(R.id.tv_notic).apply { |
| | | text = Html.fromHtml(getString(R.string.invite_reward_text), Html.FROM_HTML_MODE_LEGACY) |
| | | } |
| | | recyclerSuccessView = findViewById(R.id.invitationsuccessRecyclerView) |
| | | addSuccessListener() |
| | | |
| | | override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float { |
| | | return 100f / displayMetrics.densityDpi // 控制滚动速度 |
| | | 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 |
| | | } |
| | | } |
| | | } |
| | | smoothScroller.targetPosition = position |
| | | layoutManager.startSmoothScroll(smoothScroller) |
| | | 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() { |
| | | 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.notifyDataSetChanged() |
| | | } |
| | | |
| | | 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 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)) |
| | | } |
| | | } |
| | | |
| | | 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) |
| | | } |
| | | } |
| | | |
| | | private fun pxToDp(px: Int, context: Context): Int { |
| | | return (px / (context.resources.displayMetrics.density)).toInt() |
| | | } |
| | | 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() { |