Long Long Ago,微信8.0更新了一个“炸屎”的新功能,还蛮有新意。
这两天和朋友聊天触发了这个功能,当时心想,诶!这文章素材不就来了吗?!趁着周末时间,赶紧来实现一下。
我们先来看看整体实现的效果:
本篇文章无技术含量,纯属娱乐,纯属娱乐
思路
经过不断试验,反复查看了微信炸的效果,基本上可以将其拆解成几个小部分。
- 选择炸弹表情,发送后炸弹会以抛物线的形式向对方发送,同时会进行翻转;
- 在碰到表情的时候,出现爆炸动画;
- 在动画爆炸的同时,出现大概7个表情,随机朝向,做放大效果,最后下移并渐渐消失。
效果已经拆解了,技术方面具体实现思路如下:
- IM聊天界面,主要以Recyclerview分为发送方和接收方两种type;
- 以贝塞尔曲线实现炸弹抛物线的效果,这里用二阶贝塞尔曲线即可;
- 炸弹抛过去的同时,使用动画进行旋转;
- Lottie实现爆炸效果;
- 缩放、平移、渐变动画实现7个的炸出效果。
具体实现
IM聊天界面
Im聊天界面很简单,一个Recyclerview,分为左右两个type,根据type值,分别加载左侧和右侧的布局。
class ImAdapter : BaseDelegateMultiAdapter(){
companion object {
private const val TAG = "ImMsgAdapter"
private const val TYPE_LEFT = 0
private const val TYPE_RIGHT = 1
}
init {
setMultiTypeDelegate(object : BaseMultiTypeDelegate() {
override fun getItemType(data: List, position: Int): Int {
if (data[position]?.type == 0) {
return TYPE_LEFT
}
return TYPE_RIGHT
}
})
Objects.requireNonNull(getMultiTypeDelegate())!!
.addItemType(TYPE_LEFT, R.layout.item_im_left)
.addItemType(TYPE_RIGHT, R.layout.item_im_right)
}
override fun convert(holder: BaseViewHolder, item: ImMsg?) {
if (holder.itemViewType == TYPE_LEFT) {
holder.setText(R.id.tv_chat_left_msg,item?.msg)
} else {
holder.setText(R.id.tv_chat_right_msg,item?.msg)
}
}
}
这里创建一个实体类ImMsg,用于模拟聊天数据。
data class ImMsg(val type:Int,val msg:String)
//初始化聊天信息,模拟数据
val msgList = mutableListOf()
msgList.add(ImMsg(0, "Hello!"))
msgList.add(ImMsg(0, "我们来测试一下"))
msgList.add(ImMsg(0, "微信爆炸效果"))
msgList.add(ImMsg(1, "欧了!"))
msgList.add(ImMsg(0, "\uD83D\uDE14"))
msgList.add(ImMsg(0, "\uD83D\uDCA9"))
msgList.add(ImMsg(0, "\uD83D\uDCA9"))
msgList.add(ImMsg(0, "\uD83D\uDCA9"))
val adapter = ImAdapter()
imRv.adapter = adapter
adapter.addData(msgList)
当然,聊天界面还需要加上输入框,以及表情栏,这里就不一一展示。
发送炸弹
聊天界面搭建完成,接下来就开始进入正题,从扔炸弹开始。
上面已经分析了,扔炸弹的过程其实是一个类似抛物线的过程,从右侧聊天发送出去开始到碰到左侧结束。
看到扔炸弹的过程,第一反应就是用贝塞尔曲线来解决炸弹路线的问题,炸弹的行进路线比较简单,这里用二阶贝塞尔曲线就能实现。
我们都知道,要实现二阶贝塞尔曲线,至少得知道两个点的数据,一个是控制点,一个是终点的坐标。
从上图就可以基本知道,控制点在开始点和终点中间靠上的位置,这里以屏幕宽和高来做计算,模拟出控制点。
val resources: Resources = this.resources
val dm: DisplayMetrics = resources.displayMetrics
val scWidth = dm.widthPixels
val scHeight = dm.heightPixels
val controlX : Int = scWidth/2 - 400//控制点x
val controlY : Int = scHeight/2 - 400//控制点y
终点同样的道理,以起点为参考值,计算出坐标
val lastX = ivBomb.x-200
val lastY = ivBomb.y-800
有个控制点和终点的坐标,那就开始绘制二阶贝塞尔曲线,实现贝塞尔曲线,Android中已经有现成的API,Path中有个quadTo方法,只要传入控制点和终点的坐标,即生成贝塞尔曲线路径。
path.quadTo(
-controlX,
-controlY,
-lastX,
-lastY
)
有了path,接下来就是将炸弹按照贝塞尔曲线动起来,这里就需要用到属性动画结合PathMeasure,让炸弹按照路径移动。
pathMeasure = PathMeasure(path, false)
valueAnimator = ValueAnimator.ofFloat(0f, pathMeasure.length)
valueAnimator.duration = MILLS + 100
valueAnimator.interpolator = LinearInterpolator()
valueAnimator.addUpdateListener { animation ->
val animatedValue = animation.animatedValue as Float
pathMeasure.getPosTan(animatedValue, mCurrentPosition, null)
ivBomb.translationX = mCurrentPosition[0]
ivBomb.translationY = mCurrentPosition[1]
ivBomb.rotation = animatedValue
}
微信的炸弹在抛物线的同时,是有个不断翻转的效果。
那我们实现就只需要监听属性动画的更新listener,在监听移动的过程中,设置rotation,使炸弹不断进行翻转。
我们来看效果:
爆炸效果
接下来该说的就是炸弹的爆炸效果,这里给出的方案是使用Lottie库来加载。微信的爆炸效果还是蛮逼真,找了好久,都没找到相应的素材,这里属实无法模拟。
于是就从https://lottiefiles.com/ 资源库中找到了一个类似爆炸效果的json文件,结合Lottie实现爆炸的效果。
Lottie的使用也很简单,添加依赖
implementation 'com.airbnb.android:lottie:3.4.0'
在layout中添加LottieAnimationView
在炸弹执行抛物线结束时,开始播放
mLottieAnimation?.playAnimation()
来看下炸弹结合爆炸的整体效果
炸开效果
当炸弹爆炸后,有一个被炸开的效果,如下图。
从微信效果上看,存在7个的表情,并且每个表情随机倾斜角度,从爆炸动画那里为起点,由小到大弹出,形成由远及近的效果。
实现的方案是自定义View绘制出7个相同的表情图,利用Math.random随机旋转角度,且7个表情在可控范围内随机摆放。
//爆炸刚开始时随机分别7个图,且旋转角度随机
for (i in 0..6) {
dst?.let {
it.left = i * 50 + 150
it.top = mHeight + (Math.random() * 400).toInt() - mHeight / 4
it.right = it.left + 100
it.bottom = it.top + 100
}
val matrix = Matrix()
matrix.postScale(i.toFloat() + 4, i.toFloat() + 4)
matrix.postTranslate((-bitmap.width / 2).toFloat(), (-bitmap.height / 2).toFloat())
matrix.postRotate((Math.random() * 70).toFloat())
matrix.postTranslate(dst!!.top.toFloat(), dst!!.left.toFloat())
val bitmap1 =
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
canvas.drawBitmap(bitmap1, null, dst!!, paint)
matrix.reset()
}
至于由远及近的效果,就需要对自定义的粑粑View进行缩放动画。
val scaleXAni = ObjectAnimator.ofFloat(baView, "scaleX", 0f, 1.8f)
val scaleYAni = ObjectAnimator.ofFloat(baView, "scaleY", 0f, 1.5f)
另外,在炸开的最后,会向下移动并且同时渐变透明,所以在缩放动画后开始平移动画
translation和渐变动画alpha。
val tranY1Ani = ObjectAnimator.ofFloat(baView, "translationY", 0f, -300f)
val tranYAni = ObjectAnimator.ofFloat(baView, "translationY", -300f, 300f)
val alphaAni = ObjectAnimator.ofFloat(baView, "alpha", 1f, 0f)
最后使用AnimatorSet将动画集合,开始播放。
val animatorSet = AnimatorSet()
animatorSet.play(scaleXAni).with(scaleYAni).with(tranY1Ani).before(tranYAni)
.before(alphaAni)
看看效果。
基本上每个拆解的部分都已经实现,现在开始将IM界面、炸弹抛物线、爆炸效果以及最后的炸开效果合在一起。
最后的最后,实现的效果如下:
在炸弹爆炸的瞬间,用户头像和表情都会有被震到抖动的效果,这里就没实现了,有兴趣的小伙伴可以试试。
好了,本篇文章就到这了。觉得还凑合的,可以给个三连哟!
推荐阅读:
玩会儿Compose,原神主题列表
Compose版来啦!仿自如裸眼3D效果