实现View的添加和移除动画
点击查看采用系统实现方式
采用LayoutTransition 实现 添加view 和移除view 的动画效果
- 优点 采用系统api 安全 实现方便快捷
- 缺点 不能添加和删除动画同时执行,只能等到一个执行完之后才能进行操作
自定义ViewGroup实现的效果
device-2020-11-20-183752.2020-11-25 17_19_30.gif
整体代码
/**
* @author
* @desc : 支持添加view 伴随动画的效果
*
*/
class AnimViewGroup(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
/**
* 缓存一个view 为了复用
*/
var cacheScrapView: View? = null
private set
private var removeAnimId = 0
private var addAnimId = 0
private var removeAnim: Animation? = null
private var addAnim: Animation? = null
init {
context.obtainStyledAttributes(attrs, R.styleable.AnimViewGroup).apply {
removeAnimId = getResourceId(R.styleable.AnimViewGroup_anim_remove_child, 0)
addAnimId = getResourceId(R.styleable.AnimViewGroup_anim_add_child, 0)
}.recycle()
}
override fun onFinishInflate() {
super.onFinishInflate()
if (childCount != 1) {
throw Exception("this group must have only one child in xml")
}
}
/**
* 设置动画效果
* @param enter 进入的动画
* @param exit 离开的动画
*/
fun setCustomAnimations(@AnimRes enter: Int, @AnimRes exit: Int){
addAnim = loadAnimation(enter)
removeAnim = loadAnimation(exit)
}
fun addChildWithAnim(view: View) {
if (childCount < 1) {
addView(view)
return
}
//原始的view 移除动画
val child0 = getChildAt(0)
if (removeAnim == null) removeAnim = loadAnimation(removeAnimId)
child0.startAnimation(removeAnim)
child0.animation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
//此处直接调用 removeView(child0) 会导致崩溃
//参考https://stackoverflow.com/questions/28180592/android-remove-view-when-its-animation-finished
post {
removeView(child0)
cacheScrapView = child0
}
}
override fun onAnimationStart(animation: Animation?) {
}
})
//新添加的view执行的动画
if (addAnim == null) addAnim = loadAnimation(addAnimId)
addView(view)
view.startAnimation(addAnim)
}
/**
* 处理加载动画
* @param animId 动画的xml文件配置
*/
private fun loadAnimation(animId: Int): Animation? {
if (animId != 0) {
val dir = context.resources.getResourceTypeName(animId)
// try AnimationUtils first
if ("anim" == dir) {
val animation = AnimationUtils.loadAnimation(context, animId)
if (animation != null) {
return animation
}
}
}
return null
}
}
自定义的style
<declare-styleable name="AnimViewGroup">
<attr name="anim_remove_child" format="reference"/>
<attr name="anim_add_child" format="reference"/>
</declare-styleable>
anim文件下定义的动画
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="100%p"
android:toYDelta="0%p"
android:duration="@integer/card_flip_time_full"/>
<!-- <alpha-->
<!-- android:fromAlpha="0.5"-->
<!-- android:toAlpha="1.0"-->
<!-- android:duration="@integer/card_flip_time_full"/>-->
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="0%p"
android:toYDelta="-100%p"
android:duration="@integer/card_flip_time_full"/>
<alpha
android:fromAlpha="1"
android:toAlpha="0.5"
android:duration="@integer/card_flip_time_full"/>
</set>
难点
- 对于动画的理解
- 报错的解决 Attempt to read from field 'int android.view.View.mViewFlags' on a null object reference
解决问题参考的文章
-
-