FloatingActionButton简称FAB。
一. 对于App或某个页面中是否要使用FloatingActionButton必要性:
FAB代表一个App或一个页面中最主要的操作,如果一个App的每个页面都有FAB,则通常表示该App最主要的功能是通过该FAB操作的。
为了突出FAB的重要性,一个页面最好只有一个FAB。
二. FloatingActionButton大小
通常有两种尺寸
1. 56 * 56dp :默认的大小,最常用的尺寸。
2. 40 * 40 dp :Mini版。
当然也可以改它的大小。
FAB中间的图标,google推荐的大小是:24 * 24dp
三. 哪些操作推荐使用FloatingActionButton
如: 添加,收藏,编辑,分享,导航等,如下图:
而如:删除,警告,错误,剪切等操作,则不推荐使用FloatingActionButton。
四. FAB的使用
Android 5.0 中引入Material Design,FAB为Android Design Support Library支持包中的中Material Design控件,要使用FAB,先要引入Design Support Library包,如:在build.gradle中加入: compile 'com.android.support:design:X.X.X',X为支持包版本号,如下图:
FAB的继承关系:
FAB的XMl属性除了继承自View,ImageView的属性外,Android 5.0后引入的常用新属性如下:
注:使用下面属性,先要引入命名空间:xmlns:app="http://schemas.android.com/apk/res-auto"
1> app:elevation="50dp":阴影的高度,elevation是Android 5.0中引入的新属性,设置该属性使控件有一个阴影,感觉该控件像是“浮”起来一样,这样达到3D效果。对应的方法:setCompatElevation(float)
2> app:fabSize="normal":FAB的大小,为normal时,大小为:56 * 56dp ,为mini时,大小为: 40 * 40 dp。
3> app:backgroundTint="#31bfcf":FAB的背景颜色。
4> app:rippleColor="#e7d16b":点击FAB时,形成的波纹颜色。
<一> 使用例子1: 简单使用FAB
点击“+”悬浮按钮,弹出悬浮按钮菜单,效果如下:
代码如下:
XMl布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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"
>
<CheckBox
android:id="@+id/cbDelay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="delay"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
/>
<RelativeLayout
android:id="@+id/rlAddBill"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000"
android:visibility="gone"
>
<LinearLayout
android:id="@+id/ll01"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="100dp"
android:orientation="horizontal"
>
<TextView
android:layout_toLeftOf="@+id/miniFab01"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="销售单"
android:textSize="15sp"
android:textColor="@android:color/white"
android:layout_gravity="center_vertical"
android:gravity="right"
android:paddingBottom="5dp"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/miniFab01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_list_white_24dp"
android:layout_marginRight="25dp"
app:fabSize="mini"
app:elevation="5dp"
app:backgroundTint="@color/color_XSD"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/ll02"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_above="@+id/ll01"
>
<TextView
android:layout_toLeftOf="@+id/miniFab02"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="销售退货"
android:textSize="15sp"
android:textColor="@android:color/white"
android:layout_gravity="center_vertical"
android:gravity="right"
android:paddingBottom="5dp"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/miniFab02"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_list_white_24dp"
android:layout_marginRight="25dp"
app:fabSize="mini"
app:elevation="5dp"
app:backgroundTint="@color/color_XSTH"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/ll03"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_above="@+id/ll02"
>
<TextView
android:layout_toLeftOf="@+id/miniFab02"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="收款单"
android:textSize="15sp"
android:textColor="@android:color/white"
android:layout_gravity="center_vertical"
android:gravity="right"
android:paddingBottom="5dp"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/miniFab03"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_list_white_24dp"
android:layout_marginRight="25dp"
app:fabSize="mini"
app:elevation="5dp"
app:backgroundTint="@color/color_SKD"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/ll04"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_above="@+id/ll03"
>
<TextView
android:layout_toLeftOf="@+id/miniFab02"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="进货单"
android:textSize="15sp"
android:textColor="@android:color/white"
android:layout_gravity="center_vertical"
android:gravity="right"
android:paddingBottom="5dp"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/miniFab04"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_list_white_24dp"
android:layout_marginRight="25dp"
app:fabSize="mini"
app:elevation="5dp"
app:backgroundTint="@color/color_JHD"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/ll05"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_above="@+id/ll04"
>
<TextView
android:layout_toLeftOf="@+id/miniFab02"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="采购退货"
android:textSize="15sp"
android:textColor="@android:color/white"
android:layout_gravity="center_vertical"
android:gravity="right"
android:paddingBottom="5dp"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/miniFab05"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_list_white_24dp"
android:layout_marginRight="25dp"
app:fabSize="mini"
app:elevation="5dp"
app:backgroundTint="@color/color_CGTH"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/ll06"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_above="@+id/ll05"
>
<TextView
android:layout_toLeftOf="@+id/miniFab02"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="付款单"
android:textSize="15sp"
android:textColor="@android:color/white"
android:layout_gravity="center_vertical"
android:gravity="right"
android:paddingBottom="5dp"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/miniFab06"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_list_white_24dp"
android:layout_marginRight="25dp"
app:fabSize="mini"
app:elevation="5dp"
app:backgroundTint="@color/color_FKD"
/>
</LinearLayout>
</RelativeLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab01Add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:src="@mipmap/ic_add_white_24dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="20dp"
app:fabSize="normal"
app:elevation="5dp"
app:backgroundTint="#31bfcf"
app:rippleColor="#e7d161"
/>
</RelativeLayout>
Java代码:
package com.zst.floatactionbutton;
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
/**
* Created by shengtao_zeng on 2016/11/10.
*/
public class BasicFABActivity extends AppCompatActivity implements View.OnClickListener{
private CheckBox cbDelay;
private FloatingActionButton fab01Add;
private boolean isAdd = false;
private RelativeLayout rlAddBill;
private int[] llId = new int[]{R.id.ll01,R.id.ll02,R.id.ll03,R.id.ll04,R.id.ll05,R.id.ll06};
private LinearLayout[] ll = new LinearLayout[llId.length];
private int[] fabId = new int[]{R.id.miniFab01,R.id.miniFab02,R.id.miniFab03,R.id.miniFab04,R.id.miniFab05,R.id.miniFab06};
private FloatingActionButton[] fab = new FloatingActionButton[fabId.length];
private AnimatorSet addBillTranslate1;
private AnimatorSet addBillTranslate2;
private AnimatorSet addBillTranslate3;
private AnimatorSet addBillTranslate4;
private AnimatorSet addBillTranslate5;
private AnimatorSet addBillTranslate6;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.basic_fab_activity);
initView();
setDefaultValues();
bindEvents();
}
private void initView(){
cbDelay = (CheckBox)findViewById(R.id.cbDelay);
fab01Add = (FloatingActionButton)findViewById(R.id.fab01Add);
rlAddBill = (RelativeLayout)findViewById(R.id.rlAddBill);
for (int i = 0; i < llId.length;i++){
ll[i] = (LinearLayout)findViewById(llId[i]);
}
for (int i = 0;i < fabId.length; i++){
fab[i] = (FloatingActionButton)findViewById(fabId[i]);
}
}
private void setDefaultValues(){
addBillTranslate1 = (AnimatorSet) AnimatorInflater.loadAnimator(this,R.animator.add_bill_anim);
addBillTranslate2 = (AnimatorSet) AnimatorInflater.loadAnimator(this,R.animator.add_bill_anim);
addBillTranslate3 = (AnimatorSet) AnimatorInflater.loadAnimator(this,R.animator.add_bill_anim);
addBillTranslate4 = (AnimatorSet) AnimatorInflater.loadAnimator(this,R.animator.add_bill_anim);
addBillTranslate5 = (AnimatorSet) AnimatorInflater.loadAnimator(this,R.animator.add_bill_anim);
addBillTranslate6 = (AnimatorSet) AnimatorInflater.loadAnimator(this,R.animator.add_bill_anim);
}
private void bindEvents(){
fab01Add.setOnClickListener(this);
for (int i = 0;i < fabId.length; i++){
fab[i].setOnClickListener(this);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.fab01Add:
fab01Add.setImageResource(isAdd ? R.mipmap.ic_add_white_24dp:R.mipmap.ic_close_white_24dp);
isAdd = !isAdd;
rlAddBill.setVisibility(isAdd ? View.VISIBLE : View.GONE);
if (isAdd) {
addBillTranslate1.setTarget(ll[0]);
addBillTranslate1.start();
addBillTranslate2.setTarget(ll[1]);
addBillTranslate2.setStartDelay(cbDelay.isChecked() ? 150 : 0);
addBillTranslate2.start();
addBillTranslate3.setTarget(ll[2]);
addBillTranslate3.setStartDelay(cbDelay.isChecked() ? 200 : 0);
addBillTranslate3.start();
addBillTranslate4.setTarget(ll[3]);
addBillTranslate4.setStartDelay(cbDelay.isChecked() ? 250 : 0);
addBillTranslate4.start();
addBillTranslate5.setTarget(ll[4]);
addBillTranslate5.setStartDelay(cbDelay.isChecked() ? 300 : 0);
addBillTranslate5.start();
addBillTranslate6.setTarget(ll[5]);
addBillTranslate6.setStartDelay(cbDelay.isChecked() ? 350 : 0);
addBillTranslate6.start();
}
break;
case R.id.miniFab01:
hideFABMenu();
break;
case R.id.miniFab02:
hideFABMenu();
break;
case R.id.miniFab03:
hideFABMenu();
break;
case R.id.miniFab04:
hideFABMenu();
break;
case R.id.miniFab05:
hideFABMenu();
break;
case R.id.miniFab06:
hideFABMenu();
break;
default:
break;
}
}
private void hideFABMenu(){
rlAddBill.setVisibility(View.GONE);
fab01Add.setImageResource(R.mipmap.ic_add_white_24dp);
isAdd = false;
}
}
Property Animation定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially"
>
<set
android:ordering="together"
>
<objectAnimator
android:propertyName="translationY"
android:valueFrom="100.00"
android:valueTo="-50.00"
android:duration="100"
android:startOffset="0"
android:repeatCount="0"
android:valueType="floatType"
/>
<objectAnimator
android:propertyName="alpha"
android:valueFrom="0.00"
android:valueTo="1.00"
android:duration="100"
android:startOffset="0"
android:valueType="floatType"
android:repeatCount="0"
/>
<objectAnimator
android:propertyName="scaleY"
android:valueFrom="0.00"
android:valueTo="1.50"
android:duration="100"
android:startOffset="0"
android:valueType="floatType"
android:repeatCount="0"
/>
</set>
<set
android:ordering="together"
>
<objectAnimator
android:propertyName="translationY"
android:valueTo="0.00"
android:duration="100"
android:startOffset="0"
android:repeatCount="0"
android:valueType="floatType"
/>
<objectAnimator
android:propertyName="scaleY"
android:valueTo="1.0"
android:duration="100"
android:startOffset="0"
android:valueType="floatType"
android:repeatCount="0"
/>
</set>
</set>
<二> 使用例子2: FAB和ListView 的结合使用
效果如下:
a> ListView向下滚动时,添加联系人的悬浮按钮隐藏。
b> ListView向上滚动时,添加联系人的悬浮按钮显示。
c> ListView向下滚动1/4时,会显示“回到顶部”的悬浮按钮,点击该按钮,ListView显示的顶部Item返回到第一个Item。
ListView滚动和FAB的隐藏/显示的关系逻辑如下图:
代码如下:
XML布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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"
>
<ListView
android:id="@+id/lvContacts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#80000000"
android:dividerHeight="1px"
>
</ListView>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fabAddContact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginRight="8dp"
android:src="@mipmap/ic_person_add_white_24dp"
app:fabSize="normal"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fabUp"
android:layout_above="@+id/fabAddContact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginRight="20dp"
android:src="@mipmap/ic_keyboard_arrow_up_white_24dp"
app:fabSize="mini"
android:layout_alignParentRight="true"
app:backgroundTint="#92be0a"
android:visibility="gone"
/>
</RelativeLayout>
主逻辑代码:
package com.zst.floatactionbutton;
import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List; public class FABInListViewActivity extends AppCompatActivity implements View.OnClickListener{
private ListView lvContacts;
private ContactsAdapter adapter;
private List<String> list = new ArrayList<>();
private long mNumber = 13812007799L;
private FloatingActionButton fabAddContact;
private FloatingActionButton fabUp;
private AnimatorSet mHideFAB;
private AnimatorSet mShowFAB;
private boolean FAB_VISIBLE = true;
private int mPreviousFirstVisibleItem; //记录前面第一个Item
private int mLastScrollY; //记录ListView中最上面的Item(View)的上一次顶部Y坐标()
private int mScrollThreshold = 2; //阈值:单位px
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fab_in_listview_activity);
initView();
setDefaultValues();
bindEvents();
}
private void initView(){
lvContacts = (ListView)findViewById(R.id.lvContacts);
fabAddContact = (FloatingActionButton)findViewById(R.id.fabAddContact);
fabUp = (FloatingActionButton)findViewById(R.id.fabUp);
}
private void setDefaultValues(){
for (int i = 0; i < 100;i++){
list.add("Contact" + i + "+" + (mNumber + i));
}
adapter = new ContactsAdapter(this,list);
lvContacts.setAdapter(adapter);
initAnimation();
}
private void initAnimation(){
mHideFAB = (AnimatorSet)AnimatorInflater.loadAnimator(this,R.animator.scroll_hide_fab);
mShowFAB = (AnimatorSet)AnimatorInflater.loadAnimator(this,R.animator.scroll_show_fab);
mHideFAB.setTarget(fabAddContact);
mShowFAB.setTarget(fabAddContact);
}
private void bindEvents(){
fabUp.setOnClickListener(this);
lvContacts.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { //停止滚动
//showFAB();
// }
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if(totalItemCount == 0) {
showFAB();
return;
}
//滚动过程中:ListView中最上面一个Item还是同一个Item
if(isSameRow(firstVisibleItem)) {
int newScrollY = getTopItemScrollY();
boolean isExceedThreshold = Math.abs(mLastScrollY - newScrollY) > mScrollThreshold;
if (isExceedThreshold) {
if (mLastScrollY > newScrollY && FAB_VISIBLE == true) {
FAB_VISIBLE = false;
hideFAB();
} else if(mLastScrollY < newScrollY && FAB_VISIBLE == false){
FAB_VISIBLE = true;
showFAB();
}
}
mLastScrollY = newScrollY;
} else {
if (firstVisibleItem > mPreviousFirstVisibleItem && FAB_VISIBLE == true){ //向下滚动
FAB_VISIBLE = false;
hideFAB();
} else if(firstVisibleItem < mPreviousFirstVisibleItem && FAB_VISIBLE == false){ //向上滚动
FAB_VISIBLE = true;
showFAB();
}
mLastScrollY = getTopItemScrollY();
mPreviousFirstVisibleItem = firstVisibleItem;
}
if (firstVisibleItem > (totalItemCount/4)) {
fabUp.setVisibility(View.VISIBLE);
} else {
fabUp.setVisibility(View.GONE);
}
}
});
}
private boolean isSameRow(int firstVisisbleItem){
return mPreviousFirstVisibleItem == firstVisisbleItem;
}
/**
* 滚动过程中,获得当前ListView中最上面的Item(View)的顶部的Y坐标(以px为单位)
* @return
*/
private int getTopItemScrollY() {
if (lvContacts == null || lvContacts.getChildAt(0) == null) return 0;
View topChild = lvContacts.getChildAt(0);
return topChild.getTop();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.fabUp:
lvContacts.setSelection(0);
fabUp.setVisibility(View.GONE);
break;
default:
break;
}
}
private void hideFAB() {
mHideFAB.start();
}
private void showFAB(){
mShowFAB.start();
}
class ContactsAdapter extends BaseAdapter {
private Context mContext;
private List<String> list;
public ContactsAdapter(Context mContext,List<String> list) {
this.mContext = mContext;
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.contacts_item,null);
TextView tvName = (TextView)convertView.findViewById(R.id.tvName);
TextView tvNumber = (TextView)convertView.findViewById(R.id.tvNumber);
tvName.setText((list.get(position).split("\\+"))[0]);
tvNumber.setText((list.get(position).split("\\+"))[1]);
return convertView;
}
}
}
Property Animation定义如下:
scroll_hide_fab.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together"
>
<objectAnimator
android:propertyName="translationY"
android:valueTo="500.00"
android:duration="200"
android:startOffset="0"
android:repeatCount="0"
android:valueType="floatType"
/>
<objectAnimator
android:propertyName="alpha"
android:valueFrom="1.00"
android:valueTo="0.00"
android:duration="200"
android:startOffset="0"
android:valueType="floatType"
android:repeatCount="0"
/>
</set>
scroll_show_fab.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together"
>
<objectAnimator
android:propertyName="translationY"
android:valueTo="0.0"
android:duration="100"
android:startOffset="0"
android:repeatCount="0"
android:valueType="floatType"
/>
<objectAnimator
android:propertyName="alpha"
android:valueFrom="0.00"
android:valueTo="1.00"
android:duration="100"
android:startOffset="0"
android:valueType="floatType"
android:repeatCount="0"
/>
</set>