展开式(可收缩)菜单弹出效果简单实现 - 主要是通过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