相信作为Android开发者,对FloatingActionButton肯定很熟悉,我们也经常看到它的身影,比如短信页面,电话页面等等。那么我们应该怎么引用悬浮按钮呢?下面就来分析分析FloatingActionButton的使用。
1.先导入support依赖库
由于FloatingActionButton是在开源库里面的,所以我们要先导入开源库。
(1)先配置build.gradle
gradle版本至少是gradle:1.2.3;
Sdk版本22以上
classpath 'com.android.tools.build:gradle:1.3.0'
compileSdkVersion 23
buildToolsVersion "22.0.1"
targetSdkVersion 23
然后导入依赖库:
compile 'com.android.support:design:23.0.1'
compile 'com.android.support:appcompat-v7:23.0.1'
导库的时候要的版本号为22.2.0以上,不然可能会报错
(2)在style.xml中主题样式需要继承“Theme.AppCompat[…]”
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
不然会报错:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.meizu.floatactionbuttontest/com.meizu.floatactionbuttontest.MainActivity}: android.view.InflateException: Binary XML file line #7: Error inflating class android.support.design.widget.FloatingActionButton
2.控件的具体用法
1.使用android5.0原生自带的FloatActionButton
<android.support.design.widget.FloatingActionButton
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
fab:elevation="6dp"
fab:pressedTranslationZ="12dp"
fab:fabSize="normal"
android:src="@android:drawable/ic_menu_camera" />
(1)先介绍默认(效果图中绿色的):
下面效果图中绿色的那个FloatActionButton是Android默认自带的,从上面的效果图中我们可以看到FloatingActionButton正常显示的情况下有个填充的颜色,有个阴影;点击的时候会有一个rippleColor,并且阴影的范围可以增大。在布局文件中我
只添加了一个图标,一个elevation(这样就可以显示阴影)和一个pressedTranslationZ(这样在它被按下的时候阴影就会增大)。
(2)可定制的属性:
这个填充色以及rippleColor如何自定义呢?
FAB会采用主题中定义的accent color,同时采用colorControlHighlight作为ripple color。不过这两个都是可以自定义的。默认的颜色取的是,theme中的colorAccent,rippleColor默认取的是theme中的colorControlHighlight。
两种方法自定义这两个颜色:
(1)在布局文件中引用自定义颜色值:
xmlns:fab="http://schemas.android.com/apk/res-auto"
fab:backgroundTint="#ff87ffeb"
fab:rippleColor="#33728dff"
(2)可以在style中定义colorAccent
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primaryDark</item>
<item name="android:navigationBarColor">@color/primary</item>
<item name="colorAccent">@color/primary</item>
</style>
</resources>
当然也可以在代码中调用相关方法,不过backgroundTint稍微麻烦点,因为它使用的是state list。
mFbutton.setRippleColor(Color.GRAY);
fab.setBackgroundTintList(ColorStateList.valueOf("Your color"));
上面的方式都可以改变FAB的颜色
具体属性详细介绍
fab:backgroundTint="#ff87ffeb"
fab:rippleColor="#33728dff"
这两个属性上面已经介绍过,就是改变FAB的颜色和点击是水波纹的颜色
fab:elevation="6dp"
fab:pressedTranslationZ="12dp"
这两个属性是FAB的阴影和轮廓的设置
fab:fabSize="mini"
fabSize默认是normal版本。
这个属性是设置FAB的大小,有两种大小的浮动操作按钮,mini版和normal版,效果图中右上的(比较小的那个)FAB就是通过这几个属性设置后的效果。
特别需要注意的事项
首先,根据安卓系统版本的不同,会出现很多未料到的事情。这些问题似乎都和一个叫做 borderWidth的属性有关, borderWidth需要设置成0:
fab:borderWidth="0dp"
另外,在lollipop以前的版本上,使用这个属性会在周围产生外边距(margin),而21+以上则不会。产生的原因和CardView上所看到的是一样的:lollipop 以前阴影是使用一个drawable来渲染的,使用的是自身的空间来绘制,而Lollipop之后阴影是由系统渲染的。CardView有一个属性可以启 用compat padding。但是浮动操作按钮却没有,所以根据安卓版本添加一个margin就可以了。
android:layout_margin="@dimen/fab_compat_margin"
现在只需在dimen.xml文件中定义一个属性值。
values目录下:
<dimen name="fab_compat_margin">0dp</dimen>
values-v21目录下:
<dimen name="fab_compat_margin">16dp</dimen>
2.自定义FloatActionButton控件
(1)在Attr.xml中自定义属性:
<resources>
<declare-styleable name="FloatingActionButton">
<attr name="fab_colorPressed" format="color" />
<attr name="fab_colorNormal" format="color" />
<attr name="fab_colorRipple" format="color" />
<attr name="fab_colorDisabled" format="color" />
<attr name="fab_shadow" format="boolean" />
<attr name="fab_type" format="enum">
<enum name="normal" value="0" />
<enum name="mini" value="1" />
</attr>
</declare-styleable>
</resources>
(2)控件的代码(核心部分)
public class FloatingActionButton extends ImageButton {
public static final int TYPE_NORMAL = 0;
public static final int TYPE_MINI = 1;
private boolean mVisible;
private int mColorNormal;
private int mColorPressed;
private int mColorRipple;
private int mColorDisabled;
private boolean mShadow;
private int mType;
private int mShadowSize;
private boolean mMarginsSet;
public FloatingActionButton(Context context) {
this(context, null);
}
public FloatingActionButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public FloatingActionButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int size = getDimension(
mType == TYPE_NORMAL ? R.dimen.mz_fab_size_normal : R.dimen.mz_fab_size_mini);
if (mShadow && !hasLollipopApi()) {
size += mShadowSize * 2;
setMarginsWithoutShadow();
}
setMeasuredDimension(size, size);
}
private void init(Context context, AttributeSet attributeSet) {
mVisible = true;
mColorNormal = getColor(R.color.mz_color_normal);
mColorPressed = darkenColor(mColorNormal);
mColorRipple = lightenColor(mColorNormal);
mColorDisabled = getColor(android.R.color.darker_gray);
mType = TYPE_NORMAL;
mShadow = true;
mShadowSize = getDimension(R.dimen.mz_fab_shadow_size);
if (hasLollipopApi()) {
StateListAnimator stateListAnimator = AnimatorInflater.loadStateListAnimator(context,
R.anim.fab_press_elevation);
setStateListAnimator(stateListAnimator);
}
if (attributeSet != null) {
initAttributes(context, attributeSet);
}
updateBackground();
}
private void initAttributes(Context context, AttributeSet attributeSet) {
TypedArray attr = getTypedArray(context, attributeSet, R.styleable.FloatingActionButton);
if (attr != null) {
try {
mColorNormal = attr.getColor(R.styleable.FloatingActionButton_fab_colorNormal,
getColor(R.color.color_normal));
mColorPressed = attr.getColor(R.styleable.FloatingActionButton_fab_colorPressed,
darkenColor(mColorNormal));
mColorRipple = attr.getColor(R.styleable.FloatingActionButton_fab_colorRipple,
lightenColor(mColorNormal));
mColorDisabled = attr.getColor(R.styleable.FloatingActionButton_fab_colorDisabled,
mColorDisabled);
mShadow = attr.getBoolean(R.styleable.FloatingActionButton_fab_shadow, true);
mType = attr.getInt(R.styleable.FloatingActionButton_fab_type, TYPE_NORMAL);
} finally {
attr.recycle();
}
}
}
/**
*定义了不同状态值下与之对应的颜色资源
*/
private void updateBackground() {
StateListDrawable drawable = new StateListDrawable();
drawable.addState(new int[]{android.R.attr.state_pressed}, createDrawable(mColorPressed));
drawable.addState(new int[]{-android.R.attr.state_enabled}, createDrawable(mColorDisabled));
drawable.addState(new int[]{}, createDrawable(mColorNormal));
setBackgroundCompat(drawable);
}
private Drawable createDrawable(int color) {
OvalShape ovalShape = new OvalShape();
ShapeDrawable shapeDrawable = new ShapeDrawable(ovalShape);
shapeDrawable.getPaint().setColor(color);
if (mShadow && !hasLollipopApi()) {
Drawable shadowDrawable = getResources().getDrawable(mType == TYPE_NORMAL ? R.mipmap.fab_shadow
: R.mipmap.fab_shadow_mini);
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{shadowDrawable, shapeDrawable});
layerDrawable.setLayerInset(1, mShadowSize, mShadowSize, mShadowSize, mShadowSize);
return layerDrawable;
} else {
return shapeDrawable;
}
}
private TypedArray getTypedArray(Context context, AttributeSet attributeSet, int[] attr) {
return context.obtainStyledAttributes(attributeSet, attr, 0, 0);
}
private int getColor(int id) {
return getResources().getColor(id);
}
private int getDimension( int id) {
return getResources().getDimensionPixelSize(id);
}
private void setMarginsWithoutShadow() {
if (!mMarginsSet) {
if (getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
int leftMargin = layoutParams.leftMargin - mShadowSize;
int topMargin = layoutParams.topMargin - mShadowSize;
int rightMargin = layoutParams.rightMargin - mShadowSize;
int bottomMargin = layoutParams.bottomMargin - mShadowSize;
layoutParams.setMargins(leftMargin, topMargin, rightMargin, bottomMargin);
requestLayout();
mMarginsSet = true;
}
}
}
/**
* 设置背景
* @param drawable
*/
private void setBackgroundCompat(Drawable drawable) {
if (hasLollipopApi()) {
float elevation;
if (mShadow) {
elevation = getElevation() > 0.0f ? getElevation()
: getDimension(R.dimen.mz_fab_elevation_lollipop);
} else {
elevation = 0.0f;
}
setElevation(elevation);
RippleDrawable rippleDrawable = new RippleDrawable(new ColorStateList(new int[][]{{}},
new int[]{mColorRipple}), drawable, null);
setOutlineProvider(new ViewOutlineProvider() { //设置轮廓
@Override
public void getOutline(View view, Outline outline) {
int size = getDimension(mType == TYPE_NORMAL ? R.dimen.fab_size_normal
: R.dimen.fab_size_mini);
outline.setOval(0, 0, size, size);
}
});
setClipToOutline(true);
setBackground(rippleDrawable);
} else if (hasJellyBeanApi()) {
setBackground(drawable);
} else {
setBackgroundDrawable(drawable);
}
}
/**
* 设置正常状态下的颜色值
* @param color
*/
public void setColorNormal(int color) {
if (color != mColorNormal) {
mColorNormal = color;
updateBackground();
}
}
public void setColorNormalResId(int colorResId) {
setColorNormal(getColor(colorResId));
}
public int getColorNormal() {
return mColorNormal;
}
/**
* 设置按下的颜色值
* @param color
*/
public void setColorPressed(int color) {
if (color != mColorPressed) {
mColorPressed = color;
updateBackground();
}
}
public void setColorPressedResId(int colorResId) {
setColorPressed(getColor(colorResId));
}
public int getColorPressed() {
return mColorPressed;
}
/**
* 设置水波纹的颜色
* @param color
*/
public void setColorRipple(int color) {
if (color != mColorRipple) {
mColorRipple = color;
updateBackground();
}
}
public void setColorRippleResId(int colorResId) {
setColorRipple(getColor(colorResId));
}
public int getColorRipple() {
return mColorRipple;
}
/**
* 设置是否有阴影
* @param shadow
*/
public void setShadow(boolean shadow) {
if (shadow != mShadow) {
mShadow = shadow;
updateBackground();
}
}
public boolean hasShadow() {
return mShadow;
}
/**
* 设置浮动操作按钮的版本,有NORMAL,MINI两种
* @param type
*/
public void setType(int type) {
if (type != mType) {
mType = type;
updateBackground();
}
}
public int getType() {
return mType;
}
public boolean isVisible() {
return mVisible;
}
private boolean hasLollipopApi() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
private boolean hasJellyBeanApi() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
}
private boolean hasHoneycombApi() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
}
/**
* 调色,亮度调低
* @param color
* @return
*/
private static int darkenColor(int color) {
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[2] *= 0.9f;
return Color.HSVToColor(hsv);
}
/**
* 调色,亮度调高
* @param color
* @return
*/
private static int lightenColor(int color) {
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[2] *= 1.1f;
return Color.HSVToColor(hsv);
}
}