最近在写一个 6盘 的第三方客户端,设计上是想要在文件管理页面底部加一个 BottomAppBar
,随滚动隐藏。因为需要实现上传文件/创建文件夹/传输列表,如果都是作为图标放在 BottomAppBar
里,感觉不美观,所以准备浓缩在 FloatingActionButton
里。
但是查了才发现 Google 官方虽然出了 FloatingActionMenu 的设计规格,但是 Material 包里却至今没实现(都麻了,不少设计规格里有的功能一直都还没做)。
找了几个第三方库,star 数多的基本都不再更新了。就算不更了,本来有侥幸心理想拿来看看能不能用,测试完发现,如果单单作为 FAB 那完全没问题,但是就没办法跟 BottomAppBar 好好联动了。
官方的 FloatingActionButton
是可以在加了 app:layout_anchor="@id/bottomAppBar"
的情况下,自动的让 BottomAppBar 顶部出现一个嵌合 FAB 的凹槽的,如果用了第三方库那没办法出现……
思考了一下,应该可以通过预先写定 FAB 菜单所需的 FAB 后,将其设置为 invisible
,然后在点击主 FAB 时通过配合动画让它们出现,最终实现效果如下:
在此之前
由于 BottomAppBar
和 FloatingActionButton
都是 Google Design 包里的控件,因此需要导包。
首先要在确保 Project 级的 build.gradle
中存在 google()
allprojects {
repositories {
google()
jcenter()
maven { url "https://jitpack.io" }
}
}
接着在 Module 级 的 build.gradle
中加入:
dependencies {
implementation 'com.google.android.material:material:<version>'
}
<version> 可以参考 Google's Maven Repository 或者 MVN Repository。
动画文件
这里用到 Clans/FloatingActionButton@GitHub 项目里的四种动画,分别是:
- 「按比例放大」「按比例缩小」(菜单 FAB)
- 「从左到右进入」「从右到左进入」(主 FAB)
在 res
目录中新建 anim
文件夹,分别新建以下四个文件并填入对应内容。
fab_scale_up.xml
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/overshoot"
android:fromXScale="0.0"
android:toXScale="1.0"
android:fromYScale="0.0"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="200" />
fab_scale_down.xml
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/accelerate_quint"
android:fromXScale="1.0"
android:toXScale="0.0"
android:fromYScale="1.0"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="200" />
fab_slide_in_from_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/overshoot">
<alpha
android:fromAlpha="0"
android:toAlpha="1"
android:duration="300" />
<translate
android:fromXDelta="-15%p"
android:toXDelta="0"
android:duration="200" />
</set>
fab_slide_in_from_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/overshoot">
<alpha
android:fromAlpha="0"
android:toAlpha="1"
android:duration="300" />
<translate
android:fromXDelta="15%p"
android:toXDelta="0"
android:duration="200" />
</set>
布局文件
打开你要加入 BottomAppBar
的布局文件,向其中加入:
<?xml version="1.0" encoding="utf-8"?>
<......>
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomAppBar"
style="@style/Widget.MaterialComponents.BottomAppBar.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:hideOnScroll="true"
app:menu="@menu/file_bottom_appbar"
app:navigationContentDescription="@string/file_to_parent_folder"
app:navigationIcon="@drawable/ic_baseline_keyboard_capslock_24" />
<LinearLayout
android:id="@+id/file_fab_menu_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="100dp"
android:gravity="center"
android:orientation="vertical">
<include layout="@layout/file_fab_upload_layout"/>
<include layout="@layout/file_fab_create_folder_layout"/>
<include layout="@layout/file_fab_transmission_layout"/>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/file_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/file_upload"
app:backgroundTint="@color/colorAccent"
app:layout_anchor="@id/bottomAppBar"
app:srcCompat="@drawable/ic_baseline_add_24"
app:tint="@android:color/white" />
</......>
实际上就是一个 BottomAppBar
和一个 FloatingActionButton
,另外再加了一个 LinearLayout
用来放菜单 FAB。控件涉及到的菜单、图标、内容描述、颜色等请根据实际情况自行修改。(注:为 BottomAppBar
添加 app:hideOnScroll="true"
属性可以在页面滑动时自动向下隐藏,但保留主 FAB)
由于我们调用了三个布局文件,这里也需要贴一下,因为三个基本上一样,只需要修改图标等就行,所以只贴一个。
file_fab_upload_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/file_fab_upload"
android:visibility="invisible"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:layout_marginTop="4dp"
android:layout_marginBottom="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/file_upload"
android:layout_marginEnd="10dp"
android:textSize="16sp"
android:textColor="@android:color/white"
android:background="@drawable/fab_label_style"
android:padding="5dp"
android:elevation="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/file_fab_upload_button"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/file_fab_upload_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="12dp"
android:animateLayoutChanges="true"
android:contentDescription="@string/file_upload"
android:visibility="visible"
app:backgroundTint="@color/colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_baseline_cloud_upload_24"
app:tint="@android:color/white" />
</androidx.constraintlayout.widget.ConstraintLayout>
其中所用到的 @drawable/fab_label_style
如下:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#424242" />
<corners android:radius="5dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
Kotlin 代码
修改对应的 Activity 文件:
companion object {
...
var isShowFabMenu = false
lateinit var showAnimation: Animation
lateinit var hideAnimation: Animation
lateinit var showMenuAnimation: Animation
lateinit var hideMenuAnimation: Animation
...
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.XXXXXXXXXX)
initFAB()
...
}
private fun initFAB() {
showAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_scale_up)
hideAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_scale_down)
showMenuAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_slide_in_from_left)
hideMenuAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_slide_in_from_right)
file_fab.setOnClickListener {
if (!isShowFabMenu) {
//还没显示菜单
showMenu()
} else {
//显示菜单了
hideMenu()
}
}
}
private fun showMenu() {
file_fab.startAnimation(showMenuAnimation)
file_fab.setImageDrawable(
ContextCompat.getDrawable(
this,
R.drawable.ic_baseline_close_24
)
)
file_fab_upload.startAnimation(showAnimation)
file_fab_create_folder.startAnimation(showAnimation)
file_fab_transmission.startAnimation(showAnimation)
file_fab_upload.visibility = View.VISIBLE
file_fab_create_folder.visibility = View.VISIBLE
file_fab_transmission.visibility = View.VISIBLE
isShowFabMenu = true
}
private fun hideMenu() {
file_fab.startAnimation(hideMenuAnimation)
file_fab.setImageDrawable(
ContextCompat.getDrawable(
this,
R.drawable.ic_baseline_add_24
)
)
file_fab_upload.startAnimation(hideAnimation)
file_fab_create_folder.startAnimation(hideAnimation)
file_fab_transmission.startAnimation(hideAnimation)
file_fab_upload.visibility = View.INVISIBLE
file_fab_create_folder.visibility = View.INVISIBLE
file_fab_transmission.visibility = View.INVISIBLE
isShowFabMenu = false
}
监听主 FAB 的点击事件,并预先用 isShowFabMenu
作为 FLAG 来判断当前 FAB 菜单是否展开。
- 未展开,则执行
showMenu()
方法,为主 FAB 设置「从左到右进入」动画,并且把图标改成 ×。同时为菜单 FAB 各设置「按比例放大」,并使其visibility
变为VISIBLE
。最后将 FLAG 设置为true
,表明当前菜单已展开。 - 展开,则执行
hideMenu()
方法,为主 FAB 设置「从右到左进入」动画,并且把图标改回 +。同时为菜单 FAB 各设置「按比例缩小」,并使其visibility
变为 INVISIBLE
。最后将 FLAG 设置为false
,表明当前菜单已不再展开。
优货购 Chrome 87.0.4280.141
安卓还是原生流畅一些,之前用html5写的很卡啊
TigerBeanst Edge Chromium 89.0.760.0
原生控件肯定是要流畅一点……H5 还要受到 WebView 的制约