前言
本篇文章我们实现一个简单的动画效果,目的是熟悉和加深Android属性动画的使用。另外这次我们使用kotlin来进行开发,不熟悉kotlin的同学可以自行简单了解一下kotlin语法,基本应该能看懂
我们知道,android的View Animation动画可以移动、放大等效果,但是不能改变布局的实际属性。比如使用scaleAnimation使布局缩放,但是布局的实际大小并没有改变,所以会遮盖旁边的布局,并不是把布局撑开,挤压其他布局。
效果展示
现在我们要实现一种效果如下:
这时候使用ViewAnimation就会比较麻烦,所以我们使用ObjectAnimator来实现。
动画分析
这个效果一共有三个状态:
(状态1)
(状态2)
(状态3)
整个效果包含两个阶段:
淹没 —— 从状态1到状态2。整个粉红色的区域向上淹没整个页面
展开 —— 从状态2到状态3。当淹没整个页面后,从中间展开直至整个页面
整体布局
这两个阶段就是实际上就是通过两部分动画的依次执行来实现的,我们先来看看布局:
代码语言:javascript复制<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width= "match_parent"
android:layout_height= "match_parent" >
<RelativeLayout
android:id="@ id/animation_content"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_gravity="bottom"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/girl1"/>
<FrameLayout
android:id="@ id/spread_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_centerInParent="true" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/spread_view"
android:drawableBottom="@drawable/camera_top"
android:background="#e07468" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/spread_view"
android:drawableTop="@drawable/camera_bottom"
android:background="#e07468" />
</RelativeLayout >
</FrameLayout>
整个布局主要由四个部分组成:
- animation_content —— 这个就是整个粉色区域的部分。
- spread_view —— 这个是黑色区域的部分,一开始高度是0
- 两个textview —— 中间的圆形button实际上由上下两个独立的部分组成的,而且这两部分中间夹着spread_view(这么布局是为了第二阶段的动画,下面会详细讲解)
这里要注意,我们使用了textview而不是imageview来实现button的布局,是因为如果使用imageview,当第二阶段展开到button的两部分超出屏幕顶部和底部时,imageview区域压缩导致图片会被缩小而不是溢出,差别如下
使用ImageView的效果
使用TextView的效果
所以我们这里使用TextView。并通过drawableBottom等属性来设置图片,这样当区域缩小时,图片大小并不会改变。
源码解析
下面就是实现的代码:
代码语言:javascript复制class FloodAndSpreadActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.flood_and_spread_activity)
init()
}
fun init(){
val height: Int = window.windowManager.defaultDisplay.height
var floodWrapper = ViewWrapper(animation_content)
var spreadWrapper = ViewWrapper(spread_view)
var floodAnimation = ObjectAnimator.ofInt(floodWrapper, "height", height)
floodAnimation.duration = 1000
floodAnimation.start()
floodAnimation.addListener(object: Animator.AnimatorListener{
override fun onAnimationRepeat(p0: Animator?) {
}
override fun onAnimationCancel(p0: Animator?) {
}
override fun onAnimationStart(p0: Animator?) {
}
override fun onAnimationEnd(p0: Animator?) {
ObjectAnimator.ofInt(spreadWrapper, "height", height).setDuration(1000).start()
}
})
}
class ViewWrapper (var mTarget: View){
fun getHeight():Int{
return mTarget.layoutParams.height
}
fun setHeight(height: Int){
mTarget.layoutParams.height = height
mTarget.requestLayout()
}
}
}
Kotlin的一个优点就是能够大量的简化代码,可以看到只用十几行就实现了这个效果。
回到代码本身,我们通过上面的分析知道整个过程由两部分动画组成:淹没和展开。
在代码中可以看到淹没动画floodAnimation,它通过floodWrapper来动态改变animation_content的高度直至屏幕高度,这样就实现了淹没的效果。而且由于spread_view设置成了centerInParent,而button的两个部分与spread_view关联,所以在这个动画过程中button会跟随着一起移动并始终处于animation_content中心。
知识点总结
ObjectAnimator
这里简单说说我们用到的方法:ofInt(floodWrapper, "height", height)
。这个方法主要针对int类型的参数。第一个参数是要改变属性的类,可以是代理类,下面会讲到;第二个参数是要改变的属性名,实际上是调用类的对应的getter和setter方法;第三个参数是属性的最终值,整个动画过程中改属性会从当前的值逐渐改变至最终值。
ObjectAnimator还有很多方法,大家有兴趣可以自行学习。
ViewWrapper
这里要先说一说floodWrapper,它是一个ViewWrapper对象。ObjectAnimator会通过getter和setter方法来改变类某个属性的值,但是如果没有对应的方法或者需要更多的操作,我们可以使用代理的形式,ViewWrapper就是一个代理类。由于View只有getHeight函数没有setHeight函数,所以我们使用ViewWrapper代理它并实现getHeight和setHeight函数。
总结
再回到floodAnimation,我们看到有一个监听器,当动画结束的时候开始了另外一个动画。动画的这种关联行为也可以使用另外一种方式:AnimatorSet,通过AnimatorSet可以实现多个动画的不同顺序的执行,处理复杂的动画效果非常有用。由于本篇只是一个简单的顺序执行就没有使用,关于AnimatorSet的使用比较简单,大家可以查阅官方文档。
在floodAnimation完成时我们执行了另外一个动画,这就是第二个阶段:展开。这次我们改变的是spread_view的高度,由于button的两个部分是与spread_view关联的,所以当spread_view高度改变时,button的两个部分也随着分离开,这样就形成了展开的效果,直到spread_view展开到整个屏幕。
这样这个效果就完成了,这个效果实际的应用是一个过渡或者过场动画。主要的知识点就是属性动画的使用。
大家有兴趣可以自己手动实现一下,对这两个功能有更深入的了解。