点击后的缩放效果
本文通过ScaleAnimation
实现缩放效果,代码如下:
private fun playThumbUpScaleAnimator() {
// x、y轴方向都从1倍放大到2倍,以控件的中心为原点进行缩放
ScaleAnimation(1f, 2f, 1f, 2f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f).run {
// 先取消控件当前的动画效果(重复点击时)
view.clearAnimation()
// 设置动画的持续时间
duration = 300
// 开始播放动画
view.startAnimation(this)
}
}
拇指的散开效果
有5个拇指分别往不同的方向移动,本文通过动态添加View
,并对View
设置动画来实现。可以看到在移动的同时还有缩放的效果,所以需要同时播放几个动画。
本文通过ValueAnimator
和AnimatorSet
来实现该效果,代码如图:
// 此数组控制动画的效果
// 第一个参数控制X轴移动距离
// 第二个参数控制Y轴移动距离
// 第三个参数控制缩放的倍数(基于原大小)
val animatorConfig: ArrayList<ArrayList<Float>> = arrayListOf(
arrayListOf(-160f, 150f, 1f),
arrayListOf(80f, 130f, 1.1f),
arrayListOf(-120f, -170f, 1.3f),
arrayListOf(80f, -130f, 1f),
arrayListOf(-20f, -80f, 0.8f))
private fun playDiffusionAnimator() {
for (index in 0 until 5) {
binding.root.run {
if (this is ViewGroup) {
// 创建控件
val ivThumbUp = AppCompatImageView(context)
ivThumbUp.setImageResource(R.drawable.icon_thumb_up)
// 设置与原控件一样的大小
ivThumbUp.layoutParams = FrameLayout.LayoutParams(DensityUtil.dp2Px(25), DensityUtil.dp2Px(25))
// 先设置为全透明
ivThumbUp.alpha = 0f
addView(ivThumbUp)
// 设置与原控件一样的位置
ivThumbUp.x = binding.ivThumbUp.x
ivThumbUp.y = binding.ivThumbUp.y
AnimatorSet().apply {
// 设置动画集开始播放前的延迟
startDelay = 330L + index * 50L
// 设置动画监听
addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {
// 开始播放时把控件设置为不透明
ivThumbUp.alpha = 1f
}
override fun onAnimationEnd(animation: Animator) {
// 播放结束后再次设置为透明,并从根布局中移除
ivThumbUp.alpha = 0f
ivThumbUp.clearAnimation()
ivThumbUp.post { removeView(ivThumbUp) }
}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}
})
// 设置三个动画同时播放
playTogether(
// 缩放动画
ValueAnimator.ofFloat(1f, animatorConfig[index][2]).apply {
duration = 700
// 设置插值器,速度一开始快,快结束时减慢
interpolator = DecelerateInterpolator()
addUpdateListener { values ->
(values.animatedValue as Float).let { value ->
ivThumbUp.scaleX = value
ivThumbUp.scaleY = value
}
}
},
// X轴的移动动画
ValueAnimator.ofFloat(ivThumbUp.x, ivThumbUp.x + animatorConfig[index][0]).apply {
duration = 700
interpolator = DecelerateInterpolator()
addUpdateListener { values ->
ivThumbUp.x = values.animatedValue as Float
}
},
// Y轴的移动动画
ValueAnimator.ofFloat(ivThumbUp.y, ivThumbUp.y + animatorConfig[index][1]).apply {
duration = 700
interpolator = DecelerateInterpolator()
addUpdateListener { values ->
ivThumbUp.y = values.animatedValue as Float
}
})
}.start()
}
}
}
}
示例
整合之后做了个示例Demo,完整代码如下:
class AnimatorSetExampleActivity : BaseGestureDetectorActivity() {
private lateinit var binding: LayoutAnimatorsetExampleActivityBinding
private val animatorConfig: ArrayList<java.util.ArrayList<Float>> = arrayListOf(
arrayListOf(-160f, 150f, 1f),
arrayListOf(80f, 130f, 1.1f),
arrayListOf(-120f, -170f, 1.3f),
arrayListOf(80f, -130f, 1f),
arrayListOf(-20f, -80f, 0.8f))
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.layout_animatorset_example_activity)
binding.ivThumbUp.setOnClickListener {
playThumbUpScaleAnimator()
playDiffusionAnimator()
}
}
private fun playThumbUpScaleAnimator() {
// x,y轴方向都从1倍放大到2倍,以控件的中心为原点进行缩放
ScaleAnimation(1f, 2f, 1f, 2f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f).run {
// 先取消控件当前的动画效果(重复点击时)
binding.ivThumbUp.clearAnimation()
// 设置动画的持续时间
duration = 300
// 开始播放动画
binding.ivThumbUp.startAnimation(this)
}
}
private fun playDiffusionAnimator() {
for (index in 0 until 5) {
binding.root.run {
if (this is ViewGroup) {
// 创建控件
val ivThumbUp = AppCompatImageView(context)
ivThumbUp.setImageResource(R.drawable.icon_thumb_up)
// 设置与原控件一样的大小
ivThumbUp.layoutParams = FrameLayout.LayoutParams(DensityUtil.dp2Px(25), DensityUtil.dp2Px(25))
// 先设置为全透明
ivThumbUp.alpha = 0f
addView(ivThumbUp)
// 设置与原控件一样的位置
ivThumbUp.x = binding.ivThumbUp.x
ivThumbUp.y = binding.ivThumbUp.y
AnimatorSet().apply {
// 设置动画集开始播放前的延迟
startDelay = 330L + index * 50L
// 设置动画监听
addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {
// 开始播放时把控件设置为不透明
ivThumbUp.alpha = 1f
}
override fun onAnimationEnd(animation: Animator) {
// 播放结束后再次设置为透明,并从根布局中移除
ivThumbUp.alpha = 0f
ivThumbUp.clearAnimation()
ivThumbUp.post { removeView(ivThumbUp) }
}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}
})
// 设置三个动画同时播放
playTogether(
// 缩放动画
ValueAnimator.ofFloat(1f, animatorConfig[index][2]).apply {
duration = 700
// 设置插值器,速度一开始快,快结束时减缓
interpolator = DecelerateInterpolator()
addUpdateListener { values ->
(values.animatedValue as Float).let { value ->
ivThumbUp.scaleX = value
ivThumbUp.scaleY = value
}
}
},
// Y轴的移动动画
ValueAnimator.ofFloat(ivThumbUp.x, ivThumbUp.x + animatorConfig[index][0]).apply {
duration = 700
interpolator = DecelerateInterpolator()
addUpdateListener { values ->
ivThumbUp.x = values.animatedValue as Float
}
},
// X轴的移动动画
ValueAnimator.ofFloat(ivThumbUp.y, ivThumbUp.y + animatorConfig[index][1]).apply {
duration = 700
interpolator = DecelerateInterpolator()
addUpdateListener { values ->
ivThumbUp.y = values.animatedValue as Float
}
})
}.start()
}
}
}
}
}
效果如图:
MotionLayout
MotionLayout
是ConstraintLayout
的子类,包含在ConstraintLayout
库中,在ConstraintLayout
的基础上,增加了管理控件动画的功能。
添加库
如果之前没有使用ConstraintLayout
,那么需要在app module下的build.gradle中添加代码,如下:
dependencies {
// 项目中使用AndroidX
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1'
// 项目中未使用AndroidX
implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta1'
}
点赞效果的实现
尝试使用MotionLayout
来实现之前的点赞动画,最终实现了缩放以及发散效果。
布局中添加MotionLayout
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<!--根节点改为使用MotionLayout-->
<!--layoutDescription 配置MotionScene配置文件-->
<!--showPaths设置是否显示动画的轨迹-->
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/motion_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
motion:layoutDescription="@xml/example_motion_scene"
tools:showPaths="true">
<include
android:id="@+id/include_title"
layout="@layout/layout_title" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_thumb_up"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
android:src="@drawable/icon_thumb_up"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_thumb_up1"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
android:src="@drawable/icon_thumb_up"
android:visibility="gone"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_thumb_up2"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
android:src="@drawable/icon_thumb_up"
android:visibility="gone"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_thumb_up3"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
android:src="@drawable/icon_thumb_up"
android:visibility="gone"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_thumb_up4"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
android:src="@drawable/icon_thumb_up"
android:visibility="gone"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_thumb_up5"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
android:src="@drawable/icon_thumb_up"
android:visibility="gone"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.motion.widget.MotionLayout>
</layout>
创建MotionScene
配置文件
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<!--配置动画的属性-->
<!--duration 配置动画的持续时间-->
<!--constraintSetStart 配置动画开始时,控件集的状态-->
<!--constraintSetEnd 配置动画结束时,控件集的状态-->
<!--motionInterpolator 配置动画的插值器,-->
<Transition
android:id="@+id/transition_thumb"
android:duration="1500"
motion:constraintSetEnd="@id/thumb_end"
motion:constraintSetStart="@id/thumb_start"
motion:motionInterpolator="linear">
<!--点击时触发动画-->
<!--targetId 配置触发事件的控件id-->
<!--clickAction 配置点击触发的效果-->
<!--clickAction toggle 当前控件集为开始状态,则播放动画切换至结束状态,反之亦然-->
<!--clickAction transitionToEnd 播放控件集开始到结束的动画-->
<!--clickAction transitionToStart 播放控件集结束到开始的动画-->
<!--clickAction jumpToEnd 不播放动画,控件集直接切换至结束状态-->
<!--clickAction jumpToStart 不播放动画,控件集直接切换至开始状态-->
<OnClick
motion:clickAction="transitionToEnd"
motion:targetId="@id/iv_thumb_up" />
<!--关键帧集合,用于实现缩放效果-->
<KeyFrameSet>
<!--修改属性-->
<!--framePosition 取值范围为0-100-->
<!--motionTarget 设置修改的对象-->
<!--scaleX 设置x轴缩放大小-->
<!--scaleY 设置y轴缩放大小-->
<KeyAttribute
android:scaleX="1.6"
android:scaleY="1.6"
motion:framePosition="25"
motion:motionTarget="@id/iv_thumb_up" />
<KeyAttribute
android:scaleX="1"
android:scaleY="1"
motion:framePosition="50"
motion:motionTarget="@id/iv_thumb_up" />
</KeyFrameSet>
</Transition>
<!--控件集 动画开始时的状态-->
<ConstraintSet android:id="@+id/thumb_start">
<!--与layout文件中的控件对应-->
<!--visibilityMode 如果需要改变控件的可见性,需要将此字段配置为ignore-->
<Constraint
android:id="@+id/iv_thumb_up"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:visibilityMode="ignore" />
<Constraint
android:id="@+id/iv_thumb_up1"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:visibilityMode="ignore">
<!--改变控件的属性-->
<!--attributeName 属性名-->
<!--customFloatValue Float类型属性值-->
<CustomAttribute
motion:attributeName="alpha"
motion:customFloatValue="1" />
</Constraint>
<Constraint
android:id="@+id/iv_thumb_up2"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:visibilityMode="ignore">
<CustomAttribute
motion:attributeName="alpha"
motion:customFloatValue="1" />
</Constraint>
<Constraint
android:id="@+id/iv_thumb_up3"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:visibilityMode="ignore">
<CustomAttribute
motion:attributeName="alpha"
motion:customFloatValue="1" />
</Constraint>
<Constraint
android:id="@+id/iv_thumb_up4"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:visibilityMode="ignore">
<CustomAttribute
motion:attributeName="alpha"
motion:customFloatValue="1" />
</Constraint>
<Constraint
android:id="@+id/iv_thumb_up5"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:visibilityMode="ignore">
<CustomAttribute
motion:attributeName="alpha"
motion:customFloatValue="1" />
</Constraint>
</ConstraintSet>
<!--控件集 动画结束时的状态-->
<ConstraintSet android:id="@+id/thumb_end">
<Constraint
android:id="@+id/iv_thumb_up"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:visibilityMode="ignore" />
<Constraint
android:id="@+id/iv_thumb_up1"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginTop="110dp"
android:layout_marginEnd="90dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:visibilityMode="ignore">
<CustomAttribute
motion:attributeName="alpha"
motion:customFloatValue="0.5" />
</Constraint>
<Constraint
android:id="@+id/iv_thumb_up2"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginStart="70dp"
android:layout_marginTop="95dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:visibilityMode="ignore">
<CustomAttribute
motion:attributeName="alpha"
motion:customFloatValue="0.4" />
</Constraint>
<Constraint
android:id="@+id/iv_thumb_up3"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="85dp"
android:layout_marginBottom="140dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:visibilityMode="ignore">
<CustomAttribute
motion:attributeName="alpha"
motion:customFloatValue="0.6" />
</Constraint>
<Constraint
android:id="@+id/iv_thumb_up4"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="120dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:visibilityMode="ignore">
<CustomAttribute
motion:attributeName="alpha"
motion:customFloatValue="0.2" />
</Constraint>
<Constraint
android:id="@+id/iv_thumb_up5"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="60dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:visibilityMode="ignore">
<CustomAttribute
motion:attributeName="alpha"
motion:customFloatValue="0" />
</Constraint>
</ConstraintSet>
</MotionScene>
监听动画状态
class MotionLayoutExampleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: LayoutMotionLayoutExampleActivityBinding = DataBindingUtil.setContentView(this, R.layout.layout_motion_layout_example_activity)
binding.motionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
// 动画开始
// 把发散的按钮显示出来
binding.ivThumbUp1.visibility = View.VISIBLE
binding.ivThumbUp2.visibility = View.VISIBLE
binding.ivThumbUp3.visibility = View.VISIBLE
binding.ivThumbUp4.visibility = View.VISIBLE
binding.ivThumbUp5.visibility = View.VISIBLE
}
override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
// 动画进行中
}
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
// 动画完成
// 隐藏发散的按钮,将状态还原
binding.root.postDelayed({
binding.ivThumbUp1.visibility = View.GONE
binding.ivThumbUp2.visibility = View.GONE
binding.ivThumbUp3.visibility = View.GONE
binding.ivThumbUp4.visibility = View.GONE
binding.ivThumbUp5.visibility = View.GONE
binding.motionLayout.progress = 0f
}, 200)
}
override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
}
})
}
}
效果如图:
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓
![](https://i-blog.csdnimg.cn/blog_migrate/925ee9d2e647142287b5aadd2b9d9181.png)