先上效果圖
需求梳理
抖音APP中呐馆,視頻的點(diǎn)贊和直播中點(diǎn)贊效果是不同的肥缔,先找尋兩者的共同點(diǎn)提取接口:
類(lèi)型 | 圖片 | 動(dòng)畫(huà) | 初始旋轉(zhuǎn)角度 | Y軸偏移量 |
---|---|---|---|---|
視頻點(diǎn)贊 | 單張紅心 | 組合動(dòng)畫(huà) | 隨機(jī)旋轉(zhuǎn)角度 | -50 |
直播點(diǎn)贊 | 多張圖片隨機(jī) | 組合動(dòng)畫(huà) | 無(wú) | -20 |
Y軸偏移量是多次調(diào)試的,看各位同學(xué)需求汹来,都可以調(diào)整续膳。
interface ILikeFollowData {
/**
* 獲取圖標(biāo)列表
*/
fun getIconList(): List<Any>
/**
* 獲取圖標(biāo)大小
*/
fun getIconSize(): Int
/**
* 獲取旋轉(zhuǎn)角度范圍
*/
fun getRotationRange(): IntRange
/**
* 獲取Y軸偏移量
*/
fun getYOffset(): Int
/**
* 獲取動(dòng)畫(huà)集合
*/
fun getAnimatorSet(view: View): AnimatorSet
}
實(shí)現(xiàn)
視頻點(diǎn)贊數(shù)據(jù)類(lèi)
class VideoLikeFollowData : ILikeFollowData {
private val iconList = arrayListOf<Any>(R.drawable.img_like_follow)
override fun getIconList() = iconList
override fun getIconSize() = 240
override fun getRotationRange() = -30..30
override fun getYOffset() = -50
override fun getAnimatorSet(view: View): AnimatorSet {
val set = AnimatorSet()
//放大
set.playSequentially(
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.4f).apply {
duration = 50
},
ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.4f).apply {
duration = 50
},
)
},
//縮小
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofFloat(view, "scaleX", 1.4f, 1f).apply {
duration = 200
},
ObjectAnimator.ofFloat(view, "scaleY", 1.4f, 1f).apply {
duration = 200
},
)
},
ValueAnimator.ofInt(0, 100).apply {
duration = 200
},
//縮放/透明并位移
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.8f).apply {
duration = 500
},
ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.8f).apply {
duration = 500
},
ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).apply {
duration = 500
},
ObjectAnimator.ofFloat(
view,
"translationY",
view.y,
view.y - 300f
).apply {
duration = 500
},
)
})
return set
}
}
直播點(diǎn)贊數(shù)據(jù)類(lèi)
class LiveLikeFollowData : ILikeFollowData {
private val iconList = arrayListOf<Any>(
R.drawable.img_like_follow_1,
R.drawable.img_like_follow_2,
R.drawable.img_like_follow_3,
R.drawable.img_like_follow_4,
R.drawable.img_like_follow_5,
R.drawable.img_like_follow_6,
R.drawable.img_like_follow_7,
)
override fun getIconList() = iconList
override fun getIconSize() = 100
override fun getRotationRange() = 0..0
override fun getYOffset() = -20
override fun getAnimatorSet(view: View): AnimatorSet {
val set = AnimatorSet()
//放大
set.playSequentially(
AnimatorSet().apply {
// 同時(shí)播放
playTogether(
ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.1f).apply {
duration = 50
},
ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.1f).apply {
duration = 50
},
ObjectAnimator.ofFloat(view, "rotation", -20f, -6f).apply {
duration = 50
}
)
},
//縮小
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofFloat(view, "scaleX", 1.1f, 1f).apply {
duration = 200
},
ObjectAnimator.ofFloat(view, "scaleY", 1.1f, 1f).apply {
duration = 200
},
ObjectAnimator.ofFloat(view, "rotation", -6f, 4f, 0f).apply {
duration = 200
}
)
},
ValueAnimator.ofInt(0, 100).apply {
duration = 200
},
//縮放/透明并位移
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.8f).apply {
duration = 500
},
ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.8f).apply {
duration = 500
},
ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).apply {
duration = 500
}
)
})
return set
}
}
上述代碼中的圖片,各位同學(xué)可以讓UI自行設(shè)計(jì)切圖收班,如果需要坟岔,可以在末尾代碼鏈接處下載獲取。
功能model
object LikeFollowModel {
//短視頻展示數(shù)據(jù)
val videoLikeFollowData: ILikeFollowData by lazy {
VideoLikeFollowData()
}
//直播展示數(shù)據(jù)
val liveLikeFollowData: ILikeFollowData by lazy {
LiveLikeFollowData()
}
/**
* 展示點(diǎn)贊效果
* @param x: 點(diǎn)擊的x坐標(biāo)
* @param y: 點(diǎn)擊的y坐標(biāo)
* @param viewGroup: 父布局
* @param data: 展示數(shù)據(jù)
*/
fun show(x: Int, y: Int, viewGroup: ViewGroup, data: ILikeFollowData = videoLikeFollowData) {
//添加愛(ài)心
val likeView = AppCompatImageView(viewGroup.context)
//加載圖片,封裝的Glide方法,同學(xué)可自行處理
loadImage(likeView, data.getIconList().random())
val size = data.getIconSize()
val layoutParams = ViewGroup.LayoutParams(size, size)
likeView.layoutParams = layoutParams
//設(shè)置愛(ài)心位置
likeView.x = (x - (size / 2)).toFloat()
likeView.y = (y - size + data.getYOffset()).toFloat()
//設(shè)置隨機(jī)旋轉(zhuǎn)角度
likeView.rotation = (data.getRotationRange().random()).toFloat()
viewGroup.addView(likeView)
//獲取動(dòng)畫(huà)集合
val animatorSet = data.getAnimatorSet(likeView)
//添加動(dòng)畫(huà)結(jié)束監(jiān)聽(tīng)
animatorSet.addListener(onEnd = {
tryCatch({
//清理動(dòng)畫(huà)
likeView.clearAnimation()
//隱藏圖片
likeView.visibility = View.GONE
//移除圖片,延遲500毫秒移除,防止動(dòng)畫(huà)還沒(méi)結(jié)束就移除
HandlerUtils.postRunnable({
viewGroup.removeView(likeView)
}, 500)
})
})
animatorSet.start()
}
}
以上四個(gè)類(lèi)就是所有的功能代碼摔桦。
實(shí)現(xiàn)思路還是蠻簡(jiǎn)單的:
獲取點(diǎn)擊事件坐標(biāo)
避免同學(xué)獲取點(diǎn)擊坐標(biāo)有疑問(wèn)社付,這里再補(bǔ)充上相關(guān)代碼:
/**
* 設(shè)置點(diǎn)擊監(jiān)聽(tīng)
* 返回坐標(biāo)
*/
@SuppressLint("ClickableViewAccessibility")
private fun setViewOnClickListener(view: View, clickCallBack: (x: Int, y: Int) -> Unit) {
var x = 0f
var y = 0f
view.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
x = event.x
y = event.y
}
MotionEvent.ACTION_UP -> {
if (x == event.x && y == event.y) {
clickCallBack(event.x.toInt(), event.y.toInt())
}
}
}
true
}
}