抓娃娃
目录
[1. 首先准备几张钩子的相关图片](#1. 首先准备几张钩子的相关图片)
[2.自定义View 的布局文件 my_view_catch.xml](#2.自定义View 的布局文件 my_view_catch.xml)
1. 首先准备几张钩子的相关图片
从前往后分别命名为:ic_tongs_top , ic_tongs_center , ic_tongs_open , ic_tongs_close ,ic_catch_coin
2.自定义View 的布局文件 my_view_catch.xml
XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="500dp"
tools:background="@color/white">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/bubbleView0"
android:layout_width="50dp"
android:layout_height="50dp"
android:tag="0"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bubbleView1"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_catch_coin"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/bubbleView1"
android:layout_width="50dp"
android:layout_height="50dp"
android:tag="1"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bubbleView2"
app:layout_constraintStart_toEndOf="@+id/bubbleView0"
app:srcCompat="@drawable/ic_catch_coin"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/bubbleView2"
android:layout_width="50dp"
android:layout_height="50dp"
android:tag="2"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/bubbleView1"
app:srcCompat="@drawable/ic_catch_coin"/>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/tongsView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="92dp"
android:layout_height="9dp"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_tongs_top" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/tongsCenterView"
android:layout_width="8dp"
android:layout_height="3dp"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_tongs_center" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/tongsBottomView"
android:layout_width="92dp"
android:layout_height="49dp"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"
android:scaleType="fitStart"
app:srcCompat="@drawable/ic_tongs_close" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.constraintlayout.widget.ConstraintLayout>
3.自定义View的代码
Kotlin
package com.example.test.ui.widget
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.Animation
import android.widget.FrameLayout
import android.widget.Toast
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import androidx.core.view.doOnNextLayout
import androidx.core.view.updateLayoutParams
class MyCatchView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private val binding = MyViewCatchBinding.inflate(LayoutInflater.from(context), this, true)
private var horizontalAnimator: ValueAnimator? = null //钩爪左右移动的动画
private var horizontalMoveAnimator: ValueAnimator? = null //钩爪开始抓取时移动到指定位置的动画
private var moveDownAnimator: ValueAnimator? = null //钩爪抓取时向下移动的动画
private var moveUpAnimator: ValueAnimator? = null //钩爪抓取时向上返回的动画
private var captureAnimatorSet: AnimatorSet? = null //抓取动画集合
private var bubbleViewList: MutableList<AppCompatImageView> = mutableListOf()
private lateinit var targetView: AppCompatImageView //要抓取的目标视图
private var viewWidth = 0f
private var viewHeight = 0f
private var onCapture = false
init {
initBubbleView()
}
private fun startHorizontalAnimator() {
binding.tongsView.post {
//在tongsView绘制完成之后才能获取宽高
if (horizontalAnimator == null) {
horizontalAnimator =
ValueAnimator.ofFloat(0f, viewWidth - binding.tongsView.width)
.apply {
duration = 3000
repeatCount = ValueAnimator.INFINITE
repeatMode = ValueAnimator.REVERSE
addUpdateListener {
binding.tongsView.translationX = it.animatedValue as Float
}
}
}
LogUtil.d("CatchView", "tongsX: ${viewWidth - binding.tongsView.width}")
horizontalAnimator?.cancel()
horizontalAnimator?.start()
}
}
//开始抓取
fun startCapture() {
if (bubbleViewList.isEmpty()) {
initBubbleView()
Toast.makeText(context, "奖池重置!", Toast.LENGTH_SHORT).show()
return
}
if (onCapture) return
onCapture = true
Toast.makeText(context, "$onCapture", Toast.LENGTH_SHORT).show()
horizontalAnimator?.cancel()
captureAnimatorSet?.cancel()
targetView = bubbleViewList.random()
val startY = binding.tongsBottomView.y
val startX = binding.tongsView.x
val targetX = targetView.x + (targetView.width - binding.tongsView.width) / 2
val targetY = targetView.y + targetView.height / 2f - binding.tongsBottomView.height
captureAnimatorSet = AnimatorSet().apply {
// 设置动画序列
playSequentially(
getHorizontalMoveAnimator(startX, targetX),
getMoveDownAnimator(startY, targetY),
getMoveUpAnimator(targetY, startY),
getHorizontalMoveAnimator(binding.tongsView.x, 0f, 1000)
)
// 动画结束回调
doOnEnd {
onCapture = false
startHorizontalAnimator()
Toast.makeText(context, "恭喜获得tag:${targetView.tag}", Toast.LENGTH_SHORT).show()
}
}
captureAnimatorSet?.start()
}
//将钩爪移动到指定水平位置
private fun getHorizontalMoveAnimator(
startX: Float,
targetX: Float,
delay: Long = 0,
endCallBack: () -> Unit = {}
): ValueAnimator {
horizontalMoveAnimator = ValueAnimator.ofFloat(startX, targetX).apply {
duration = 1000
startDelay = delay
interpolator = AccelerateDecelerateInterpolator()
addUpdateListener {
binding.tongsView.translationX = it.animatedValue as Float
}
doOnEnd { endCallBack.invoke() }
}
return horizontalMoveAnimator!!
}
//钩爪向下移动
private fun getMoveDownAnimator(startY: Float, endY: Float): ValueAnimator {
val startHeight = binding.tongsCenterView.height
val openTongsY = startY + (endY - startY) / 4 * 3
var setOpenSrc = false
moveDownAnimator = ValueAnimator.ofFloat(startY, endY).apply {
duration = 1000
interpolator = AccelerateDecelerateInterpolator()
addUpdateListener {
//并不是钩爪向下移动,而是通过增加杆View的高度来把钩爪挤下去
val animatedValue = it.animatedValue as Float
binding.tongsCenterView.apply {
val params = layoutParams
params.height = startHeight + (animatedValue - startY).toInt()
layoutParams = params
}
if (!setOpenSrc && animatedValue >= openTongsY) {
binding.tongsBottomView.setImageResource(R.drawable.ic_tongs_open)
!setOpenSrc
}
}
}
return moveDownAnimator!!
}
//钩爪向上移动
private fun getMoveUpAnimator(startY: Float, endY: Float): ValueAnimator {
var startHeight = 0
val targetViewStartY = targetView.y
moveUpAnimator = ValueAnimator.ofFloat(startY, endY).apply {
duration = 1000
startDelay = 150
interpolator = AccelerateDecelerateInterpolator()
doOnStart {
//必须在动画开始的时候再获取
startHeight = binding.tongsCenterView.height
}
addUpdateListener {
val animatedValue = animatedValue as Float
val s = (animatedValue - startY).toInt() //滑动距离
binding.tongsCenterView.apply {
val params = layoutParams
params.height = startHeight + s
layoutParams = params
}
targetView.y = targetViewStartY + s
}
doOnEnd {
binding.tongsBottomView.setImageResource(R.drawable.ic_tongs_close)
targetView.visibility = View.INVISIBLE
bubbleViewList.remove(targetView)
}
}
return moveUpAnimator!!
}
private fun initBubbleView() {
bubbleViewList.clear()
bubbleViewList.apply {
add(binding.bubbleView0)
add(binding.bubbleView1)
add(binding.bubbleView2)
}
bubbleViewList.forEach {
it.visibility = View.VISIBLE
it.y = viewHeight - it.height - dp2px(20f)
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
viewWidth = w.toFloat()
viewHeight = h.toFloat()
startHorizontalAnimator()
}
override fun onDetachedFromWindow() {
horizontalAnimator?.cancel()
horizontalAnimator = null
horizontalMoveAnimator?.cancel()
horizontalMoveAnimator = null
moveDownAnimator?.cancel()
moveDownAnimator = null
moveUpAnimator?.cancel()
moveUpAnimator = null
captureAnimatorSet?.cancel()
captureAnimatorSet = null
super.onDetachedFromWindow()
}
}
4.具体使用
XML
<com.example.test.ui.widget.MyCatchView
android:id="@+id/catchView"
android:layout_width="match_parent"
android:layout_height="500dp"/>
Kotlin
binding.catchView.setOnClickListener {
binding.catchView.startCapture()
}