展开式(可收缩)菜单弹出效果简单实现 - 主要是通过AnimatorSet联动实现
属性动画简单使用可参考:Android 属性动画(Animator)简单使用
可以分为3种ObjectAnimator动画:
位移动画(translationX/translationY)
透明度动画(alpha)
旋转动画(rotation)
先上效果:
开始贴代码
#1 xml布局:(FloatingActionButton换成其他View都可以,这儿只是示例)
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
//...TODO anything
<FrameLayout android:id="@+id/main_layout_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_on_black_50"
android:clickable="true"
android:visibility="gone" />
<com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/main_fab_transfer"
style="@style/GreenFabButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_transfer"
android:visibility="gone"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/main_fab_return"
style="@style/GreenFabButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_return"
android:visibility="gone"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/main_fab_minus"
style="@style/GreenFabButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_minus"
android:visibility="gone"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/main_fab_plus"
style="@style/GreenFabButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="30dp"
android:src="@drawable/ic_plus"
android:visibility="gone"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin" />
<com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/main_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:visibility="visible"
android:layout_margin="@dimen/fab_margin"
app:fabCustomSize="60dp"
app:tint="@color/colorWhite"
app:maxImageSize="30dp"
app:srcCompat="@android:drawable/ic_dialog_email" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
cc: 因为要从一个菜单按钮弹出,所以其他子菜单按钮需要放置在统一位置即可,最好将菜单按钮放在最上层;
#2 动画代码:
位移动画(translationX/translationY):)
ObjectAnimator.ofFloat(
menuView,
"translationX",
0F,
x)
ObjectAnimator.ofFloat(
menuView,
"translationY",
0F,
y)
//x,y 可以理解为 移动终点坐标点至起始点的距离
透明度动画(alpha) :)
ObjectAnimator.ofFloat(menuView, "alpha", 0F, 1F)
旋转动画(rotation) :)
ObjectAnimator.ofFloat(mainView, "rotation", 0F, 90F) //旋转90度
#3 Activity/Fragment 最终实现:)
class MainActivity : AppCompatActivity() {
private var menuViews = mutableListOf<FloatingActionButton>()
private var isMenuOpen = false
companion object {
private const val DISTANCE: Int = 70
private const val ANIMATION_ALPHA: Long = 600
private const val ANIMATION_TRANSLATION: Long = 600
private const val ANIMATION_ROTATION: Long = 800
}
//每个子菜单弹出后的点坐标,即水平方向张开
private val childMenuPoints by lazy {
mutableListOf(
PointF(DISTANCE.toDp() * -1, 0F),
PointF(DISTANCE.toDp() * -2, 0F),
PointF(DISTANCE.toDp() * -3, 0F),
PointF(DISTANCE.toDp() * -4, 0F)
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
menuViews.add(main_fab_minus)
menuViews.add(main_fab_plus)
menuViews.add(main_fab_return)
menuViews.add(main_fab_transfer)
main_layout_bg.setOnClickListener {
showCloseAnim()
}
main_fab.setOnClickListener {
if (!isMenuOpen) {
showOpenAnim()
} else {
showCloseAnim()
}
}
}
private fun showOpenAnim() {
main_fab.isEnabled = false
main_layout_bg.show()
//for循环来开始小图标的出现动画
for (i in menuViews.indices) {
menuViews[i].show()
val set = AnimatorSet()
with(set) {
playTogether(
ObjectAnimator.ofFloat(menuViews[i], "translationX", 0F, childMenuPoints[i].x),
ObjectAnimator.ofFloat(menuViews[i], "translationY", 0F, childMenuPoints[i].y),
ObjectAnimator.ofFloat(menuViews[i], "alpha", 0F, 1F).setDuration(ANIMATION_ALPHA)
)
interpolator = BounceInterpolator()//添加回弹动画
duration = ANIMATION_TRANSLATION
start()
addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationEnd(animation: Animator?) {
//菜单状态置开启
isMenuOpen = true
main_fab.isEnabled = true
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
})
}
}
//转动图标本身90°
val rotate: ObjectAnimator = ObjectAnimator.ofFloat(main_fab, "rotation", 0F, 90F)
.setDuration(ANIMATION_ROTATION)
rotate.interpolator = BounceInterpolator()//添加回弹动画
rotate.start()
}
private fun showCloseAnim() {
main_fab.isEnabled = false
main_layout_bg.hide()
//for循环来开始小图标的出现动画
for (i in menuViews.indices) {
val set = AnimatorSet()
with(set){
playTogether(
ObjectAnimator.ofFloat(menuViews[i], "translationX", childMenuPoints[i].x, 0F),
ObjectAnimator.ofFloat(menuViews[i], "translationY", childMenuPoints[i].y, 0F),
ObjectAnimator.ofFloat(menuViews[i], "alpha", 1f, 0f).setDuration(ANIMATION_ALPHA)
)
interpolator = BounceInterpolator() //添加回弹动画
duration = ANIMATION_TRANSLATION
start()
addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationEnd(animation: Animator?) {
menuViews[i].hide()
//菜单状态置关闭
isMenuOpen = false
main_fab.isEnabled = true
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
})
}
}
//转动标本身90°
val rotate: ObjectAnimator = ObjectAnimator.ofFloat(main_fab, "rotation", 0F, 90F)
.setDuration(ANIMATION_ROTATION)
rotate.interpolator = BounceInterpolator()
rotate.start()
}
}
cc: 以上代码为水平展开式(可收缩)菜单弹出效果,易于简单说明;爆炸式展开的现实其实逻辑是一致的,只需更改子菜单点坐标即可;
源码附上
Git地址: ExpandableMenuDemo