標題可能不太直觀,想了半天也沒想到好的,那么就先貼一張具體應用場景的效果圖:

就是這一塊:

相信大家在做商城類應用時,經常會有相似需求!
本專案實作的效果如下圖:

最開始通過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://www.uj5u.com/yidong/254430.html
標籤:其他
上一篇:ListView 和 RecyclerView 的簡單使用
下一篇:Android 星期選擇器
