如何使用Kotlin在Android中创建自定义可扩展和可展开的浮动动作按钮

381 阅读6分钟

使用Kotlin在Android中创建自定义可扩展和可扩充的浮动动作按钮

本教程将介绍如何在Android中创建一个自定义的可扩展浮动动作按钮(FAB)。这涉及到使用动画类。

动画显示了按钮在被点击时的状态变化。

什么是浮动动作按钮?

浮动动作按钮(FAB)是一个圆形按钮,通常漂浮在屏幕上,显示应用程序的主要动作。

可扩展的浮动动作按钮是一种FAB,当点击或用户滚动屏幕上的内容时,它可以产生动画。

前提条件

要完成本教程,读者应该。

  • 对[Material组件]有一个基本的了解。
  • 熟悉[Kotlin]编程语言和ViewBinding
  • 安装有[Android Studio]。

目标

在本教程中,我们将。

  • 设计一个自定义的可扩展浮动动作按钮。
  • 设计一个可扩展的浮动动作按钮。
  • 学习如何处理FAB的点击。

设计自定义可扩展的FAB

创建一个新的安卓项目后,进入drawable 文件夹,从矢量资产中导入可绘制的图标。这些图标将被放置在FAB的中心位置。要添加图标,右击drawable ,选择new ,然后选择Vector Asset ,从Clip Art 选择一个图标。

本教程将选择add,call, 和message 的图标。因此,我们要创建一个有三个按钮的自定义FAB。

将以下代码粘贴到activity_main.xml 文件中。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

  <com.google.android.material.floatingactionbutton.FloatingActionButton
      android:id="@+id/floatingActionButtonAdd"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginEnd="24dp"
      android:layout_marginBottom="24dp"
      android:clickable="true"
      android:focusable="true"
      android:backgroundTint="@color/purple_700"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:srcCompat="@drawable/ic_add" />

  <com.google.android.material.floatingactionbutton.FloatingActionButton
      android:id="@+id/floatingActionButtonCall"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginBottom="16dp"
      android:clickable="true"
      android:focusable="true"
      android:tint="@color/white"
      android:visibility="invisible"
      app:layout_constraintBottom_toTopOf="@+id/floatingActionButtonAdd"
      app:layout_constraintEnd_toEndOf="@+id/floatingActionButtonAdd"
      app:srcCompat="@drawable/ic_call" />

  <com.google.android.material.floatingactionbutton.FloatingActionButton
      android:id="@+id/floatingActionButtonMessage"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginBottom="16dp"
      android:clickable="true"
      android:focusable="true"
      android:tint="@color/white"
      android:visibility="invisible"
      app:layout_constraintBottom_toTopOf="@+id/floatingActionButtonCall"
      app:layout_constraintEnd_toEndOf="@+id/floatingActionButtonCall"
      app:srcCompat="@drawable/ic_message_white_24dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

最初,CallMessage 按钮被设置为不可见。当Add 按钮被点击时,它们将出现。

创建自定义FAB动画

我们现在将创建动画,当Add 按钮被点击时,适用于其他两个按钮(CallMessage)。

要创建动画,进入资源管理器,选择动画,然后点击(+)添加Animation Resource File

我们将为Add 按钮准备两个动画资源文件,一个用于打开时旋转,另一个用于关闭时旋转。

CallMessage 按钮将有两个动画资源文件,用于从上到下的动画,Add 按钮。

  1. rotate_open_animation.xml:这使按钮从0到45度旋转。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">

    <rotate
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toDegrees="45"
        android:duration="300"/>

</set>
  1. rotate_close_animation.xml:: 将Add 按钮从45度旋转到0度的动画。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">

    <rotate
        android:fromDegrees="45"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toDegrees="0"
        android:duration="300"/>

</set>
  1. from_bottom_animation.xml:这是从底部对按钮进行动画处理。这里,CallMessage 按钮从下往上翻译。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">

    <translate
        android:duration="300"
        android:fromYDelta="300%"
        android:toYDelta="0%" />

    <scale
        android:pivotY="50%"
        android:pivotX="50%"
        android:toXScale="0.9"
        android:toYScale="0.9"/>

    <alpha
        android:fromAlpha="0"
        android:toAlpha="1"
        android:duration="800"/>

</set>

解释。

  • translate 标签在沿X和Y轴移动按钮时很有用。
  • scale 用于沿X轴和/或Y轴缩放按钮。
  • pivot 标签显示沿X轴和Y轴的旋转中心。
  • alpha tag表示动画的不透明度。它的设置范围是0到1,持续时间是800ms。
  1. to_bottom_animation.xml:从上到下对按钮进行动画。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"

    android:fillAfter="true">
    <translate
        android:duration="300"
        android:fromYDelta="0%"
        android:toYDelta="300%" />

    <scale
        android:pivotY="50%"
        android:pivotX="50%"
        android:fromYScale="0.9"
        android:fromXScale="0.9"
        android:toXScale="0.9"
        android:toYScale="0.9"/>

    <alpha
        android:fromAlpha="1"
        android:toAlpha="0"
        android:duration="150"/>

</set>

下面是对动画中使用的属性的解释。

  • fromXDelta - 在动画开始时应用X坐标的变化。
  • toXDelta - 在动画结束时改变X坐标。
  • fromYDelta - 改变Y坐标,在动画开始时应用。
  • toYDelta - 在动画结束时应用的Y坐标变化。
  • fromDegrees - 旋转开始的角度。
  • toDegrees - 旋转停止的角度。
  • duration - 动画播放的周期,以毫秒为单位。
  • pivotXpivotY 构成动画的中心点。

初始化动画

MainActivity.kt ,添加以下代码来全局初始化动画。

private val rotateOpenAnimation: Animation by lazy {AnimationUtils.loadAnimation(this, R.anim.rotate_open_animation)}
private val rotateCloseAnimation: Animation by lazy {AnimationUtils.loadAnimation(this, R.anim.rotate_close_animation)}
private val fromBottomAnimation: Animation by lazy {AnimationUtils.loadAnimation(this, R.anim.from_bottom_animation)}
private val toBottomAnimation: Animation by lazy {AnimationUtils.loadAnimation(this, R.anim.to_bottom_animation)}

下面是MainActivty.kt 类的完整实现。

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private var addButtonClicked = false

    // Initializing the animations
    private val rotateOpenAnimation: Animation by lazy {AnimationUtils.loadAnimation(this, R.anim.rotate_open_animation)}
    private val rotateCloseAnimation: Animation by lazy {AnimationUtils.loadAnimation(this, R.anim.rotate_close_animation)}
    private val fromBottomAnimation: Animation by lazy {AnimationUtils.loadAnimation(this, R.anim.from_bottom_animation)}
    private val toBottomAnimation: Animation by lazy {AnimationUtils.loadAnimation(this, R.anim.to_bottom_animation)}

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.floatingActionButtonAdd.setOnClickListener {
            onAddButtonClicked()
        }
        binding.floatingActionButtonCall.setOnClickListener {
            Toast.makeText(this, "Call Button Clicked", Toast.LENGTH_SHORT).show()
        }
        binding.floatingActionButtonMessage.setOnClickListener {
            val intent = Intent(this, MessageActivity::class.java)
            startActivity(intent)
        }
    }
    private fun onAddButtonClicked() {
        setVisibility(addButtonClicked)
        setAnimation(addButtonClicked)
        buttonSetClickable()

        if (!addButtonClicked){
            addButtonClicked = true
        }else{
            addButtonClicked = false
        }
    }

    //Setting call and message buttons visible
    private fun setVisibility(buttonClicked: Boolean) {
        if (!buttonClicked){
            binding.floatingActionButtonCall.visibility = VISIBLE
            binding.floatingActionButtonMessage.visibility = VISIBLE
        }else{
            binding.floatingActionButtonCall.visibility = INVISIBLE
            binding.floatingActionButtonMessage.visibility = INVISIBLE
        }
    }

    //Setting the animation on the buttons
    private fun setAnimation(buttonClicked: Boolean) {
        if (!buttonClicked){
            binding.floatingActionButtonCall.startAnimation(fromBottomAnimation)
            binding.floatingActionButtonMessage.startAnimation(fromBottomAnimation)
            binding.floatingActionButtonAdd.startAnimation(rotateOpenAnimation)
        }else{
            binding.floatingActionButtonCall.startAnimation(toBottomAnimation)
            binding.floatingActionButtonMessage.startAnimation(toBottomAnimation)
            binding.floatingActionButtonAdd.startAnimation(rotateCloseAnimation)
        }
    }

    //Checking if the add button is clicked
    private fun buttonSetClickable() {
        if (!addButtonClicked){
            binding.floatingActionButtonCall.isClickable = true
            binding.floatingActionButtonMessage.isClickable = true
        }else{
            binding.floatingActionButtonCall.isClickable = false
            binding.floatingActionButtonMessage.isClickable = false
        }
    }
}

设计可扩展的浮动按钮

对于可扩展的FAB,我们将使用一个第三方的库。因此,在settings.gradle ,在repositories 里面添加jitpack库。

maven { url 'https://jitpack.io' }

build.gradle 项目级文件中,添加以下依赖关系。

implementation 'com.github.imtuann:FloatingActionButtonExpandable:1.1.2'

同步项目后,你现在就可以开始设计按钮了。这个库有所有的功能,每当RecyclerView 滚动的时候,就会对按钮进行动画处理。

可扩展的FAB主要用于消息应用程序中,以添加聊天记录,如在谷歌消息应用程序中。因此,它要求我们使用一个RecyclerView 。一个RecyclerView ,用来显示一个数据列表。它由以下组件组成。

  • RecyclerRow - 这是在你的活动中声明的视图。它是数据列表将被显示的地方。
  • Layout Manager - 它定义了列表应该如何组织我们的数据。它可以是水平的、垂直的,或者是网格布局。
  • Adapter - 它将我们的数据,通常是一个列表,连接到我们的 。它还观察列表中的变化并更新 。RecyclerView RecyclerView
  • ViewHolder - 这持有我们显示数据的视图。

我们将创建一个名为message_recycler_row.xml 的回收器行项,并添加以下XML代码。

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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="wrap_content"
    app:cardElevation="10dp"
    android:layout_margin="8dp"
    app:cardCornerRadius="10dp">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/chatsTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="20dp"
            android:text="Hello!"
            android:textSize="20sp"
            android:textColor="@color/black"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

activity_message.xml ,添加以下代码来创建可扩展的FAB按钮和RecyclerView

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MessageActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerRow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:layout_editor_absoluteX="1dp"
        tools:listitem="@layout/message_recycler_row" />

    <com.tuann.floatingactionbuttonexpandable.FloatingActionButtonExpandable
        android:id="@+id/expandableFAB"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="24dp"
        app:fab_content="Start a Chat"
        app:fab_duration="300"
        app:fab_expanded="true"
        app:fab_icon="@drawable/ic_message_white_24dp"
        app:fab_padding="15dp"
        app:fab_padding_text_icon="20dp"
        app:fab_text_size="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

创建消息回收器适配器

适配器将我们的数据,通常是一个列表,连接到我们的RecyclerView 。它还观察列表中的变化并更新RecyclerView 。在Adapter中,我们有一个ViewHolder ,该类持有显示数据的View。

一个Adapter需要你实现以下方法。

  • onCreateViewHolder() - RecyclerView调用这个方法来创建一个 。ViewHolder
  • onBindView.Holder()- 这个方法将数据绑定在作为参数提供的ViewHolder
  • getItemCount() - 返回 中的项目数量。RecyclerView

MessageAdapter.kt 类中,添加以下代码来创建适配器类。

class MessageAdapter(private var text: List<String>): RecyclerView.Adapter<MessageAdapter.MyViewHolder>() {
    // ViewHolder class for holding the view
    inner class MyViewHolder(val binding: MessageRecyclerRowBinding): RecyclerView.ViewHolder(binding.root){

        init {
            binding.chatsTextView.setOnClickListener {
                val position: Int = adapterPosition
                Toast.makeText(binding.chatsTextView.context, "$position", Toast.LENGTH_SHORT).show()
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        return MyViewHolder(MessageRecyclerRowBinding.inflate(LayoutInflater.from(parent.context), parent, false))
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.binding.chatsTextView.text = text[position]
    }
  
    override fun getItemCount(): Int {
        return text.size
    }
}

最后,在MessageActivity.kt 类中添加下面的代码。

class MessageActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMessageBinding
    private var chatLists = mutableListOf<String>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMessageBinding.inflate(layoutInflater)
        setContentView(binding.root)
        addToChatList()
        binding.recyclerRow.adapter = MessageAdapter(chatLists)
        setUpFloatingActionButton()

    }

    private fun setUpFloatingActionButton() {
        binding.expandableFAB.setOnClickListener {
            Toast.makeText(applicationContext, "FAB button clicked", Toast.LENGTH_SHORT).show()
        }
        // Detect a scroll and respond based on the direction
        binding.recyclerRow.addOnScrollListener(object : RecyclerView.OnScrollListener(){
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                if (dy > 0){ // Scrolling down
                    binding.expandableFAB.collapse()
                }else{ // Scrolling up
                    binding.expandableFAB.expand()
                }
            }
        })
    }
    // Add data to the list
    private fun addToChatList() {
        for (i in 0..50){
            chatLists.add("Hello, Happy Coding. Let's have a chat even as we code.")
        }
    }
}

上面的类有设置浮动动作按钮的监听器的方法。当FAB按钮被点击时,它将展开并烤出一个文本,以显示按钮被点击。

RecyclerView 被设置为检测滚动位置的变化。当滚动时,该按钮将展开,因此被称为可扩展的浮动动作按钮。

Project Demo

总结

在本教程中,我们已经学会了如何创建一个自定义的可扩展浮动动作按钮和一个可扩展浮动动作按钮。所提供的代码有助于创建FAB,也有助于学习如何在RecyclerView ,实现点击和滚动监听器。