标题可能不太直观,想了半天也没想到好的,那么就先贴一张具体应用场景的效果图:
就是这一块:
相信大家在做商城类应用时,经常会有相似需求!
本项目实现的效果如下图:
最开始通过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
github:https://github.com/baiyuliang/PinTuanView
转载:https://blog.csdn.net/baiyuliang2013/article/details/113335810