Android实现仿谷歌日历FAB:仅内部图标旋转而非整个按钮
你之前的方案问题出在把旋转动画绑定到了整个FAB控件上,才会导致方形按钮跟着旋转出现违和感。不需要叠放两个主FAB,单FAB就能实现目标效果,所有形变、旋转动画只作用在FAB内部的图标上即可。
实现步骤
1. 修改布局文件
删掉原来叠放在底部的第二个主FAB,只保留1个主FAB和上层的迷你功能FAB:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/constraintLayout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <!-- 迷你功能FAB --> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab_task" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="24dp" android:visibility="invisible" app:backgroundTint="@color/fab_calendar" app:fabSize="mini" app:layout_constraintBottom_toTopOf="@+id/fab_main" app:layout_constraintEnd_toEndOf="@+id/fab_main" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="@+id/fab_main" app:srcCompat="@drawable/ic_baseline_task_alt_24" app:tint="@color/white" /> <!-- 唯一主FAB,初始显示加号图标 --> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab_main" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" android:onClick="mainFabClickListener" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:tint="@color/black" app:srcCompat="@drawable/ic_baseline_add_24" /> </androidx.constraintlayout.widget.ConstraintLayout>
2. 编写Java逻辑
核心规则:不对FAB本身加任何旋转、缩放动画,所有旋转、透明度过渡只作用在FAB内部的图标控件上,FAB本身仅做背景色、图标色调的平滑渐变。
private FloatingActionButton mFabMain; private FloatingActionButton mFabTask; private boolean isFabExpanded = false; private int colorNormal; private int colorCalendar; private final long ANIM_DURATION = 300L; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mFabMain = findViewById(R.id.fab_main); mFabTask = findViewById(R.id.fab_task); colorNormal = ContextCompat.getColor(this, R.color.design_default_color_secondary); colorCalendar = ContextCompat.getColor(this, R.color.fab_calendar); } public void mainFabClickListener(View view) { // 拿到FAB内部承载图标的子View View iconView = mFabMain.getChildAt(0); if (!isFabExpanded) { // 加号转日历的动画 ObjectAnimator rotateOut = ObjectAnimator.ofFloat(iconView, "rotation", 0f, 180f); ObjectAnimator alphaOut = ObjectAnimator.ofFloat(iconView, "alpha", 1f, 0f); AnimatorSet outSet = new AnimatorSet(); outSet.playTogether(rotateOut, alphaOut); outSet.setDuration(ANIM_DURATION/2); outSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { // 旧图标完全淡出后切换新图标,执行反向转回来的入场动画 mFabMain.setImageResource(R.drawable.ic_baseline_event_24); iconView.setRotation(-180f); ObjectAnimator rotateIn = ObjectAnimator.ofFloat(iconView, "rotation", -180f, 0f); ObjectAnimator alphaIn = ObjectAnimator.ofFloat(iconView, "alpha", 0f, 1f); // FAB背景、图标颜色同步渐变 ObjectAnimator bgColor = ObjectAnimator.ofArgb(mFabMain, "backgroundTint", colorNormal, colorCalendar); ObjectAnimator iconTint = ObjectAnimator.ofArgb(mFabMain, "imageTint", Color.BLACK, Color.WHITE); AnimatorSet inSet = new AnimatorSet(); inSet.playTogether(rotateIn, alphaIn, bgColor, iconTint); inSet.setDuration(ANIM_DURATION/2); inSet.start(); toggleMiniFabsVisibility(true); isFabExpanded = true; } }); outSet.start(); } else { // 日历转回加号的反向动画 ObjectAnimator rotateOut = ObjectAnimator.ofFloat(iconView, "rotation", 0f, -180f); ObjectAnimator alphaOut = ObjectAnimator.ofFloat(iconView, "alpha", 1f, 0f); AnimatorSet outSet = new AnimatorSet(); outSet.playTogether(rotateOut, alphaOut); outSet.setDuration(ANIM_DURATION/2); outSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mFabMain.setImageResource(R.drawable.ic_baseline_add_24); iconView.setRotation(180f); ObjectAnimator rotateIn = ObjectAnimator.ofFloat(iconView, "rotation", 180f, 0f); ObjectAnimator alphaIn = ObjectAnimator.ofFloat(iconView, "alpha", 0f, 1f); ObjectAnimator bgColor = ObjectAnimator.ofArgb(mFabMain, "backgroundTint", colorCalendar, colorNormal); ObjectAnimator iconTint = ObjectAnimator.ofArgb(mFabMain, "imageTint", Color.WHITE, Color.BLACK); AnimatorSet inSet = new AnimatorSet(); inSet.playTogether(rotateIn, alphaIn, bgColor, iconTint); inSet.setDuration(ANIM_DURATION/2); inSet.start(); toggleMiniFabsVisibility(false); isFabExpanded = false; } }); outSet.start(); } } private void toggleMiniFabsVisibility(boolean show) { if (show) { mFabTask.setVisibility(View.VISIBLE); mFabTask.setTranslationY(20f); mFabTask.setAlpha(0f); mFabTask.animate().translationY(0f).alpha(1f).setDuration(200L).start(); } else { mFabTask.animate().translationY(20f).alpha(0f).setDuration(200L) .withEndAction(() -> mFabTask.setVisibility(View.INVISIBLE)) .start(); } }
如果需要和谷歌日历完全一致的丝滑图标变形效果,可以把硬切图标的逻辑替换为AnimatedVectorDrawable,实现加号到日历图标的路径形变动画即可。
内容的提问来源于stack exchange,提问作者backslashV
相关产品推荐
相关产品推荐

