拼团列表item自动滚动+倒计时实现

标题可能不太直观,想了半天也没想到好的,那么就先贴一张具体应用场景的效果图:
在这里插入图片描述
就是这一块:

在这里插入图片描述
相信大家在做商城类应用时,经常会有相似需求!

本项目实现的效果如下图:
在这里插入图片描述
最开始通过RecyclerView实现,也可以,但是在进行嵌套特别是ScrollView等滑动控件时,就会出现触摸bug,即便你将recyclerview禁止滑动也不行,虽说这个bug不算太影响体验,但是对于追求完美的我,不能忍,就再寻找了另一种实现方式:动态add/removeView+属性动画!

原理其实很简单:

首先,分析效果图,ui上显示itemview的个数为始终2,每次向上滑动1,很容易就能想到,在父容器中始终有3个itemview,父容器高度为2个itemview的高度,然后第一个itemview在规定时间内,向上移动一个itemview的高度即可(通过不断设置marginTop),然后父容器remove第一个view,再add下一个view,循环往复!

在这里插入图片描述
至于向上滚动时,第一个view的渐隐效果,只需要在属性动画的UpdateListener中,除了不断设置marginTop外,另外就是实时更新view的透明度即可!

animator.addUpdateListener {
    
     animation: ValueAnimator ->
    val value = animation.animatedValue as Int
    var layoutParams: LinearLayout.LayoutParams =v.rootView.getChildAt(0).layoutParams as LinearLayout.LayoutParams     
    layoutParams.topMargin = -value
    v.rootView.getChildAt(0).layoutParams = layoutParams
    v.rootView.getChildAt(0).alpha = (0.6f - value / height.toFloat())
}

以下时PinTuanView主要代码:

PinTuanView:

package com.byl.pin.view

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.core.view.children
import com.byl.pin.R
import com.byl.pin.bean.ItemBean
import com.byl.pin.databinding.ViewItemBinding
import com.byl.pin.databinding.ViewPinBinding
import com.byl.pin.util.Utils.dp2Px


class PinTuanView : FrameLayout {
    
    

    companion object {
    
    
        const val SHOW_COUNT = 2
        const val TIME_INTERVAL: Long = 3000
    }

    val v = ViewPinBinding.inflate(LayoutInflater.from(context), this, true)
    var listData: ArrayList<ItemBean>? = null
    var scrollTask: ScrollTask? = null
    var preShowIndex = 0

    constructor(context: Context) : super(context) {
    
    
        initAttributeSet(null)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
    
    
        initAttributeSet(attrs)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
    
    
        initAttributeSet(attrs)
    }

    private fun initAttributeSet(attrs: AttributeSet?) {
    
    

    }

    fun setData() {
    
    
        stop()
        v.rootView.removeAllViews()
        listData = ArrayList()
        for (i in 0..3) {
    
    
            val itemBean = ItemBean()
            itemBean.id = i
            itemBean.name = "哈哈哈$i"
            itemBean.endTime = System.currentTimeMillis() + 2 * 24 * 60 * 60 * 1000
            listData!!.add(itemBean)
        }

        //如果list条数为不超过2个,则不执行滚动动画
        if (listData!!.size <= SHOW_COUNT) {
    
    
            listData!!.forEach {
    
    
                v.rootView.addView(getItemView(it))
            }
            return
        }

        //超过两个时,先向容器中添加三个Item
        for (i in 0..SHOW_COUNT) {
    
    
            v.rootView.addView(getItemView(listData!![i]))
        }

        preShowIndex = SHOW_COUNT + 1//即将显示的数据的index
    }

    fun getItemView(itemBean: ItemBean): View {
    
    
        val itemView = ViewItemBinding.inflate(LayoutInflater.from(context), null, false)
        itemView.tvName.text = itemBean.name
        itemView.mCountDownView.setRemainTime(itemBean.endTime)
        itemView.mCountDownView.start()
        itemView.mCountDownView.addTimerEndListener {
    
    
            //TODO 倒计时结束
        }
        itemView.btnAddTeam.setOnClickListener {
    
    
            clickTeam(itemBean)
        }
        return itemView.root
    }

    fun start() {
    
    
        if (listData!!.size <= SHOW_COUNT) return
        if (scrollTask != null) {
    
    
            removeCallbacks(scrollTask)
            scrollTask = null
        }
        scrollTask = ScrollTask()
        postDelayed(scrollTask, TIME_INTERVAL)
    }

    fun stop() {
    
    
        if (scrollTask == null) return
        removeCallbacks(scrollTask)
        scrollTask = null
        v.rootView.children.forEach {
    
    
            it.findViewById<CountTimerView>(R.id.mCountDownView).stop()
        }
    }

    inner class ScrollTask : Runnable {
    
    

        override fun run() {
    
    
            val height = dp2Px(context, 80f)
            val animator = ValueAnimator.ofInt(height)
            animator.duration = 500
            animator.start()
            animator.addUpdateListener {
    
     animation: ValueAnimator ->
                val value = animation.animatedValue as Int
                var layoutParams: LinearLayout.LayoutParams =
                    v.rootView.getChildAt(0).layoutParams as LinearLayout.LayoutParams
                layoutParams.topMargin = -value
                v.rootView.getChildAt(0).layoutParams = layoutParams
                v.rootView.getChildAt(0).alpha = (0.6f - value / height.toFloat())
            }
            animator.addListener(object : AnimatorListenerAdapter() {
    
    
                override fun onAnimationEnd(animation: Animator?) {
    
    
                    super.onAnimationEnd(animation)
                    v.rootView.getChildAt(0).findViewById<CountTimerView>(
                        R.id.mCountDownView
                    ).stop()
                    v.rootView.removeViewAt(0)
                    if (listData!!.size <= preShowIndex) {
    
    
                        preShowIndex = 0
                    }
                    v.rootView.addView(getItemView(listData!![preShowIndex]))
                    preShowIndex++
                    postDelayed(scrollTask, TIME_INTERVAL)
                    //当转完一圈后,刷新数据(这个地方为模拟刷新,可以在实际应用中重新请求接口,达到无感刷新)
                    if (preShowIndex == SHOW_COUNT + 1) {
    
    
                        handler.postDelayed({
    
    
                            setData()
                            start()
                        }, 1000)
                    }
                }
            })
        }

    }


    private lateinit var clickTeam: (itemBean: ItemBean) -> Unit

    fun addClickTeamListener(clickTeam: (itemBean: ItemBean) -> Unit) {
    
    
        this.clickTeam = clickTeam
    }

}

使用方法非常简单:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@color/white">

    <com.byl.pin.view.PinTuanView
        android:id="@+id/mPinTuanView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>
  mPinTuanView.setData()
  mPinTuanView.start()
  override fun onDestroy() {
    
    
       super.onDestroy()
       mPinTuanView.stop()
   }

另外,其中还有自定义的CountTimerView,具体可参考项目源码:

https://download.csdn.net/download/baiyuliang2013/14949415

扫描二维码关注公众号,回复: 12412619 查看本文章

github:https://github.com/baiyuliang/PinTuanView

猜你喜欢

转载自blog.csdn.net/baiyuliang2013/article/details/113335810