diff --git a/.gitignore b/.gitignore index 2f1edcfe..cf7155c0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,7 @@ .DS_Store /build +gen + .idea *.iml \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt index d6456956..75d516c1 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2014 Jerzy Chalupski Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 840b237d..5d3404b4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Features ![Demo](screenshots/buttons.png) -* Customizable background colors for normal and pressed states (only solid colors supported) and icon drawable. +* Customizable background colors for normal and pressed states and icon drawable. ![Demo](screenshots/custom.png) @@ -17,22 +17,17 @@ Features ![Demo](screenshots/menu.gif) -Usage -===== -The library will be soon available on Maven Central, but for now you have to build it and install it in your local Maven repo: +* Optional labels for buttons in `FloatingActionsMenu`. -``` -git clone https://github.com/futuresimple/android-floating-action-button -cd android-floating-action-button -cd library -gradle installArchives -``` + ![Demo](screenshots/labels.png) -And add the following configuration to your `build.gradle`: +Usage +===== +Just add the dependency to your `build.gradle`: ```groovy dependencies { - compile 'com.getbase:floatingactionbutton:1.0.0' + compile 'com.getbase:floatingactionbutton:1.8.0' } ``` @@ -44,6 +39,10 @@ The API is **extremely** limited at the moment. It solves few select use cases i Unlike some other FloatingActionButton libraries this library doesn't implement "quick return" pattern, i.e. hiding the button on scrolling down and showing it on scrolling up. That's intentional, I think that should be responsibility of another component, not the button itself. +This library is `minSdkVersion=14` and if that changes, the version number will be increased, not decreased. It means that Honeycomb, Gingerbread or - gods forbid - Froyo, won't ever be supported. I won't even consider merging pull requests fully implementing support for older versions. We need to move on as Android community and focus on delivering value for 95% of users of modern Android OS instead of jumping through burning hoops to support ancient devices with ancient OS. + +If you **really** require support for older Android versions, [str4d](https://github.com/str4d) maintains [a version of this library with `minSdkVersion=4`](https://github.com/str4d/android-floating-action-button). + Credits ======= I used [FloatingActionButton](https://github.com/makovkastar/FloatingActionButton) library by [Oleksandr Melnykov](https://github.com/makovkastar) as a base for development. @@ -63,4 +62,4 @@ License distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/build.gradle b/build.gradle index 9b8abe4f..8310d20c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,19 +1,19 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:0.12.2' + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.0.0' - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } } allprojects { - repositories { - jcenter() - } + repositories { + jcenter() + } } diff --git a/changelog.md b/changelog.md new file mode 100644 index 00000000..012fce31 --- /dev/null +++ b/changelog.md @@ -0,0 +1,73 @@ +Change Log +========== + +Version 1.8.0 *(2015-02-18)* +---------------------------- + + * Added `fab_labelsPosition` attribute + * Fixed issue with labels style being overridden by theme style + +Version 1.7.0 *(2015-02-01)* +---------------------------- + + * Added removeButton API to FloatingActionsMenu + * Fixed NPEs related to incorrect animators initialization + * Fixed labels positions for FloatingActionsMenu with mini add button + +Version 1.6.0 *(2015-01-16)* +---------------------------- + + * Added disabled state for FloatingActionButton background + * Added button size attribute for FloatingActionsMenu add button + +Version 1.5.1 *(2014-12-30)* +---------------------------- + + * Fixed setting FloatingActionButton icon with `fab_icon` XML attribute + * Fixed issues with changing visibility of FloatingActionButtons inside FloatingActionsMenu + + +Version 1.5.0 *(2014-12-30)* +---------------------------- + + * Added icon drawable setter for FloatingActionButton + * Better looking strokes of FloatingActionButtons + * Option to disable strokes of FloatingActionButton + +Version 1.4.0 *(2014-12-21)* +---------------------------- + + * Added labels setter for FloatingActionButton + * Reduce memory footprint of FloatingActionButton and fix OOM issues. + +Version 1.3.0 *(2014-12-08)* +---------------------------- + + * Added support for labels in FloatingActionsMenu expanding in vertical direction. + +Version 1.2.1 *(2014-12-04)* +---------------------------- + + * Fixed IDE error when using `setSize(FloatingActionButton.SIZE_FOO)` setter. + +Version 1.2.0 *(2014-12-02)* +---------------------------- + + * Fixed horizontal alignment of mini FloatingActionButtons in FloatingActionsMenu + * Added support for different FloatingActionsMenu expand directions + * Support transparent buttons + * Added xxxhdpi drawables + * Added consumer proguard configuration to library + * Added getters and setters for FloatingActionButton properties + * Exposed FloatingActionsMenu isExpanded property + +Version 1.1.0 *(2014-10-23)* +---------------------------- + + * Fixed issue with broken animation state after screen rotation + * Fixed name clash with appcompat v21. All FloatingActionButton attributes are now prefixed with 'fab_' prefix + +Version 1.0.0 *(2014-09-19)* +---------------------------- + + * Initial release. diff --git a/gradle.properties b/gradle.properties index 351be31e..b6591e5a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,8 +17,8 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -VERSION_NAME=1.0.0 -VERSION_CODE=1 +VERSION_NAME=1.8.0 +VERSION_CODE=11 GROUP=com.getbase POM_DESCRIPTION=Floating Action Button for Android based on Material Design specification diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1e61d1fd..c823c582 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Thu Nov 27 11:18:21 CET 2014 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip diff --git a/library/build.gradle b/library/build.gradle index 58d19bce..b07f6bd2 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,26 +1,26 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 19 - buildToolsVersion "19.1.0" + compileSdkVersion 19 + buildToolsVersion "21.1.2" - defaultConfig { - applicationId "com.getbase.floatingactionbutton" - minSdkVersion 14 - targetSdkVersion 19 - versionName project.VERSION_NAME - versionCode Integer.parseInt(project.VERSION_CODE) - } - buildTypes { - release { - runProguard false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } + defaultConfig { + minSdkVersion 14 + targetSdkVersion 19 + versionName project.VERSION_NAME + versionCode Integer.parseInt(project.VERSION_CODE) + consumerProguardFiles 'consumer-proguard-rules.pro' + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + } } dependencies { - compile 'com.android.support:support-annotations:19.1.0' + compile 'com.android.support:support-annotations:19.1.0' } apply from: './gradle-mvn-push.gradle' diff --git a/library/consumer-proguard-rules.pro b/library/consumer-proguard-rules.pro new file mode 100644 index 00000000..b5429595 --- /dev/null +++ b/library/consumer-proguard-rules.pro @@ -0,0 +1,5 @@ +# keep getters/setters in RotatingDrawable so that animations can still work. +-keepclassmembers class com.getbase.floatingactionbutton.FloatingActionsMenu$RotatingDrawable { + void set*(***); + *** get*(); +} diff --git a/library/src/main/java/com/getbase/floatingactionbutton/AddFloatingActionButton.java b/library/src/main/java/com/getbase/floatingactionbutton/AddFloatingActionButton.java index e151b96d..42f9787b 100644 --- a/library/src/main/java/com/getbase/floatingactionbutton/AddFloatingActionButton.java +++ b/library/src/main/java/com/getbase/floatingactionbutton/AddFloatingActionButton.java @@ -8,6 +8,8 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.Shape; +import android.support.annotation.ColorRes; +import android.support.annotation.DrawableRes; import android.util.AttributeSet; public class AddFloatingActionButton extends FloatingActionButton { @@ -27,22 +29,36 @@ public AddFloatingActionButton(Context context, AttributeSet attrs, int defStyle @Override void init(Context context, AttributeSet attributeSet) { - if (attributeSet != null) { - TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.AddFloatingActionButton, 0, 0); - if (attr != null) { - try { - mPlusColor = attr.getColor(R.styleable.AddFloatingActionButton_plusIconColor, getColor(android.R.color.white)); - } finally { - attr.recycle(); - } - } - } else { - mPlusColor = getColor(android.R.color.white); - } + TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.AddFloatingActionButton, 0, 0); + mPlusColor = attr.getColor(R.styleable.AddFloatingActionButton_fab_plusIconColor, getColor(android.R.color.white)); + attr.recycle(); super.init(context, attributeSet); } + /** + * @return the current Color of plus icon. + */ + public int getPlusColor() { + return mPlusColor; + } + + public void setPlusColorResId(@ColorRes int plusColor) { + setPlusColor(getColor(plusColor)); + } + + public void setPlusColor(int color) { + if (mPlusColor != color) { + mPlusColor = color; + updateBackground(); + } + } + + @Override + public void setIcon(@DrawableRes int icon) { + throw new UnsupportedOperationException("Use FloatingActionButton if you want to use custom icon"); + } + @Override Drawable getIconDrawable() { final float iconSize = getDimension(R.dimen.fab_icon_size); diff --git a/library/src/main/java/com/getbase/floatingactionbutton/FloatingActionButton.java b/library/src/main/java/com/getbase/floatingactionbutton/FloatingActionButton.java index 53faadbe..b83285d7 100644 --- a/library/src/main/java/com/getbase/floatingactionbutton/FloatingActionButton.java +++ b/library/src/main/java/com/getbase/floatingactionbutton/FloatingActionButton.java @@ -3,46 +3,59 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Paint.Style; -import android.graphics.RectF; +import android.graphics.Rect; +import android.graphics.Shader; import android.graphics.Shader.TileMode; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.ShapeDrawable.ShaderFactory; import android.graphics.drawable.StateListDrawable; +import android.graphics.drawable.shapes.OvalShape; import android.os.Build; import android.os.Build.VERSION_CODES; import android.support.annotation.ColorRes; import android.support.annotation.DimenRes; import android.support.annotation.DrawableRes; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; import android.util.AttributeSet; import android.widget.ImageButton; +import android.widget.TextView; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; public class FloatingActionButton extends ImageButton { public static final int SIZE_NORMAL = 0; public static final int SIZE_MINI = 1; - private static final int HALF_TRANSPARENT_WHITE = Color.argb(128, 255, 255, 255); - private static final int HALF_TRANSPARENT_BLACK = Color.argb(128, 0, 0, 0); + @Retention(RetentionPolicy.SOURCE) + @IntDef({ SIZE_NORMAL, SIZE_MINI }) + public @interface FAB_SIZE { + } int mColorNormal; int mColorPressed; + int mColorDisabled; + String mTitle; @DrawableRes private int mIcon; + private Drawable mIconDrawable; private int mSize; private float mCircleSize; private float mShadowRadius; private float mShadowOffset; private int mDrawableSize; + boolean mStrokeVisible; public FloatingActionButton(Context context) { this(context, null); @@ -59,22 +72,131 @@ public FloatingActionButton(Context context, AttributeSet attrs, int defStyle) { } void init(Context context, AttributeSet attributeSet) { - mColorNormal = getColor(android.R.color.holo_blue_dark); - mColorPressed = getColor(android.R.color.holo_blue_light); - mIcon = 0; - mSize = SIZE_NORMAL; - if (attributeSet != null) { - initAttributes(context, attributeSet); - } - - mCircleSize = getDimension(mSize == SIZE_NORMAL ? R.dimen.fab_size_normal : R.dimen.fab_size_mini); + TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionButton, 0, 0); + mColorNormal = attr.getColor(R.styleable.FloatingActionButton_fab_colorNormal, getColor(android.R.color.holo_blue_dark)); + mColorPressed = attr.getColor(R.styleable.FloatingActionButton_fab_colorPressed, getColor(android.R.color.holo_blue_light)); + mColorDisabled = attr.getColor(R.styleable.FloatingActionButton_fab_colorDisabled, getColor(android.R.color.darker_gray)); + mSize = attr.getInt(R.styleable.FloatingActionButton_fab_size, SIZE_NORMAL); + mIcon = attr.getResourceId(R.styleable.FloatingActionButton_fab_icon, 0); + mTitle = attr.getString(R.styleable.FloatingActionButton_fab_title); + mStrokeVisible = attr.getBoolean(R.styleable.FloatingActionButton_fab_stroke_visible, true); + attr.recycle(); + + updateCircleSize(); mShadowRadius = getDimension(R.dimen.fab_shadow_radius); mShadowOffset = getDimension(R.dimen.fab_shadow_offset); - mDrawableSize = (int) (mCircleSize + 2 * mShadowRadius); + updateDrawableSize(); updateBackground(); } + private void updateDrawableSize() { + mDrawableSize = (int) (mCircleSize + 2 * mShadowRadius); + } + + private void updateCircleSize() { + mCircleSize = getDimension(mSize == SIZE_NORMAL ? R.dimen.fab_size_normal : R.dimen.fab_size_mini); + } + + public void setSize(@FAB_SIZE int size) { + if (size != SIZE_MINI && size != SIZE_NORMAL) { + throw new IllegalArgumentException("Use @FAB_SIZE constants only!"); + } + + if (mSize != size) { + mSize = size; + updateCircleSize(); + updateDrawableSize(); + updateBackground(); + } + } + + @FAB_SIZE + public int getSize() { + return mSize; + } + + public void setIcon(@DrawableRes int icon) { + if (mIcon != icon) { + mIcon = icon; + mIconDrawable = null; + updateBackground(); + } + } + + public void setIconDrawable(@NonNull Drawable iconDrawable) { + if (mIconDrawable != iconDrawable) { + mIcon = 0; + mIconDrawable = iconDrawable; + updateBackground(); + } + } + + /** + * @return the current Color for normal state. + */ + public int getColorNormal() { + return mColorNormal; + } + + public void setColorNormalResId(@ColorRes int colorNormal) { + setColorNormal(getColor(colorNormal)); + } + + public void setColorNormal(int color) { + if (mColorNormal != color) { + mColorNormal = color; + updateBackground(); + } + } + + /** + * @return the current color for pressed state. + */ + public int getColorPressed() { + return mColorPressed; + } + + public void setColorPressedResId(@ColorRes int colorPressed) { + setColorPressed(getColor(colorPressed)); + } + + public void setColorPressed(int color) { + if (mColorPressed != color) { + mColorPressed = color; + updateBackground(); + } + } + + /** + * @return the current color for disabled state. + */ + public int getColorDisabled() { + return mColorDisabled; + } + + public void setColorDisabledResId(@ColorRes int colorDisabled) { + setColorDisabled(getColor(colorDisabled)); + } + + public void setColorDisabled(int color) { + if (mColorDisabled != color) { + mColorDisabled = color; + updateBackground(); + } + } + + public void setStrokeVisible(boolean visible) { + if (mStrokeVisible != visible) { + mStrokeVisible = visible; + updateBackground(); + } + } + + public boolean isStrokeVisible() { + return mStrokeVisible; + } + int getColor(@ColorRes int id) { return getResources().getColor(id); } @@ -83,20 +205,22 @@ float getDimension(@DimenRes int id) { return getResources().getDimension(id); } - private void initAttributes(Context context, AttributeSet attributeSet) { - TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionButton, 0, 0); - if (attr != null) { - try { - mColorNormal = attr.getColor(R.styleable.FloatingActionButton_colorNormal, getColor(android.R.color.holo_blue_dark)); - mColorPressed = attr.getColor(R.styleable.FloatingActionButton_colorPressed, getColor(android.R.color.holo_blue_light)); - mSize = attr.getInt(R.styleable.FloatingActionButton_size, SIZE_NORMAL); - mIcon = attr.getResourceId(R.styleable.FloatingActionButton_icon, 0); - } finally { - attr.recycle(); - } + public void setTitle(String title) { + mTitle = title; + TextView label = getLabelView(); + if (label != null) { + label.setText(title); } } + TextView getLabelView() { + return (TextView) getTag(R.id.fab_label); + } + + public String getTitle() { + return mTitle; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -104,112 +228,183 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { } void updateBackground() { - float circleLeft = mShadowRadius; - float circleTop = mShadowRadius - mShadowOffset; - - final RectF circleRect = new RectF(circleLeft, circleTop, circleLeft + mCircleSize, circleTop + mCircleSize); + final float strokeWidth = getDimension(R.dimen.fab_stroke_width); + final float halfStrokeWidth = strokeWidth / 2f; LayerDrawable layerDrawable = new LayerDrawable( new Drawable[] { getResources().getDrawable(mSize == SIZE_NORMAL ? R.drawable.fab_bg_normal : R.drawable.fab_bg_mini), - createFillDrawable(circleRect), - createStrokesDrawable(circleRect), + createFillDrawable(strokeWidth), + createOuterStrokeDrawable(strokeWidth), getIconDrawable() }); - float iconOffset = (mCircleSize - getDimension(R.dimen.fab_icon_size)) / 2f; + int iconOffset = (int) (mCircleSize - getDimension(R.dimen.fab_icon_size)) / 2; - int iconInsetHorizontal = (int) (mShadowRadius + iconOffset); - int iconInsetTop = (int) (circleTop + iconOffset); - int iconInsetBottom = (int) (mShadowRadius + mShadowOffset + iconOffset); + int circleInsetHorizontal = (int) (mShadowRadius); + int circleInsetTop = (int) (mShadowRadius - mShadowOffset); + int circleInsetBottom = (int) (mShadowRadius + mShadowOffset); - layerDrawable.setLayerInset(3, iconInsetHorizontal, iconInsetTop, iconInsetHorizontal, iconInsetBottom); + layerDrawable.setLayerInset(1, + circleInsetHorizontal, + circleInsetTop, + circleInsetHorizontal, + circleInsetBottom); + + layerDrawable.setLayerInset(2, + (int) (circleInsetHorizontal - halfStrokeWidth), + (int) (circleInsetTop - halfStrokeWidth), + (int) (circleInsetHorizontal - halfStrokeWidth), + (int) (circleInsetBottom - halfStrokeWidth)); + + layerDrawable.setLayerInset(3, + circleInsetHorizontal + iconOffset, + circleInsetTop + iconOffset, + circleInsetHorizontal + iconOffset, + circleInsetBottom + iconOffset); setBackgroundCompat(layerDrawable); } Drawable getIconDrawable() { - if (mIcon != 0) { + if (mIconDrawable != null) { + return mIconDrawable; + } else if (mIcon != 0) { return getResources().getDrawable(mIcon); } else { return new ColorDrawable(Color.TRANSPARENT); } } - private StateListDrawable createFillDrawable(RectF circleRect) { + private StateListDrawable createFillDrawable(float strokeWidth) { StateListDrawable drawable = new StateListDrawable(); - drawable.addState(new int[] { android.R.attr.state_pressed }, createCircleDrawable(circleRect, mColorPressed)); - drawable.addState(new int[] { }, createCircleDrawable(circleRect, mColorNormal)); + drawable.addState(new int[] { -android.R.attr.state_enabled }, createCircleDrawable(mColorDisabled, strokeWidth)); + drawable.addState(new int[] { android.R.attr.state_pressed }, createCircleDrawable(mColorPressed, strokeWidth)); + drawable.addState(new int[] { }, createCircleDrawable(mColorNormal, strokeWidth)); return drawable; } - private Drawable createCircleDrawable(RectF circleRect, int color) { - final Bitmap bitmap = Bitmap.createBitmap(mDrawableSize, mDrawableSize, Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); + private Drawable createCircleDrawable(int color, float strokeWidth) { + int alpha = Color.alpha(color); + int opaqueColor = opaque(color); - final Paint paint = new Paint(); + ShapeDrawable fillDrawable = new ShapeDrawable(new OvalShape()); + + final Paint paint = fillDrawable.getPaint(); paint.setAntiAlias(true); - paint.setColor(color); + paint.setColor(opaqueColor); + + Drawable[] layers = { + fillDrawable, + createInnerStrokesDrawable(opaqueColor, strokeWidth) + }; - canvas.drawOval(circleRect, paint); + LayerDrawable drawable = alpha == 255 || !mStrokeVisible + ? new LayerDrawable(layers) + : new TranslucentLayerDrawable(alpha, layers); + + int halfStrokeWidth = (int) (strokeWidth / 2f); + drawable.setLayerInset(1, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth); + + return drawable; + } + + private static class TranslucentLayerDrawable extends LayerDrawable { + private final int mAlpha; + + public TranslucentLayerDrawable(int alpha, Drawable... layers) { + super(layers); + mAlpha = alpha; + } + + @Override + public void draw(Canvas canvas) { + Rect bounds = getBounds(); + canvas.saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, mAlpha, Canvas.ALL_SAVE_FLAG); + super.draw(canvas); + canvas.restore(); + } + } + + private Drawable createOuterStrokeDrawable(float strokeWidth) { + ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape()); + + final Paint paint = shapeDrawable.getPaint(); + paint.setAntiAlias(true); + paint.setStrokeWidth(strokeWidth); + paint.setStyle(Style.STROKE); + paint.setColor(Color.BLACK); + paint.setAlpha(opacityToAlpha(0.02f)); - return new BitmapDrawable(getResources(), bitmap); + return shapeDrawable; } private int opacityToAlpha(float opacity) { return (int) (255f * opacity); } - private Drawable createStrokesDrawable(RectF circleRect) { - final Bitmap bitmap = Bitmap.createBitmap(mDrawableSize, mDrawableSize, Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); + private int darkenColor(int argb) { + return adjustColorBrightness(argb, 0.9f); + } - final float strokeWidth = getDimension(R.dimen.fab_stroke_width); - final float halfStrokeWidth = strokeWidth / 2f; + private int lightenColor(int argb) { + return adjustColorBrightness(argb, 1.1f); + } + + private int adjustColorBrightness(int argb, float factor) { + float[] hsv = new float[3]; + Color.colorToHSV(argb, hsv); + + hsv[2] = Math.min(hsv[2] * factor, 1f); - RectF outerStrokeRect = new RectF( - circleRect.left - halfStrokeWidth, - circleRect.top - halfStrokeWidth, - circleRect.right + halfStrokeWidth, - circleRect.bottom + halfStrokeWidth + return Color.HSVToColor(Color.alpha(argb), hsv); + } + + private int halfTransparent(int argb) { + return Color.argb( + Color.alpha(argb) / 2, + Color.red(argb), + Color.green(argb), + Color.blue(argb) ); + } - RectF innerStrokeRect = new RectF( - circleRect.left + halfStrokeWidth, - circleRect.top + halfStrokeWidth, - circleRect.right - halfStrokeWidth, - circleRect.bottom - halfStrokeWidth + private int opaque(int argb) { + return Color.rgb( + Color.red(argb), + Color.green(argb), + Color.blue(argb) ); + } - final Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setStrokeWidth(strokeWidth); - paint.setStyle(Style.STROKE); + private Drawable createInnerStrokesDrawable(final int color, float strokeWidth) { + if (!mStrokeVisible) { + return new ColorDrawable(Color.TRANSPARENT); + } - // outer - paint.setColor(Color.BLACK); - paint.setAlpha(opacityToAlpha(0.02f)); - canvas.drawOval(outerStrokeRect, paint); + ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape()); - // inner bottom - paint.setShader(new LinearGradient(innerStrokeRect.centerX(), innerStrokeRect.top, innerStrokeRect.centerX(), innerStrokeRect.bottom, - new int[] { Color.TRANSPARENT, HALF_TRANSPARENT_BLACK, Color.BLACK }, - new float[] { 0f, 0.8f, 1f }, - TileMode.CLAMP - )); - paint.setAlpha(opacityToAlpha(0.04f)); - canvas.drawOval(innerStrokeRect, paint); + final int bottomStrokeColor = darkenColor(color); + final int bottomStrokeColorHalfTransparent = halfTransparent(bottomStrokeColor); + final int topStrokeColor = lightenColor(color); + final int topStrokeColorHalfTransparent = halfTransparent(topStrokeColor); - // inner top - paint.setShader(new LinearGradient(innerStrokeRect.centerX(), innerStrokeRect.top, innerStrokeRect.centerX(), innerStrokeRect.bottom, - new int[] { Color.WHITE, HALF_TRANSPARENT_WHITE, Color.TRANSPARENT }, - new float[] { 0f, 0.2f, 1f }, - TileMode.CLAMP - )); - paint.setAlpha(opacityToAlpha(0.8f)); - canvas.drawOval(innerStrokeRect, paint); + final Paint paint = shapeDrawable.getPaint(); + paint.setAntiAlias(true); + paint.setStrokeWidth(strokeWidth); + paint.setStyle(Style.STROKE); + shapeDrawable.setShaderFactory(new ShaderFactory() { + @Override + public Shader resize(int width, int height) { + return new LinearGradient(width / 2, 0, width / 2, height, + new int[] { topStrokeColor, topStrokeColorHalfTransparent, color, bottomStrokeColorHalfTransparent, bottomStrokeColor }, + new float[] { 0f, 0.2f, 0.5f, 0.8f, 1f }, + TileMode.CLAMP + ); + } + }); - return new BitmapDrawable(getResources(), bitmap); + return shapeDrawable; } @SuppressWarnings("deprecation") @@ -221,4 +416,14 @@ private void setBackgroundCompat(Drawable drawable) { setBackgroundDrawable(drawable); } } + + @Override + public void setVisibility(int visibility) { + TextView label = getLabelView(); + if (label != null) { + label.setVisibility(visibility); + } + + super.setVisibility(visibility); + } } diff --git a/library/src/main/java/com/getbase/floatingactionbutton/FloatingActionsMenu.java b/library/src/main/java/com/getbase/floatingactionbutton/FloatingActionsMenu.java index 8e2d747d..4acfb787 100644 --- a/library/src/main/java/com/getbase/floatingactionbutton/FloatingActionsMenu.java +++ b/library/src/main/java/com/getbase/floatingactionbutton/FloatingActionsMenu.java @@ -12,13 +12,23 @@ import android.support.annotation.ColorRes; import android.support.annotation.NonNull; import android.util.AttributeSet; +import android.view.ContextThemeWrapper; import android.view.View; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.OvershootInterpolator; +import android.widget.TextView; public class FloatingActionsMenu extends ViewGroup { + public static final int EXPAND_UP = 0; + public static final int EXPAND_DOWN = 1; + public static final int EXPAND_LEFT = 2; + public static final int EXPAND_RIGHT = 3; + + public static final int LABELS_ON_LEFT_SIDE = 0; + public static final int LABELS_ON_RIGHT_SIDE = 1; + private static final int ANIMATION_DURATION = 300; private static final float COLLAPSED_PLUS_ROTATION = 0f; private static final float EXPANDED_PLUS_ROTATION = 90f + 45f; @@ -26,8 +36,13 @@ public class FloatingActionsMenu extends ViewGroup { private int mAddButtonPlusColor; private int mAddButtonColorNormal; private int mAddButtonColorPressed; + private int mAddButtonSize; + private boolean mAddButtonStrokeVisible; + private int mExpandDirection; private int mButtonSpacing; + private int mLabelsMargin; + private int mLabelsVerticalOffset; private boolean mExpanded; @@ -35,6 +50,18 @@ public class FloatingActionsMenu extends ViewGroup { private AnimatorSet mCollapseAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION); private AddFloatingActionButton mAddButton; private RotatingDrawable mRotatingDrawable; + private int mMaxButtonWidth; + private int mMaxButtonHeight; + private int mLabelsStyle; + private int mLabelsPosition; + private int mButtonsCount; + + private OnFloatingActionsMenuUpdateListener mListener; + + public interface OnFloatingActionsMenuUpdateListener { + void onMenuExpanded(); + void onMenuCollapsed(); + } public FloatingActionsMenu(Context context) { this(context, null); @@ -51,28 +78,36 @@ public FloatingActionsMenu(Context context, AttributeSet attrs, int defStyle) { } private void init(Context context, AttributeSet attributeSet) { - mAddButtonPlusColor = getColor(android.R.color.white); - mAddButtonColorNormal = getColor(android.R.color.holo_blue_dark); - mAddButtonColorPressed = getColor(android.R.color.holo_blue_light); - mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing) - getResources().getDimension(R.dimen.fab_shadow_radius) - getResources().getDimension(R.dimen.fab_shadow_offset)); - - if (attributeSet != null) { - TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0); - if (attr != null) { - try { - mAddButtonPlusColor = attr.getColor(R.styleable.FloatingActionsMenu_addButtonPlusIconColor, getColor(android.R.color.white)); - mAddButtonColorNormal = attr.getColor(R.styleable.FloatingActionsMenu_addButtonColorNormal, getColor(android.R.color.holo_blue_dark)); - mAddButtonColorPressed = attr.getColor(R.styleable.FloatingActionsMenu_addButtonColorPressed, getColor(android.R.color.holo_blue_light)); - } finally { - attr.recycle(); - } - } + mLabelsMargin = getResources().getDimensionPixelSize(R.dimen.fab_labels_margin); + mLabelsVerticalOffset = getResources().getDimensionPixelSize(R.dimen.fab_shadow_offset); + + TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0); + mAddButtonPlusColor = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonPlusIconColor, getColor(android.R.color.white)); + mAddButtonColorNormal = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorNormal, getColor(android.R.color.holo_blue_dark)); + mAddButtonColorPressed = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorPressed, getColor(android.R.color.holo_blue_light)); + mAddButtonSize = attr.getInt(R.styleable.FloatingActionsMenu_fab_addButtonSize, FloatingActionButton.SIZE_NORMAL); + mAddButtonStrokeVisible = attr.getBoolean(R.styleable.FloatingActionsMenu_fab_addButtonStrokeVisible, true); + mExpandDirection = attr.getInt(R.styleable.FloatingActionsMenu_fab_expandDirection, EXPAND_UP); + mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionsMenu_fab_labelStyle, 0); + mLabelsPosition = attr.getInt(R.styleable.FloatingActionsMenu_fab_labelsPosition, LABELS_ON_LEFT_SIDE); + attr.recycle(); + + if (mLabelsStyle != 0 && expandsHorizontally()) { + throw new IllegalStateException("Action labels in horizontal expand orientation is not supported."); } createAddButton(context); } + public void setOnFloatingActionsMenuUpdateListener(OnFloatingActionsMenuUpdateListener listener) { + mListener = listener; + } + + private boolean expandsHorizontally() { + return mExpandDirection == EXPAND_LEFT || mExpandDirection == EXPAND_RIGHT; + } + private static class RotatingDrawable extends LayerDrawable { public RotatingDrawable(Drawable drawable) { super(new Drawable[] { drawable }); @@ -107,6 +142,7 @@ void updateBackground() { mPlusColor = mAddButtonPlusColor; mColorNormal = mAddButtonColorNormal; mColorPressed = mAddButtonColorPressed; + mStrokeVisible = mAddButtonStrokeVisible; super.updateBackground(); } @@ -117,8 +153,8 @@ Drawable getIconDrawable() { final OvershootInterpolator interpolator = new OvershootInterpolator(); - final ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", COLLAPSED_PLUS_ROTATION); - final ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", EXPANDED_PLUS_ROTATION); + final ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", EXPANDED_PLUS_ROTATION, COLLAPSED_PLUS_ROTATION); + final ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", COLLAPSED_PLUS_ROTATION, EXPANDED_PLUS_ROTATION); collapseAnimator.setInterpolator(interpolator); expandAnimator.setInterpolator(interpolator); @@ -131,6 +167,7 @@ Drawable getIconDrawable() { }; mAddButton.setId(R.id.fab_expand_menu_button); + mAddButton.setSize(mAddButtonSize); mAddButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -141,6 +178,21 @@ public void onClick(View v) { addView(mAddButton, super.generateDefaultLayoutParams()); } + public void addButton(FloatingActionButton button) { + addView(button, mButtonsCount - 1); + mButtonsCount++; + + if (mLabelsStyle != 0) { + createLabels(); + } + } + + public void removeButton(FloatingActionButton button) { + removeView(button.getLabelView()); + removeView(button); + mButtonsCount--; + } + private int getColor(@ColorRes int id) { return getResources().getColor(id); } @@ -152,46 +204,180 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = 0; int height = 0; - for (int i = 0; i < getChildCount(); i++) { + mMaxButtonWidth = 0; + mMaxButtonHeight = 0; + int maxLabelWidth = 0; + + for (int i = 0; i < mButtonsCount; i++) { View child = getChildAt(i); - width = Math.max(width, child.getMeasuredWidth()); - height += child.getMeasuredHeight(); + if (child.getVisibility() == GONE) { + continue; + } + + switch (mExpandDirection) { + case EXPAND_UP: + case EXPAND_DOWN: + mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth()); + height += child.getMeasuredHeight(); + break; + case EXPAND_LEFT: + case EXPAND_RIGHT: + width += child.getMeasuredWidth(); + mMaxButtonHeight = Math.max(mMaxButtonHeight, child.getMeasuredHeight()); + break; + } + + if (!expandsHorizontally()) { + TextView label = (TextView) child.getTag(R.id.fab_label); + if (label != null) { + maxLabelWidth = Math.max(maxLabelWidth, label.getMeasuredWidth()); + } + } } - height += mButtonSpacing * (getChildCount() - 1); - height = height * 12 / 10; // for overshoot + if (!expandsHorizontally()) { + width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0); + } else { + height = mMaxButtonHeight; + } + + switch (mExpandDirection) { + case EXPAND_UP: + case EXPAND_DOWN: + height += mButtonSpacing * (getChildCount() - 1); + height = adjustForOvershoot(height); + break; + case EXPAND_LEFT: + case EXPAND_RIGHT: + width += mButtonSpacing * (getChildCount() - 1); + width = adjustForOvershoot(width); + break; + } setMeasuredDimension(width, height); } + private int adjustForOvershoot(int dimension) { + return dimension * 12 / 10; + } + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - int addButtonY = b - t - mAddButton.getMeasuredHeight(); - mAddButton.layout(0, addButtonY, mAddButton.getMeasuredWidth(), addButtonY + mAddButton.getMeasuredHeight()); + switch (mExpandDirection) { + case EXPAND_UP: + case EXPAND_DOWN: + boolean expandUp = mExpandDirection == EXPAND_UP; + + int addButtonY = expandUp ? b - t - mAddButton.getMeasuredHeight() : 0; + // Ensure mAddButton is centered on the line where the buttons should be + int buttonsHorizontalCenter = mLabelsPosition == LABELS_ON_LEFT_SIDE + ? r - l - mMaxButtonWidth / 2 + : mMaxButtonWidth / 2; + int addButtonLeft = buttonsHorizontalCenter - mAddButton.getMeasuredWidth() / 2; + mAddButton.layout(addButtonLeft, addButtonY, addButtonLeft + mAddButton.getMeasuredWidth(), addButtonY + mAddButton.getMeasuredHeight()); + + int labelsOffset = mMaxButtonWidth / 2 + mLabelsMargin; + int labelsXNearButton = mLabelsPosition == LABELS_ON_LEFT_SIDE + ? buttonsHorizontalCenter - labelsOffset + : buttonsHorizontalCenter + labelsOffset; + + int nextY = expandUp ? + addButtonY - mButtonSpacing : + addButtonY + mAddButton.getMeasuredHeight() + mButtonSpacing; + + for (int i = mButtonsCount - 1; i >= 0; i--) { + final View child = getChildAt(i); + + if (child == mAddButton || child.getVisibility() == GONE) continue; + + int childX = buttonsHorizontalCenter - child.getMeasuredWidth() / 2; + int childY = expandUp ? nextY - child.getMeasuredHeight() : nextY; + child.layout(childX, childY, childX + child.getMeasuredWidth(), childY + child.getMeasuredHeight()); - int bottomY = addButtonY - mButtonSpacing; + float collapsedTranslation = addButtonY - childY; + float expandedTranslation = 0f; - for (int i = getChildCount() - 1; i >= 0; i--) { - final View child = getChildAt(i); + child.setTranslationY(mExpanded ? expandedTranslation : collapsedTranslation); + child.setAlpha(mExpanded ? 1f : 0f); - if (child == mAddButton) continue; + LayoutParams params = (LayoutParams) child.getLayoutParams(); + params.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation); + params.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation); + params.setAnimationsTarget(child); - int childY = bottomY - child.getMeasuredHeight(); - child.layout(0, childY, child.getMeasuredWidth(), childY + child.getMeasuredHeight()); + View label = (View) child.getTag(R.id.fab_label); + if (label != null) { + int labelXAwayFromButton = mLabelsPosition == LABELS_ON_LEFT_SIDE + ? labelsXNearButton - label.getMeasuredWidth() + : labelsXNearButton + label.getMeasuredWidth(); - float collapsedTranslation = addButtonY - childY; - float expandedTranslation = 0f; + int labelLeft = mLabelsPosition == LABELS_ON_LEFT_SIDE + ? labelXAwayFromButton + : labelsXNearButton; - child.setTranslationY(mExpanded ? expandedTranslation : collapsedTranslation); - child.setAlpha(mExpanded ? 1f : 0f); + int labelRight = mLabelsPosition == LABELS_ON_LEFT_SIDE + ? labelsXNearButton + : labelXAwayFromButton; - LayoutParams params = (LayoutParams) child.getLayoutParams(); - params.mCollapseY.setFloatValues(collapsedTranslation); - params.mExpandY.setFloatValues(expandedTranslation); - params.setAnimationsTarget(child); + int labelTop = childY - mLabelsVerticalOffset + (child.getMeasuredHeight() - label.getMeasuredHeight()) / 2; - bottomY = childY - mButtonSpacing; + label.layout(labelLeft, labelTop, labelRight, labelTop + label.getMeasuredHeight()); + + label.setTranslationY(mExpanded ? expandedTranslation : collapsedTranslation); + label.setAlpha(mExpanded ? 1f : 0f); + + LayoutParams labelParams = (LayoutParams) label.getLayoutParams(); + labelParams.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation); + labelParams.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation); + labelParams.setAnimationsTarget(label); + } + + nextY = expandUp ? + childY - mButtonSpacing : + childY + child.getMeasuredHeight() + mButtonSpacing; + } + break; + + case EXPAND_LEFT: + case EXPAND_RIGHT: + boolean expandLeft = mExpandDirection == EXPAND_LEFT; + + int addButtonX = expandLeft ? r - l - mAddButton.getMeasuredWidth() : 0; + // Ensure mAddButton is centered on the line where the buttons should be + int addButtonTop = b - t - mMaxButtonHeight + (mMaxButtonHeight - mAddButton.getMeasuredHeight()) / 2; + mAddButton.layout(addButtonX, addButtonTop, addButtonX + mAddButton.getMeasuredWidth(), addButtonTop + mAddButton.getMeasuredHeight()); + + int nextX = expandLeft ? + addButtonX - mButtonSpacing : + addButtonX + mAddButton.getMeasuredWidth() + mButtonSpacing; + + for (int i = mButtonsCount - 1; i >= 0; i--) { + final View child = getChildAt(i); + + if (child == mAddButton || child.getVisibility() == GONE) continue; + + int childX = expandLeft ? nextX - child.getMeasuredWidth() : nextX; + int childY = addButtonTop + (mAddButton.getMeasuredHeight() - child.getMeasuredHeight()) / 2; + child.layout(childX, childY, childX + child.getMeasuredWidth(), childY + child.getMeasuredHeight()); + + float collapsedTranslation = addButtonX - childX; + float expandedTranslation = 0f; + + child.setTranslationX(mExpanded ? expandedTranslation : collapsedTranslation); + child.setAlpha(mExpanded ? 1f : 0f); + + LayoutParams params = (LayoutParams) child.getLayoutParams(); + params.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation); + params.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation); + params.setAnimationsTarget(child); + + nextX = expandLeft ? + childX - mButtonSpacing : + childX + child.getMeasuredWidth() + mButtonSpacing; + } + + break; } } @@ -221,47 +407,86 @@ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { private class LayoutParams extends ViewGroup.LayoutParams { - private ObjectAnimator mExpandY = new ObjectAnimator(); + private ObjectAnimator mExpandDir = new ObjectAnimator(); private ObjectAnimator mExpandAlpha = new ObjectAnimator(); - private ObjectAnimator mCollapseY = new ObjectAnimator(); + private ObjectAnimator mCollapseDir = new ObjectAnimator(); private ObjectAnimator mCollapseAlpha = new ObjectAnimator(); + private boolean animationsSetToPlay; public LayoutParams(ViewGroup.LayoutParams source) { super(source); - mExpandY.setInterpolator(sExpandInterpolator); + mExpandDir.setInterpolator(sExpandInterpolator); mExpandAlpha.setInterpolator(sAlphaExpandInterpolator); - mCollapseY.setInterpolator(sCollapseInterpolator); + mCollapseDir.setInterpolator(sCollapseInterpolator); mCollapseAlpha.setInterpolator(sCollapseInterpolator); mCollapseAlpha.setProperty(View.ALPHA); - mCollapseAlpha.setFloatValues(0f); + mCollapseAlpha.setFloatValues(1f, 0f); mExpandAlpha.setProperty(View.ALPHA); - mExpandAlpha.setFloatValues(1f); - - mCollapseY.setProperty(View.TRANSLATION_Y); - mExpandY.setProperty(View.TRANSLATION_Y); - - mExpandAnimation.play(mExpandAlpha); - mExpandAnimation.play(mExpandY); - - mCollapseAnimation.play(mCollapseAlpha); - mCollapseAnimation.play(mCollapseY); + mExpandAlpha.setFloatValues(0f, 1f); + + switch (mExpandDirection) { + case EXPAND_UP: + case EXPAND_DOWN: + mCollapseDir.setProperty(View.TRANSLATION_Y); + mExpandDir.setProperty(View.TRANSLATION_Y); + break; + case EXPAND_LEFT: + case EXPAND_RIGHT: + mCollapseDir.setProperty(View.TRANSLATION_X); + mExpandDir.setProperty(View.TRANSLATION_X); + break; + } } public void setAnimationsTarget(View view) { mCollapseAlpha.setTarget(view); - mCollapseY.setTarget(view); + mCollapseDir.setTarget(view); mExpandAlpha.setTarget(view); - mExpandY.setTarget(view); + mExpandDir.setTarget(view); + + // Now that the animations have targets, set them to be played + if (!animationsSetToPlay) { + mCollapseAnimation.play(mCollapseAlpha); + mCollapseAnimation.play(mCollapseDir); + mExpandAnimation.play(mExpandAlpha); + mExpandAnimation.play(mExpandDir); + animationsSetToPlay = true; + } } } @Override protected void onFinishInflate() { super.onFinishInflate(); + bringChildToFront(mAddButton); + mButtonsCount = getChildCount(); + + if (mLabelsStyle != 0) { + createLabels(); + } + } + + private void createLabels() { + Context context = new ContextThemeWrapper(getContext(), mLabelsStyle); + + for (int i = 0; i < mButtonsCount; i++) { + FloatingActionButton button = (FloatingActionButton) getChildAt(i); + String title = button.getTitle(); + + if (button == mAddButton || title == null || + button.getTag(R.id.fab_label) != null) continue; + + TextView label = new TextView(context); + label.setTextAppearance(getContext(), mLabelsStyle); + label.setText(button.getTitle()); + addView(label); + + button.setTag(R.id.fab_label, label); + } } public void collapse() { @@ -269,6 +494,10 @@ public void collapse() { mExpanded = false; mCollapseAnimation.start(); mExpandAnimation.cancel(); + + if (mListener != null) { + mListener.onMenuCollapsed(); + } } } @@ -285,9 +514,17 @@ public void expand() { mExpanded = true; mCollapseAnimation.cancel(); mExpandAnimation.start(); + + if (mListener != null) { + mListener.onMenuExpanded(); + } } } + public boolean isExpanded() { + return mExpanded; + } + @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); diff --git a/library/src/main/res/drawable-hdpi/fab_bg_mini.png b/library/src/main/res/drawable-hdpi/fab_bg_mini.png index 4add9f48..a5984b05 100644 Binary files a/library/src/main/res/drawable-hdpi/fab_bg_mini.png and b/library/src/main/res/drawable-hdpi/fab_bg_mini.png differ diff --git a/library/src/main/res/drawable-hdpi/fab_bg_normal.png b/library/src/main/res/drawable-hdpi/fab_bg_normal.png index 2e7152fe..fa69e8ba 100644 Binary files a/library/src/main/res/drawable-hdpi/fab_bg_normal.png and b/library/src/main/res/drawable-hdpi/fab_bg_normal.png differ diff --git a/library/src/main/res/drawable-mdpi/fab_bg_mini.png b/library/src/main/res/drawable-mdpi/fab_bg_mini.png index a0c144e3..c4105977 100644 Binary files a/library/src/main/res/drawable-mdpi/fab_bg_mini.png and b/library/src/main/res/drawable-mdpi/fab_bg_mini.png differ diff --git a/library/src/main/res/drawable-mdpi/fab_bg_normal.png b/library/src/main/res/drawable-mdpi/fab_bg_normal.png index e0205261..eafe4ba0 100644 Binary files a/library/src/main/res/drawable-mdpi/fab_bg_normal.png and b/library/src/main/res/drawable-mdpi/fab_bg_normal.png differ diff --git a/library/src/main/res/drawable-xhdpi/fab_bg_mini.png b/library/src/main/res/drawable-xhdpi/fab_bg_mini.png index 30c9c979..350da4a4 100644 Binary files a/library/src/main/res/drawable-xhdpi/fab_bg_mini.png and b/library/src/main/res/drawable-xhdpi/fab_bg_mini.png differ diff --git a/library/src/main/res/drawable-xhdpi/fab_bg_normal.png b/library/src/main/res/drawable-xhdpi/fab_bg_normal.png index b6bf21a5..28125e1b 100644 Binary files a/library/src/main/res/drawable-xhdpi/fab_bg_normal.png and b/library/src/main/res/drawable-xhdpi/fab_bg_normal.png differ diff --git a/library/src/main/res/drawable-xxhdpi/fab_bg_mini.png b/library/src/main/res/drawable-xxhdpi/fab_bg_mini.png index 17c62c6d..4642841e 100644 Binary files a/library/src/main/res/drawable-xxhdpi/fab_bg_mini.png and b/library/src/main/res/drawable-xxhdpi/fab_bg_mini.png differ diff --git a/library/src/main/res/drawable-xxhdpi/fab_bg_normal.png b/library/src/main/res/drawable-xxhdpi/fab_bg_normal.png index e5e13686..f1acbc04 100644 Binary files a/library/src/main/res/drawable-xxhdpi/fab_bg_normal.png and b/library/src/main/res/drawable-xxhdpi/fab_bg_normal.png differ diff --git a/library/src/main/res/drawable-xxxhdpi/fab_bg_mini.png b/library/src/main/res/drawable-xxxhdpi/fab_bg_mini.png new file mode 100644 index 00000000..d4d7e2f3 Binary files /dev/null and b/library/src/main/res/drawable-xxxhdpi/fab_bg_mini.png differ diff --git a/library/src/main/res/drawable-xxxhdpi/fab_bg_normal.png b/library/src/main/res/drawable-xxxhdpi/fab_bg_normal.png new file mode 100644 index 00000000..c488e558 Binary files /dev/null and b/library/src/main/res/drawable-xxxhdpi/fab_bg_normal.png differ diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index 2c9c9359..b9eaaf55 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -1,20 +1,39 @@ - - - - + + + + + + + - + - - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/values/dimens.xml b/library/src/main/res/values/dimens.xml index 8ffff4e1..a2b506e3 100644 --- a/library/src/main/res/values/dimens.xml +++ b/library/src/main/res/values/dimens.xml @@ -10,7 +10,8 @@ 3dp 9dp - 0.5dp + 1dp 16dp + 8dp diff --git a/library/src/main/res/values/ids.xml b/library/src/main/res/values/ids.xml index 4855d31e..9b7c8b26 100644 --- a/library/src/main/res/values/ids.xml +++ b/library/src/main/res/values/ids.xml @@ -1,4 +1,5 @@ + \ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle index d2a26b8e..e693db68 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,24 +1,25 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 19 - buildToolsVersion "19.1.0" + compileSdkVersion 19 + buildToolsVersion "21.1.2" - defaultConfig { - applicationId "com.getbase.floatingactionbutton.sample" - minSdkVersion 14 - targetSdkVersion 19 - versionCode 1 - versionName "1.0" - } - buildTypes { - release { - runProguard false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } + defaultConfig { + applicationId "com.getbase.floatingactionbutton.sample" + minSdkVersion 14 + targetSdkVersion 19 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled true + signingConfig signingConfigs.debug + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + } } dependencies { - compile project(':library') + compile project(':library') } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 8cc75f35..9ad1530a 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ + android:theme="@style/Theme"> diff --git a/sample/src/main/java/com/getbase/floatingactionbutton/sample/MainActivity.java b/sample/src/main/java/com/getbase/floatingactionbutton/sample/MainActivity.java index 7bc64f56..d3602ac3 100644 --- a/sample/src/main/java/com/getbase/floatingactionbutton/sample/MainActivity.java +++ b/sample/src/main/java/com/getbase/floatingactionbutton/sample/MainActivity.java @@ -1,12 +1,69 @@ package com.getbase.floatingactionbutton.sample; +import com.getbase.floatingactionbutton.FloatingActionButton; +import com.getbase.floatingactionbutton.FloatingActionsMenu; + import android.app.Activity; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.OvalShape; import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Toast; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + + findViewById(R.id.pink_icon).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(MainActivity.this, "Clicked pink Floating Action Button", Toast.LENGTH_SHORT).show(); + } + }); + + FloatingActionButton button = (FloatingActionButton) findViewById(R.id.setter); + button.setSize(FloatingActionButton.SIZE_MINI); + button.setColorNormalResId(R.color.pink); + button.setColorPressedResId(R.color.pink_pressed); + button.setIcon(R.drawable.ic_fab_star); + button.setStrokeVisible(false); + + final View actionB = findViewById(R.id.action_b); + + FloatingActionButton actionC = new FloatingActionButton(getBaseContext()); + actionC.setTitle("Hide/Show Action above"); + actionC.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + actionB.setVisibility(actionB.getVisibility() == View.GONE ? View.VISIBLE : View.GONE); + } + }); + ((FloatingActionsMenu) findViewById(R.id.multiple_actions)).addButton(actionC); + + final FloatingActionButton removeAction = (FloatingActionButton) findViewById(R.id.button_remove); + removeAction.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + ((FloatingActionsMenu) findViewById(R.id.multiple_actions_down)).removeButton(removeAction); + } + }); + + ShapeDrawable drawable = new ShapeDrawable(new OvalShape()); + drawable.getPaint().setColor(getResources().getColor(R.color.white)); + ((FloatingActionButton) findViewById(R.id.setter_drawable)).setIconDrawable(drawable); + + final FloatingActionButton actionA = (FloatingActionButton) findViewById(R.id.action_a); + actionA.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + actionA.setTitle("Action A clicked"); + } + }); + + // Test that FAMs containing FABs with visibility GONE do not cause crashes + findViewById(R.id.button_gone).setVisibility(View.GONE); } } diff --git a/sample/src/main/res/drawable/fab_label_background.xml b/sample/src/main/res/drawable/fab_label_background.xml new file mode 100644 index 00000000..0d8c05b1 --- /dev/null +++ b/sample/src/main/res/drawable/fab_label_background.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index 6bf527bf..a0e841af 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -5,38 +5,89 @@ android:layout_height="match_parent"> + + + + + + + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp"/> - + android:layout_alignParentStart="true" + android:layout_above="@id/normal_plus" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + fab:fab_addButtonColorNormal="@color/white" + fab:fab_addButtonColorPressed="@color/white_pressed" + fab:fab_addButtonPlusIconColor="@color/half_black" + fab:fab_addButtonSize="mini" + fab:fab_labelStyle="@style/menu_labels_style" + fab:fab_labelsPosition="right"> + + + + + + + android:layout_marginRight="16dp" + android:layout_marginEnd="16dp"> + + + + + + + + + + + + + fab:fab_colorNormal="@color/white" + fab:fab_colorPressed="@color/white_pressed"/> + + + + + + + + + android:visibility="gone" + fab:fab_colorNormal="@color/white" + fab:fab_colorPressed="@color/white_pressed" + fab:fab_size="mini"/> + + diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml index b5aaf20f..ff0e9df5 100644 --- a/sample/src/main/res/values/colors.xml +++ b/sample/src/main/res/values/colors.xml @@ -1,9 +1,12 @@ + #B2000000 #e5e5e5 #808080 #fafafa #f1f1f1 #e91e63 #ec407a - \ No newline at end of file + #805677fc + #80738ffe + diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml new file mode 100644 index 00000000..f2e67cef --- /dev/null +++ b/sample/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/values/themes.xml b/sample/src/main/res/values/themes.xml new file mode 100644 index 00000000..80784ed4 --- /dev/null +++ b/sample/src/main/res/values/themes.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/screenshots/labels.png b/screenshots/labels.png new file mode 100644 index 00000000..ba87ad1d Binary files /dev/null and b/screenshots/labels.png differ