
購(gòu)物車放大動(dòng)畫

動(dòng)畫執(zhí)行完回調(diào)
interface RefreshDataListener {
fun onDataRefresh(tag: Int, key: String, value: String)
}
動(dòng)畫代碼
fun addCart(startView: ImageView, endView: View, xmlLayout: RelativeLayout, listener:RefreshDataListener) {
//? 一、創(chuàng)造出執(zhí)行動(dòng)畫的主題---imageview
? ? //? 代碼new一個(gè)imageview,圖片資源是上面的imageview的圖片
? ? //? (這個(gè)圖片就是執(zhí)行動(dòng)畫的圖片,從開始位置出發(fā),經(jīng)過(guò)一個(gè)拋物線(貝塞爾曲線),移動(dòng)到購(gòu)物車?yán)?
? ? val goods = AppCompatImageView(xmlLayout.context)
goods.setImageDrawable(startView.drawable)
val params = RelativeLayout.LayoutParams(80, 80)
xmlLayout.addView(goods, params)
//? 二、計(jì)算動(dòng)畫開始/結(jié)束點(diǎn)的坐標(biāo)的準(zhǔn)備工作
? ? //? 得到父布局的起始點(diǎn)坐標(biāo)(用于輔助計(jì)算動(dòng)畫開始/結(jié)束時(shí)的點(diǎn)的坐標(biāo))
? ? val parentLocation = IntArray(2)
xmlLayout.getLocationInWindow(parentLocation)
//? 得到商品圖片的坐標(biāo)(用于計(jì)算動(dòng)畫開始的坐標(biāo))
? ? val startLoc = IntArray(2)
startView.getLocationInWindow(startLoc)
//? 得到購(gòu)物車圖片的坐標(biāo)(用于計(jì)算動(dòng)畫結(jié)束后的坐標(biāo))
? ? val endLoc = IntArray(2)
endView.getLocationInWindow(endLoc)
//? 三、正式開始計(jì)算動(dòng)畫開始/結(jié)束的坐標(biāo)
? ? //? 開始掉落的商品的起始點(diǎn):商品起始點(diǎn)-父布局起始點(diǎn)+該商品圖片的一半
? ? val startX = (startLoc[0] - parentLocation[0]).toFloat()//+ startView.getWidth() / 2;
? ? val startY = (startLoc[1] - parentLocation[1]).toFloat()//+ startView.getHeight() / 2;
? ? //商品掉落后的終點(diǎn)坐標(biāo):購(gòu)物車起始點(diǎn)-父布局起始點(diǎn)+購(gòu)物車圖片的1/5
? ? val toX = (endLoc[0] - parentLocation[0] + endView.width /5).toFloat()
val toY = (endLoc[1] - parentLocation[1]).toFloat()
//? 四、計(jì)算中間動(dòng)畫的插值坐標(biāo)(貝塞爾曲線)(其實(shí)就是用貝塞爾曲線來(lái)完成起終點(diǎn)的過(guò)程)
? ? //? 開始繪制貝塞爾曲線
? ? val path = Path()
//? 移動(dòng)到起始點(diǎn)(貝塞爾曲線的起點(diǎn))
? ? path.moveTo(startX, startY)
//? 使用二次薩貝爾曲線:注意第一個(gè)起始坐標(biāo)越大,貝塞爾曲線的橫向距離就會(huì)越大,一般按照下面的式子取即可
? ? path.quadTo((startX + toX) /2, startY, toX, toY)
//mPathMeasure用來(lái)計(jì)算貝塞爾曲線的曲線長(zhǎng)度和貝塞爾曲線中間插值的坐標(biāo),
? ? //? 如果是true,path會(huì)形成一個(gè)閉環(huán)
? ? val mPathMeasure = PathMeasure(path, false)
val mCurrentPosition = FloatArray(2)
//? 屬性動(dòng)畫實(shí)現(xiàn)(從0到貝塞爾曲線的長(zhǎng)度之間進(jìn)行插值計(jì)算,獲取中間過(guò)程的距離值)
? ? val valueAnimator = ValueAnimator.ofFloat(0f, mPathMeasure.length)
valueAnimator.duration =1000
? ? //? 勻速線性插值器
? ? valueAnimator.interpolator = LinearInterpolator()
valueAnimator.addUpdateListener{ animation-> // 當(dāng)插值計(jì)算進(jìn)行時(shí),獲取中間的每個(gè)值,
? ? ? ? // 這里這個(gè)值是中間過(guò)程中的曲線長(zhǎng)度(下面根據(jù)這個(gè)值來(lái)得出中間點(diǎn)的坐標(biāo)值)
? ? ? ? val value = animation.animatedValue as Float
// boolean getPosTan(float distance, float[] pos, float[] tan) :
? ? ? ? // 傳入一個(gè)距離distance(0<=distance<=getLength()),然后會(huì)計(jì)算當(dāng)前距
? ? ? ? // 離的坐標(biāo)點(diǎn)和切線,pos會(huì)自動(dòng)填充上坐標(biāo),這個(gè)方法很重要。
? ? ? ? //mCurrentPosition此時(shí)就是中間距離點(diǎn)的坐標(biāo)值
? ? ? ? mPathMeasure.getPosTan(value, mCurrentPosition, null)
// 移動(dòng)的商品圖片(動(dòng)畫圖片)的坐標(biāo)設(shè)置為該中間點(diǎn)的坐標(biāo)
? ? ? ? goods.translationX = mCurrentPosition[0]
goods.translationY = mCurrentPosition[1]
}
? ? // 五、 開始執(zhí)行動(dòng)畫
? ? valueAnimator.start()
// 六、動(dòng)畫結(jié)束后的處理
? ? valueAnimator.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {}
//當(dāng)動(dòng)畫結(jié)束后:
? ? ? ? override fun onAnimationEnd(animation: Animator) {
// 寫個(gè)接口回調(diào)動(dòng)畫結(jié)束了
? ? ? ? ? ? listener.onDataRefresh(1,"","")
// 執(zhí)行 購(gòu)物車放大再還原動(dòng)畫
? ? ? ? ? ? endView.startAnimation(AnimationUtils.loadAnimation(xmlLayout.context, R.anim.big_small))
// 把移動(dòng)的圖片imageview從父布局里移除
? ? ? ? ? ? xmlLayout.removeView(goods)
}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}
})
}