Floating Action Button-Android M新控件

2021-08-16 10:19:05 浏览数 (1)

概述

浮动操作按钮是Material Design 中推出的控件之一

浮动操作按钮 (简称 FAB) 是: “一个特殊的promoted操作案例。因为一个浮动在UI之上的圆形图标而显得格外突出,同时它还具有特殊的手势行为”

比如,如果我们在使用email app,在列出收件箱邮件列表的时候,promoted操作可能就是新建一封邮件。

浮动操作按钮代表一个屏幕之内最基本的额操作。关于FAB按钮的更多信息和使用案例请参考谷歌的官方设计规范。

运行效果


用法

谷歌在2015年的 I/O大会上公布了可以创建浮动操作按钮的支持库,但是在这之前,则须使用诸如makovkastar/FloatingActionButton 和 futuresimple/android-floating-action-button 这样的第三方库。

Floating Action Icons

The floating action button uses the same menu icons used for the App Bar at the top of the screen. This means the image should be single color and fit the material design guidelines. The best source for these icons is the material design icons site or the official google material icons:

Once you’ve selected the icon to use, download the image by selecting the icon and then “Icon Package” and choose the “Android” package. Note that Mac users may need to use the Unarchiver to properly unzip the icon package. Bring the various drawables into the drawable folders within your Android app.

Design Support Library

效果图

操作步骤

首先确保你按照Design Support Library中的指导来配置。

现在你可以把android.support.design.widget.FloatingActionButton添加到布局中了。其中src属性指的是浮动按钮所要的图标。

代码语言:javascript复制
 .support.design.widget.FloatingActionButton
        android:src="@drawable/ic_done"
        app:fabSize="normal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

另外,如果在布局的最顶部声明了xmlns:app=”http://schemas.android.com/apk/res-auto命名空间,你还可以定义一个fabSize属性,该属性决定按钮是 normal or mini.

放置浮动操作按钮需要使用CoordinatorLayout。CoordinatorLayout帮助我们协调它所包含的子view之间的交互,这一点在我们后面讲如何根据滚动的变化让按钮动画隐藏与显示的时候有用。但是目前我们能从CoordinatorLayout得到的好处是它可以让一个元素浮动在另一个元素之上。我们只需让FloatingActionButton和ListView被包含在CoordinatorLayout中,然后使用layout_anchor 与 layout_anchorGravity 属性就可以了。

代码语言:javascript复制
.support.design.widget.CoordinatorLayout
    android:id="@ id/main_content"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

          "@ id/lvToDoList"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

          .support.design.widget.FloatingActionButton
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_gravity="bottom|right"
              android:layout_margin="16dp"
              android:src="@drawable/ic_done"
              app:layout_anchor="@id/lvToDoList"
              app:layout_anchorGravity="bottom|right|end" />
.support.design.widget.CoordinatorLayout>

按钮应该处于屏幕的右下角。建议在手机上下方的margin设置为16dp而平板上设置为24dp。上面的例子中,使用的是16dp。

而根据谷歌的设计规范,drawable的尺寸应该是24dp。

实际上只需要指定一个布局文件,就可以看到效果了,只不过是这时候的FAB是固定在屏幕指定位置的,而无法随之滚动,不着急,下面会介绍如何设置成可滚动的FAB

属性介绍

  • FAB 默认使用应用主题中设置的浮起色作为按键背景。你可以使用 app:backgroundTint 属性,或者调用 setBackgroundTintList (ColorStateList tint) 方法改变 FAB 背景色;
  • 如上文中提到的,可以使用 app:fabSize 属性选择普通大小或者迷你大小;
  • 使用 android:src 改变 FAB 对应的 drawable;
  • 使用 app:rippleColor 设置 FAB 按下时的波纹效果;
  • 使用 app:borderWidth 设置 FAB 边框宽度;
  • 使用 app:elevation 设置闲置状态下 FAB 的景深(默认是 6dp);
  • 使用 app:pressedTranslationZ 设置 FAB 按下时的景深(默认是 12dp)。

浮动操作按钮的动画

官方效果图

操作步骤

要让这个过程有动画效果,你需要利用好CoordinatorLayout,CoordinatorLayout帮助协调定义在里面的view之间的动画。

用RecyclerView替换ListViews

目前,你需要用RecyclerView来替换ListView。就如这节所描述的,RecyclerView是ListView的继承者。根据谷歌的这篇文章所讲的,不支持CoordinatorLayout和ListView一起使用。你可以查看这篇指南,它帮助你过渡到RecyclerView

代码语言:javascript复制
.support.v7.widget.RecyclerView
         android:id="@ id/lvToDoList"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
.support.v7.widget.RecyclerView>

同时你还必须把RecyclerView升级到v22版本(我在这里使用的是 23.1.1),之前的v21不支持与CoordinatorLayout一起工作,确保你的build.gradle 文件是这样的:

我这个案例中使用了cardView

代码语言:javascript复制
 compile 'com.android.support:recyclerview-v7:23.1.1'
 compile 'com.android.support:cardview-v7:23.1.1'

使用CoordinatorLayout

接下来,你需要现为浮动操作按钮实现CoordinatorLayout Behavior。这个类用于定义按钮该如何响应包含在同一CoordinatorLayout之内的其它view。

创建一个继承自 FloatingActionButton.Behavior 名叫ScrollAwareFABBehavior.java的类。目前浮动操作按钮默认的behavior是为Snackbar让出空间,就如这个视频中的效果。

继承FloatingActionButton.Behavior

代码语言:javascript复制
package demo.turing.com.materialdesignwidget.floatingActionButton;

import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;

/**
 * MyApp
 *
 * @author Mr.Yang on 2016-03-04  10:48.
 * @version 1.0
 * @desc
 */
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {


    /**
     * 因为是在XML中使用app:layout_behavior定义静态的这种行为,
     * 必须实现一个构造函数使布局的效果能够正常工作。
     * 否则 Could not inflate Behavior subclass error messages.
     *
     * @param context
     * @param attrs
     */
    public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
        super();
    }

    /**
     * 处理垂直方向上的滚动事件
     *
     * @param coordinatorLayout
     * @param child
     * @param directTargetChild
     * @param target
     * @param nestedScrollAxes
     * @return
     */
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
                                       FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {

        // Ensure we react to vertical scrolling
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
                super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target,
                        nestedScrollAxes);
    }

    /**
     * 检查Y的位置,并决定按钮是否动画进入或退出
     *
     * @param coordinatorLayout
     * @param child
     * @param target
     * @param dxConsumed
     * @param dyConsumed
     * @param dxUnconsumed
     * @param dyUnconsumed
     */
    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child,
                               View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
                dyUnconsumed);

        if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
            // User scrolled down and the FAB is currently visible -> hide the FAB
            child.hide();
        } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
            // User scrolled up and the FAB is currently not visible -> show the FAB
            child.show();
        }
    }
}

将CoordinatorLayout Behavior与浮动操作按钮联系起来,通过xml的自定义属性pp:layout_behavior中定义它

activity_fab_animation.xml

代码语言:javascript复制
.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@ id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    .support.v7.widget.RecyclerView
        android:id="@ id/rvToDoList"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    .support.design.widget.FloatingActionButton
        android:id="@ id/id_fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right"
        android:layout_margin="16dp"
        android:src="@mipmap/ic_add_white"
        app:layout_anchor="@id/rvToDoList"
        app:layout_anchorGravity="bottom|right|end"
        app:layout_behavior="demo.turing.com.materialdesignwidget.floatingActionButton.ScrollAwareFABBehavior" />
.support.design.widget.CoordinatorLayout>

FabAnimation.java

代码语言:javascript复制
package demo.turing.com.materialdesignwidget.floatingActionButton;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import demo.turing.com.materialdesignwidget.R;

/**
 * Animating the Floating Action Button
 */
public class FabAnimation extends AppCompatActivity {

    private RecyclerView recyclerView ;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fab_animation);

        recyclerView = (RecyclerView) findViewById(R.id.rvToDoList);
        // 线性布局
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(new NormalRecyclerViewAdapter(this));
    }


}

NormalRecyclerViewAdapter.java

代码语言:javascript复制
package demo.turing.com.materialdesignwidget.floatingActionButton;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import demo.turing.com.materialdesignwidget.R;

/**
 * MyApp
 *
 * @author Mr.Yang on 2016-03-04  11:10.
 * @version 1.0
 * @desc
 */
public class NormalRecyclerViewAdapter extends RecyclerView.Adapter<NormalRecyclerViewAdapter.NormalTextViewHolder> {

    private final LayoutInflater mLayoutInflater;
    private final Context mContext;
    private String[] mTitles;

    public NormalRecyclerViewAdapter(Context context) {
        mTitles = context.getResources().getStringArray(R.array.titles);
        mContext = context;
        mLayoutInflater = LayoutInflater.from(context);
    }

    @Override
    public NormalTextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new NormalTextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
    }

    @Override
    public void onBindViewHolder(NormalTextViewHolder holder, int position) {
        holder.mTextView.setText(mTitles[position]);
    }

    @Override
    public int getItemCount() {
        return mTitles == null ? 0 : mTitles.length;
    }


    public static class NormalTextViewHolder extends RecyclerView.ViewHolder {

        TextView mTextView;


        NormalTextViewHolder(View view) {
            super(view);

            mTextView = (TextView) view.findViewById(R.id.text_view);

            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d("NormalTextViewHolder", "onClick--> position = "   getPosition());
                }
            });
        }
    }
}

item_text.xml

代码语言:javascript复制
<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:id="@ id/cv_item"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    card_view:cardCornerRadius="4dp">

    <TextView
        android:id="@ id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="20dp" />
android.support.v7.widget.CardView>

向下移动 FAB消失,向上移动时,FAB出现。


embedding(嵌入)-floatingactionbutton-in-header

效果图

操作步骤

This can be achieved by use CoordinatorLayout as the root view. We need to specify layout_anchor for the FAB to the top view and layout_anchorGravity to to bottom|right|end like this:

代码语言:javascript复制
.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    "match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        "@ id/viewA"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="0.6"
            android:background="@android:color/holo_purple"
            android:orientation="horizontal"/>

        "@ id/viewB"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="0.4"
            android:background="@android:color/holo_orange_light"
            android:orientation="horizontal"/>

    

    .support.design.widget.FloatingActionButton
        android:id="@ id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:clickable="true"
        android:src="@mipmap/ic_add_white"
        app:layout_anchor="@id/viewA"
        app:layout_anchorGravity="bottom|right|end"/>

.support.design.widget.CoordinatorLayout>

For details check out this stackoverflow post. See the CoordinatorLayout guide for more details on that layout.

Issues:

本文编写时,FAB 支持库仍然存在一些 bug,在 Kitkat 和 Lollipop 中分别运行示例代码,可以看到如下结果:

Lollipop 中的 FAB:

Kitkat 中的 FAB:

Issues 1: Android 4.4 和 5.0 中边缘显示

很容易看出,Lollipop 中存在边缘显示的问题。为了解决此问题,API21 的版本统一定义底部与右边缘空白为 16dp,Lollipop 以下版本统一设置为 0dp.

values/dimens.xml

代码语言:javascript复制
<dimen name="fab_margin_right">0dpdimen>
<dimen name="fab_margin_bottom">0dpdimen>

values-v21/dimens.xml

代码语言:javascript复制
<dimen name="fab_margin_right">16dpdimen>
<dimen name="fab_margin_bottom">16dpdimen>

布局文件的 FAB 中,也设置相应的值。

代码语言:javascript复制
...
    ...
    android:layout_marginBottom="@dimen/fab_margin_bottom"
    android:layout_marginRight="@dimen/fab_margin_right"/>

Issues 2: Android 5.0 中阴影显示

再看一遍上面的截图,会发现 Kitkat 中有阴影显示,而 Lollipop 中并没有。在 Lollipop 上,可以直接在 FAB 中设置:

代码语言:javascript复制
...
    ...
    app:fabSize="normal"
    app:borderWidth="0dp"
    android:layout_marginBottom="@dimen/fab_margin_bottom"
    android:layout_marginRight="@dimen/fab_margin_right"/>

Issues 3: FAB 中没有旋转动画

https://code.google.com/p/android/issues/detail?id=176116


With Third-Party FloatingActionButton

Using makovkastar/FloatingActionButton library makes floating buttons quite simple to setup. See the library readme and the sample code for reference.

在app/build.gradle:中添加依赖

代码语言:javascript复制
dependencies {
    compile 'com.melnykov:floatingactionbutton:1.2.0'
}

在布局中添加com.melnykov.fab.FloatingActionButton 。记得在根布局中属性中添加xmlns:fab

代码语言:javascript复制
"http://schemas.android.com/apk/res/android"
             xmlns:fab="http://schemas.android.com/apk/res-auto"
             android:layout_width="match_parent"
             android:layout_height="match_parent">

    "@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    "@ id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|right"
            android:layout_margin="16dp"
            android:src="@drawable/ic_action_content_new"
            fab:fab_type="normal"
            fab:fab_shadow="true"
            fab:fab_colorNormal="@color/primary"
            fab:fab_colorPressed="@color/primary_pressed"
            fab:fab_colorRipple="@color/ripple" />
</FrameLayout>

依附到list

接下来,我们可以选择将FAB和一个ListView, ScrollView 或者 RecyclerView 关联起来,这样按钮就会随着list的向下滚动而隐藏,向上滚动而重现:

代码语言:javascript复制
ListView listView = (ListView) findViewById(android.R.id.list);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.attachToListView(listView); // or attachToRecyclerView

我们可以使用fab.attachToRecyclerView(recyclerView)来依附到一个RecyclerView,或者使用fab.attachToScrollView(scrollView)来依附到一个ScrollView。

调整按钮类型

浮动操作按钮有两种大小:默认的,这应该是最常用的情况,以及mini的,这应该只用于衔接屏幕上的其他元素。

可以把FAB的按钮类型调整为“正常”或者“mini”

代码语言:javascript复制
...
    fab:fab_type="mini" />

FAB的显示和隐藏

代码语言:javascript复制
// 带动画的显示和隐藏
fab.show();
fab.hide();
// 不带动画的
fab.show(false);
fab.hide(false);

监听滚动事件

我们可以监听所关联的list的滚动事件,以管理FAB的状态:

代码语言:javascript复制
FloatingActionButton fab = (FloatingActionButton) root.findViewById(R.id.fab);
fab.attachToListView(list, new ScrollDirectionListener() {
    @Override
    public void onScrollDown() {
        Log.d("ListViewFragment", "onScrollDown()");
    }

    @Override
    public void onScrollUp() {
        Log.d("ListViewFragment", "onScrollUp()");
    }
}, new AbsListView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        Log.d("ListViewFragment", "onScrollStateChanged()");
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 
        int totalItemCount) {
        Log.d("ListViewFragment", "onScroll()");
    }
});

手动实现

除了使用库之外,我们还可以自己开发动操作按钮。关于手动实现浮动操作按钮,可以查看big nerd ranch guide 以及 survivingwithandroid walkthrough。

参考外文: Floating Action Buttons

0 人点赞