package com.melnykov.fab; import it.geosolutions.geocollect.android.app.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.StateListDrawable; import android.graphics.drawable.shapes.OvalShape; import android.os.Build; import android.util.AttributeSet; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.AbsListView; import android.widget.ImageButton; import android.widget.ScrollView; import com.nineoldandroids.view.ViewHelper; public class FloatingActionButton extends ImageButton { @Retention(RetentionPolicy.SOURCE) public @interface TYPE { } 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 int mScrollThreshold; 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.fab_size_normal : R.dimen.fab_size_mini); if (mShadow) { size += mShadowSize * 2; setMarginsWithoutShadow(); } setMeasuredDimension(size, size); } @SuppressLint("NewApi") private void init(Context context, AttributeSet attributeSet) { mVisible = true; mColorNormal = getColor(R.color.material_blue_500); mColorPressed = darkenColor(mColorNormal); mColorRipple = lightenColor(mColorNormal); mColorDisabled = getColor(android.R.color.darker_gray); mType = TYPE_NORMAL; mShadow = true; mScrollThreshold = getResources().getDimensionPixelOffset(R.dimen.fab_scroll_threshold); mShadowSize = getDimension(R.dimen.fab_shadow_size); 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.material_blue_500)); 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) { Drawable shadowDrawable = getResources().getDrawable(mType == TYPE_NORMAL ? R.drawable.fab_shadow : R.drawable.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; } } } @SuppressWarnings("deprecation") @SuppressLint("NewApi") private void setBackgroundCompat(Drawable drawable) { if (hasJellyBeanApi()) { setBackground(drawable); } else { setBackgroundDrawable(drawable); } } private int getMarginBottom() { int marginBottom = 0; final ViewGroup.LayoutParams layoutParams = getLayoutParams(); if (layoutParams instanceof ViewGroup.MarginLayoutParams) { marginBottom = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin; } return marginBottom; } public void setColorNormal(int color) { if (color != mColorNormal) { mColorNormal = color; updateBackground(); } } public void setColorNormalResId(int colorResId) { setColorNormal(getColor(colorResId)); } public int getColorNormal() { return mColorNormal; } public void setColorPressed(int color) { if (color != mColorPressed) { mColorPressed = color; updateBackground(); } } public void setColorPressedResId(int colorResId) { setColorPressed(getColor(colorResId)); } public int getColorPressed() { return mColorPressed; } public void setColorRipple(int color) { if (color != mColorRipple) { mColorRipple = color; updateBackground(); } } public void setColorRippleResId(int colorResId) { setColorRipple(getColor(colorResId)); } public int getColorRipple() { return mColorRipple; } public void setShadow(boolean shadow) { if (shadow != mShadow) { mShadow = shadow; updateBackground(); } } public boolean hasShadow() { return mShadow; } public void setType(@TYPE int type) { if (type != mType) { mType = type; updateBackground(); } } @TYPE public int getType() { return mType; } public boolean isVisible() { return mVisible; } public void show() { show(true); } public void hide() { hide(true); } public void show(boolean animate) { toggle(true, animate, false); } public void hide(boolean animate) { toggle(false, animate, false); } private void toggle(final boolean visible, final boolean animate, boolean force) { if (mVisible != visible || force) { mVisible = visible; int height = getHeight(); if (height == 0 && !force) { ViewTreeObserver vto = getViewTreeObserver(); if (vto.isAlive()) { vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { ViewTreeObserver currentVto = getViewTreeObserver(); if (currentVto.isAlive()) { currentVto.removeOnPreDrawListener(this); } toggle(visible, animate, true); return true; } }); return; } } int translationY = visible ? 0 : height + getMarginBottom(); ViewHelper.setTranslationY(this, translationY); // On pre-Honeycomb a translated view is still clickable, so we need to disable clicks manually if (!hasHoneycombApi()) { setClickable(visible); } } } public void attachToListView(AbsListView listView) { attachToListView(listView, null, null); } public void attachToListView(AbsListView listView, ScrollDirectionListener scrollDirectionListener) { attachToListView(listView, scrollDirectionListener, null); } public void attachToScrollView(ObservableScrollView scrollView) { attachToScrollView(scrollView, null, null); } public void attachToScrollView(ObservableScrollView scrollView, ScrollDirectionListener scrollDirectionListener) { attachToScrollView(scrollView, scrollDirectionListener, null); } public void attachToListView(AbsListView listView, ScrollDirectionListener scrollDirectionListener, AbsListView.OnScrollListener onScrollListener) { AbsListViewScrollDetectorImpl scrollDetector = new AbsListViewScrollDetectorImpl(); scrollDetector.setScrollDirectionListener(scrollDirectionListener); scrollDetector.setOnScrollListener(onScrollListener); scrollDetector.setListView(listView); scrollDetector.setScrollThreshold(mScrollThreshold); listView.setOnScrollListener(scrollDetector); } public void attachToScrollView(ObservableScrollView scrollView, ScrollDirectionListener scrollDirectionListener, ObservableScrollView.OnScrollChangedListener onScrollChangedListener) { ScrollViewScrollDetectorImpl scrollDetector = new ScrollViewScrollDetectorImpl(); scrollDetector.setScrollDirectionListener(scrollDirectionListener); scrollDetector.setOnScrollChangedListener(onScrollChangedListener); scrollDetector.setScrollThreshold(mScrollThreshold); scrollView.setOnScrollChangedListener(scrollDetector); } 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; } private static int darkenColor(int color) { float[] hsv = new float[3]; Color.colorToHSV(color, hsv); hsv[2] *= 0.9f; return Color.HSVToColor(hsv); } private static int lightenColor(int color) { float[] hsv = new float[3]; Color.colorToHSV(color, hsv); hsv[2] *= 1.1f; return Color.HSVToColor(hsv); } private class AbsListViewScrollDetectorImpl extends AbsListViewScrollDetector { private ScrollDirectionListener mScrollDirectionListener; private AbsListView.OnScrollListener mOnScrollListener; private void setScrollDirectionListener(ScrollDirectionListener scrollDirectionListener) { mScrollDirectionListener = scrollDirectionListener; } public void setOnScrollListener(AbsListView.OnScrollListener onScrollListener) { mOnScrollListener = onScrollListener; } @Override public void onScrollDown() { show(); if (mScrollDirectionListener != null) { mScrollDirectionListener.onScrollDown(); } } @Override public void onScrollUp() { hide(); if (mScrollDirectionListener != null) { mScrollDirectionListener.onScrollUp(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (mOnScrollListener != null) { mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } super.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (mOnScrollListener != null) { mOnScrollListener.onScrollStateChanged(view, scrollState); } super.onScrollStateChanged(view, scrollState); } } private class ScrollViewScrollDetectorImpl extends ScrollViewScrollDetector { private ScrollDirectionListener mScrollDirectionListener; private ObservableScrollView.OnScrollChangedListener mOnScrollChangedListener; private void setScrollDirectionListener(ScrollDirectionListener scrollDirectionListener) { mScrollDirectionListener = scrollDirectionListener; } public void setOnScrollChangedListener(ObservableScrollView.OnScrollChangedListener onScrollChangedListener) { mOnScrollChangedListener = onScrollChangedListener; } @Override public void onScrollDown() { show(); if (mScrollDirectionListener != null) { mScrollDirectionListener.onScrollDown(); } } @Override public void onScrollUp() { hide(); if (mScrollDirectionListener != null) { mScrollDirectionListener.onScrollUp(); } } @Override public void onScrollChanged(ScrollView who, int l, int t, int oldl, int oldt) { if (mOnScrollChangedListener != null) { mOnScrollChangedListener.onScrollChanged(who, l, t, oldl, oldt); } super.onScrollChanged(who, l, t, oldl, oldt); } } }