package com.github.clans.fab; import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.graphics.Xfermode; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.RippleDrawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.StateListDrawable; import android.graphics.drawable.shapes.OvalShape; import android.graphics.drawable.shapes.Shape; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.ImageButton; import android.widget.TextView; public class FloatingActionButton extends ImageButton { public static final int SIZE_NORMAL = 0; public static final int SIZE_MINI = 1; int mFabSize; boolean mShowShadow; int mShadowColor; int mShadowRadius = Util.dpToPx(getContext(), 4f); int mShadowXOffset = Util.dpToPx(getContext(), 1f); int mShadowYOffset = Util.dpToPx(getContext(), 3f); private static final Xfermode PORTER_DUFF_CLEAR = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); private static final long PAUSE_GROWING_TIME = 200; private static final double BAR_SPIN_CYCLE_TIME = 500; private static final int BAR_MAX_LENGTH = 270; private int mColorNormal; private int mColorPressed; private int mColorDisabled; private int mColorRipple; private Drawable mIcon; private int mIconSize = Util.dpToPx(getContext(), 24f); private Animation mShowAnimation; private Animation mHideAnimation; private String mLabelText; private OnClickListener mClickListener; private Drawable mBackgroundDrawable; private boolean mUsingElevation; private boolean mUsingElevationCompat; // Progress private boolean mProgressBarEnabled; private int mProgressWidth = Util.dpToPx(getContext(), 6f); private int mProgressColor; private int mProgressBackgroundColor; private boolean mShouldUpdateButtonPosition; private float mOriginalX = -1; private float mOriginalY = -1; private boolean mButtonPositionSaved; private RectF mProgressCircleBounds = new RectF(); private Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Paint mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private boolean mProgressIndeterminate; private long mLastTimeAnimated; private float mSpinSpeed = 195.0f; //The amount of degrees per second private long mPausedTimeWithoutGrowing = 0; private double mTimeStartGrowing; private boolean mBarGrowingFromFront = true; private int mBarLength = 16; private float mBarExtraLength; private float mCurrentProgress; private float mTargetProgress; private int mProgress; private boolean mAnimateProgress; private boolean mShouldProgressIndeterminate; private boolean mShouldSetProgress; private int mProgressMax = 100; private boolean mShowProgressBackground; public FloatingActionButton(Context context) { this(context, null); } public FloatingActionButton(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs, defStyleAttr); } private void init(Context context, AttributeSet attrs, int defStyleAttr) { TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.FloatingActionButton, defStyleAttr, 0); mColorNormal = attr.getColor(R.styleable.FloatingActionButton_fab_colorNormal, 0xFFDA4336); mColorPressed = attr.getColor(R.styleable.FloatingActionButton_fab_colorPressed, 0xFFE75043); mColorDisabled = attr.getColor(R.styleable.FloatingActionButton_fab_colorDisabled, 0xFFAAAAAA); mColorRipple = attr.getColor(R.styleable.FloatingActionButton_fab_colorRipple, 0x99FFFFFF); mShowShadow = attr.getBoolean(R.styleable.FloatingActionButton_fab_showShadow, true); mShadowColor = attr.getColor(R.styleable.FloatingActionButton_fab_shadowColor, 0x66000000); mShadowRadius = attr.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_shadowRadius, mShadowRadius); mShadowXOffset = attr.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_shadowXOffset, mShadowXOffset); mShadowYOffset = attr.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_shadowYOffset, mShadowYOffset); mFabSize = attr.getInt(R.styleable.FloatingActionButton_fab_size, SIZE_NORMAL); mLabelText = attr.getString(R.styleable.FloatingActionButton_fab_label); mShouldProgressIndeterminate = attr.getBoolean(R.styleable.FloatingActionButton_fab_progress_indeterminate, false); mProgressColor = attr.getColor(R.styleable.FloatingActionButton_fab_progress_color, 0xFF009688); mProgressBackgroundColor = attr.getColor(R.styleable.FloatingActionButton_fab_progress_backgroundColor, 0x4D000000); mProgressMax = attr.getInt(R.styleable.FloatingActionButton_fab_progress_max, mProgressMax); mShowProgressBackground = attr.getBoolean(R.styleable.FloatingActionButton_fab_progress_showBackground, true); if (attr.hasValue(R.styleable.FloatingActionButton_fab_progress)) { mProgress = attr.getInt(R.styleable.FloatingActionButton_fab_progress, 0); mShouldSetProgress = true; } if (attr.hasValue(R.styleable.FloatingActionButton_fab_elevationCompat)) { float elevation = attr.getDimensionPixelOffset(R.styleable.FloatingActionButton_fab_elevationCompat, 0); if (isInEditMode()) { setElevation(elevation); } else { setElevationCompat(elevation); } } initShowAnimation(attr); initHideAnimation(attr); attr.recycle(); if (isInEditMode()) { if (mShouldProgressIndeterminate) { setIndeterminate(true); } else if (mShouldSetProgress) { saveButtonOriginalPosition(); setProgress(mProgress, false); } } // updateBackground(); setClickable(true); } private void initShowAnimation(TypedArray attr) { int resourceId = attr.getResourceId(R.styleable.FloatingActionButton_fab_showAnimation, R.anim.fab_scale_up); mShowAnimation = AnimationUtils.loadAnimation(getContext(), resourceId); } private void initHideAnimation(TypedArray attr) { int resourceId = attr.getResourceId(R.styleable.FloatingActionButton_fab_hideAnimation, R.anim.fab_scale_down); mHideAnimation = AnimationUtils.loadAnimation(getContext(), resourceId); } private int getCircleSize() { return getResources().getDimensionPixelSize(mFabSize == SIZE_NORMAL ? R.dimen.fab_size_normal : R.dimen.fab_size_mini); } private int calculateMeasuredWidth() { int width = getCircleSize() + calculateShadowWidth(); if (mProgressBarEnabled) { width += mProgressWidth * 2; } return width; } private int calculateMeasuredHeight() { int height = getCircleSize() + calculateShadowHeight(); if (mProgressBarEnabled) { height += mProgressWidth * 2; } return height; } int calculateShadowWidth() { return hasShadow() ? getShadowX() * 2 : 0; } int calculateShadowHeight() { return hasShadow() ? getShadowY() * 2 : 0; } private int getShadowX() { return mShadowRadius + Math.abs(mShadowXOffset); } private int getShadowY() { return mShadowRadius + Math.abs(mShadowYOffset); } private float calculateCenterX() { return (float) (getMeasuredWidth() / 2); } private float calculateCenterY() { return (float) (getMeasuredHeight() / 2); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(calculateMeasuredWidth(), calculateMeasuredHeight()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mProgressBarEnabled) { if (mShowProgressBackground) { canvas.drawArc(mProgressCircleBounds, 360, 360, false, mBackgroundPaint); } boolean shouldInvalidate = false; if (mProgressIndeterminate) { shouldInvalidate = true; long deltaTime = SystemClock.uptimeMillis() - mLastTimeAnimated; float deltaNormalized = deltaTime * mSpinSpeed / 1000.0f; updateProgressLength(deltaTime); mCurrentProgress += deltaNormalized; if (mCurrentProgress > 360f) { mCurrentProgress -= 360f; } mLastTimeAnimated = SystemClock.uptimeMillis(); float from = mCurrentProgress - 90; float to = mBarLength + mBarExtraLength; if (isInEditMode()) { from = 0; to = 135; } canvas.drawArc(mProgressCircleBounds, from, to, false, mProgressPaint); } else { if (mCurrentProgress != mTargetProgress) { shouldInvalidate = true; float deltaTime = (float) (SystemClock.uptimeMillis() - mLastTimeAnimated) / 1000; float deltaNormalized = deltaTime * mSpinSpeed; if (mCurrentProgress > mTargetProgress) { mCurrentProgress = Math.max(mCurrentProgress - deltaNormalized, mTargetProgress); } else { mCurrentProgress = Math.min(mCurrentProgress + deltaNormalized, mTargetProgress); } mLastTimeAnimated = SystemClock.uptimeMillis(); } canvas.drawArc(mProgressCircleBounds, -90, mCurrentProgress, false, mProgressPaint); } if (shouldInvalidate) { invalidate(); } } } private void updateProgressLength(long deltaTimeInMillis) { if (mPausedTimeWithoutGrowing >= PAUSE_GROWING_TIME) { mTimeStartGrowing += deltaTimeInMillis; if (mTimeStartGrowing > BAR_SPIN_CYCLE_TIME) { mTimeStartGrowing -= BAR_SPIN_CYCLE_TIME; mPausedTimeWithoutGrowing = 0; mBarGrowingFromFront = !mBarGrowingFromFront; } float distance = (float) Math.cos((mTimeStartGrowing / BAR_SPIN_CYCLE_TIME + 1) * Math.PI) / 2 + 0.5f; float length = BAR_MAX_LENGTH - mBarLength; if (mBarGrowingFromFront) { mBarExtraLength = distance * length; } else { float newLength = length * (1 - distance); mCurrentProgress += (mBarExtraLength - newLength); mBarExtraLength = newLength; } } else { mPausedTimeWithoutGrowing += deltaTimeInMillis; } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { saveButtonOriginalPosition(); if (mShouldProgressIndeterminate) { setIndeterminate(true); mShouldProgressIndeterminate = false; } else if (mShouldSetProgress) { setProgress(mProgress, mAnimateProgress); mShouldSetProgress = false; } else if (mShouldUpdateButtonPosition) { updateButtonPosition(); mShouldUpdateButtonPosition = false; } super.onSizeChanged(w, h, oldw, oldh); setupProgressBounds(); setupProgressBarPaints(); updateBackground(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public void setLayoutParams(ViewGroup.LayoutParams params) { if (params instanceof ViewGroup.MarginLayoutParams && mUsingElevationCompat) { ((ViewGroup.MarginLayoutParams) params).leftMargin += getShadowX(); ((ViewGroup.MarginLayoutParams) params).topMargin += getShadowY(); ((ViewGroup.MarginLayoutParams) params).rightMargin += getShadowX(); ((ViewGroup.MarginLayoutParams) params).bottomMargin += getShadowY(); } super.setLayoutParams(params); } void updateBackground() { LayerDrawable layerDrawable; if (hasShadow()) { layerDrawable = new LayerDrawable(new Drawable[]{ new Shadow(), createFillDrawable(), getIconDrawable() }); } else { layerDrawable = new LayerDrawable(new Drawable[]{ createFillDrawable(), getIconDrawable() }); } int iconSize = -1; if (getIconDrawable() != null) { iconSize = Math.max(getIconDrawable().getIntrinsicWidth(), getIconDrawable().getIntrinsicHeight()); } int iconOffset = (getCircleSize() - (iconSize > 0 ? iconSize : mIconSize)) / 2; int circleInsetHorizontal = hasShadow() ? mShadowRadius + Math.abs(mShadowXOffset) : 0; int circleInsetVertical = hasShadow() ? mShadowRadius + Math.abs(mShadowYOffset) : 0; if (mProgressBarEnabled) { circleInsetHorizontal += mProgressWidth; circleInsetVertical += mProgressWidth; } /*layerDrawable.setLayerInset( mShowShadow ? 1 : 0, circleInsetHorizontal, circleInsetVertical, circleInsetHorizontal, circleInsetVertical );*/ layerDrawable.setLayerInset( hasShadow() ? 2 : 1, circleInsetHorizontal + iconOffset, circleInsetVertical + iconOffset, circleInsetHorizontal + iconOffset, circleInsetVertical + iconOffset ); setBackgroundCompat(layerDrawable); } protected Drawable getIconDrawable() { if (mIcon != null) { return mIcon; } else { return new ColorDrawable(Color.TRANSPARENT); } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private Drawable createFillDrawable() { StateListDrawable drawable = new StateListDrawable(); drawable.addState(new int[]{-android.R.attr.state_enabled}, createCircleDrawable(mColorDisabled)); drawable.addState(new int[]{android.R.attr.state_pressed}, createCircleDrawable(mColorPressed)); drawable.addState(new int[]{}, createCircleDrawable(mColorNormal)); if (Util.hasLollipop()) { RippleDrawable ripple = new RippleDrawable(new ColorStateList(new int[][]{{}}, new int[]{mColorRipple}), drawable, null); setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setOval(0, 0, view.getWidth(), view.getHeight()); } }); setClipToOutline(true); mBackgroundDrawable = ripple; return ripple; } mBackgroundDrawable = drawable; return drawable; } private Drawable createCircleDrawable(int color) { CircleDrawable shapeDrawable = new CircleDrawable(new OvalShape()); shapeDrawable.getPaint().setColor(color); return shapeDrawable; } @SuppressWarnings("deprecation") @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void setBackgroundCompat(Drawable drawable) { if (Util.hasJellyBean()) { setBackground(drawable); } else { setBackgroundDrawable(drawable); } } private void saveButtonOriginalPosition() { if (!mButtonPositionSaved) { if (mOriginalX == -1) { mOriginalX = getX(); } if (mOriginalY == -1) { mOriginalY = getY(); } mButtonPositionSaved = true; } } private void updateButtonPosition() { float x; float y; if (mProgressBarEnabled) { x = mOriginalX > getX() ? getX() + mProgressWidth : getX() - mProgressWidth; y = mOriginalY > getY() ? getY() + mProgressWidth : getY() - mProgressWidth; } else { x = mOriginalX; y = mOriginalY; } setX(x); setY(y); } private void setupProgressBarPaints() { mBackgroundPaint.setColor(mProgressBackgroundColor); mBackgroundPaint.setStyle(Paint.Style.STROKE); mBackgroundPaint.setStrokeWidth(mProgressWidth); mProgressPaint.setColor(mProgressColor); mProgressPaint.setStyle(Paint.Style.STROKE); mProgressPaint.setStrokeWidth(mProgressWidth); } private void setupProgressBounds() { int circleInsetHorizontal = hasShadow() ? getShadowX() : 0; int circleInsetVertical = hasShadow() ? getShadowY() : 0; mProgressCircleBounds = new RectF( circleInsetHorizontal + mProgressWidth / 2, circleInsetVertical + mProgressWidth / 2, calculateMeasuredWidth() - circleInsetHorizontal - mProgressWidth / 2, calculateMeasuredHeight() - circleInsetVertical - mProgressWidth / 2 ); } Animation getShowAnimation() { return mShowAnimation; } Animation getHideAnimation() { return mHideAnimation; } void playShowAnimation() { mHideAnimation.cancel(); startAnimation(mShowAnimation); } void playHideAnimation() { mShowAnimation.cancel(); startAnimation(mHideAnimation); } OnClickListener getOnClickListener() { return mClickListener; } Label getLabelView() { return (Label) getTag(R.id.fab_label); } void setColors(int colorNormal, int colorPressed, int colorRipple) { mColorNormal = colorNormal; mColorPressed = colorPressed; mColorRipple = colorRipple; } @TargetApi(Build.VERSION_CODES.LOLLIPOP) void onActionDown() { if (mBackgroundDrawable instanceof StateListDrawable) { StateListDrawable drawable = (StateListDrawable) mBackgroundDrawable; drawable.setState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}); } else if (Util.hasLollipop()) { RippleDrawable ripple = (RippleDrawable) mBackgroundDrawable; ripple.setState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}); ripple.setHotspot(calculateCenterX(), calculateCenterY()); ripple.setVisible(true, true); } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) void onActionUp() { if (mBackgroundDrawable instanceof StateListDrawable) { StateListDrawable drawable = (StateListDrawable) mBackgroundDrawable; drawable.setState(new int[]{android.R.attr.state_enabled}); } else if (Util.hasLollipop()) { RippleDrawable ripple = (RippleDrawable) mBackgroundDrawable; ripple.setState(new int[]{android.R.attr.state_enabled}); ripple.setHotspot(calculateCenterX(), calculateCenterY()); ripple.setVisible(true, true); } } @Override public boolean onTouchEvent(MotionEvent event) { if (mClickListener != null && isEnabled()) { Label label = (Label) getTag(R.id.fab_label); if (label == null) return super.onTouchEvent(event); int action = event.getAction(); switch (action) { case MotionEvent.ACTION_UP: if (label != null) { label.onActionUp(); } break; } mGestureDetector.onTouchEvent(event); } return super.onTouchEvent(event); } GestureDetector mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { Label label = (Label) getTag(R.id.fab_label); if (label != null) { label.onActionDown(); } onActionDown(); return super.onDown(e); } @Override public boolean onSingleTapUp(MotionEvent e) { Label label = (Label) getTag(R.id.fab_label); if (label != null) { label.onActionUp(); } onActionUp(); return super.onSingleTapUp(e); } }); @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); ProgressSavedState ss = new ProgressSavedState(superState); ss.mCurrentProgress = this.mCurrentProgress; ss.mTargetProgress = this.mTargetProgress; ss.mSpinSpeed = this.mSpinSpeed; ss.mProgressWidth = this.mProgressWidth; ss.mProgressColor = this.mProgressColor; ss.mProgressBackgroundColor = this.mProgressBackgroundColor; ss.mShouldProgressIndeterminate = this.mProgressIndeterminate; ss.mShouldSetProgress = this.mProgressBarEnabled && mProgress > 0 && !this.mProgressIndeterminate; ss.mProgress = this.mProgress; ss.mAnimateProgress = this.mAnimateProgress; ss.mShowProgressBackground = this.mShowProgressBackground; return ss; } @Override public void onRestoreInstanceState(Parcelable state) { if (!(state instanceof ProgressSavedState)) { super.onRestoreInstanceState(state); return; } ProgressSavedState ss = (ProgressSavedState) state; super.onRestoreInstanceState(ss.getSuperState()); this.mCurrentProgress = ss.mCurrentProgress; this.mTargetProgress = ss.mTargetProgress; this.mSpinSpeed = ss.mSpinSpeed; this.mProgressWidth = ss.mProgressWidth; this.mProgressColor = ss.mProgressColor; this.mProgressBackgroundColor = ss.mProgressBackgroundColor; this.mShouldProgressIndeterminate = ss.mShouldProgressIndeterminate; this.mShouldSetProgress = ss.mShouldSetProgress; this.mProgress = ss.mProgress; this.mAnimateProgress = ss.mAnimateProgress; this.mShowProgressBackground = ss.mShowProgressBackground; this.mLastTimeAnimated = SystemClock.uptimeMillis(); } private class CircleDrawable extends ShapeDrawable { private int circleInsetHorizontal; private int circleInsetVertical; private CircleDrawable() { } private CircleDrawable(Shape s) { super(s); circleInsetHorizontal = hasShadow() ? mShadowRadius + Math.abs(mShadowXOffset) : 0; circleInsetVertical = hasShadow() ? mShadowRadius + Math.abs(mShadowYOffset) : 0; if (mProgressBarEnabled) { circleInsetHorizontal += mProgressWidth; circleInsetVertical += mProgressWidth; } } @Override public void draw(Canvas canvas) { setBounds(circleInsetHorizontal, circleInsetVertical, calculateMeasuredWidth() - circleInsetHorizontal, calculateMeasuredHeight() - circleInsetVertical); super.draw(canvas); } } private class Shadow extends Drawable { private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Paint mErase = new Paint(Paint.ANTI_ALIAS_FLAG); private float mRadius; private Shadow() { this.init(); } private void init() { setLayerType(LAYER_TYPE_SOFTWARE, null); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mColorNormal); mErase.setXfermode(PORTER_DUFF_CLEAR); if (!isInEditMode()) { mPaint.setShadowLayer(mShadowRadius, mShadowXOffset, mShadowYOffset, mShadowColor); } mRadius = getCircleSize() / 2; if (mProgressBarEnabled && mShowProgressBackground) { mRadius += mProgressWidth; } } @Override public void draw(Canvas canvas) { canvas.drawCircle(calculateCenterX(), calculateCenterY(), mRadius, mPaint); canvas.drawCircle(calculateCenterX(), calculateCenterY(), mRadius, mErase); } @Override public void setAlpha(int alpha) { } @Override public void setColorFilter(ColorFilter cf) { } @Override public int getOpacity() { return 0; } } static class ProgressSavedState extends BaseSavedState { float mCurrentProgress; float mTargetProgress; float mSpinSpeed; int mProgress; int mProgressWidth; int mProgressColor; int mProgressBackgroundColor; boolean mProgressBarEnabled; boolean mProgressBarVisibilityChanged; boolean mProgressIndeterminate; boolean mShouldProgressIndeterminate; boolean mShouldSetProgress; boolean mAnimateProgress; boolean mShowProgressBackground; ProgressSavedState(Parcelable superState) { super(superState); } private ProgressSavedState(Parcel in) { super(in); this.mCurrentProgress = in.readFloat(); this.mTargetProgress = in.readFloat(); this.mProgressBarEnabled = in.readInt() != 0; this.mSpinSpeed = in.readFloat(); this.mProgress = in.readInt(); this.mProgressWidth = in.readInt(); this.mProgressColor = in.readInt(); this.mProgressBackgroundColor = in.readInt(); this.mProgressBarVisibilityChanged = in.readInt() != 0; this.mProgressIndeterminate = in.readInt() != 0; this.mShouldProgressIndeterminate = in.readInt() != 0; this.mShouldSetProgress = in.readInt() != 0; this.mAnimateProgress = in.readInt() != 0; this.mShowProgressBackground = in.readInt() != 0; } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeFloat(this.mCurrentProgress); out.writeFloat(this.mTargetProgress); out.writeInt((mProgressBarEnabled ? 1 : 0)); out.writeFloat(this.mSpinSpeed); out.writeInt(this.mProgress); out.writeInt(this.mProgressWidth); out.writeInt(this.mProgressColor); out.writeInt(this.mProgressBackgroundColor); out.writeInt(this.mProgressBarVisibilityChanged ? 1 : 0); out.writeInt(this.mProgressIndeterminate ? 1 : 0); out.writeInt(this.mShouldProgressIndeterminate ? 1 : 0); out.writeInt(this.mShouldSetProgress ? 1 : 0); out.writeInt(this.mAnimateProgress ? 1 : 0); out.writeInt(this.mShowProgressBackground ? 1 : 0); } public static final Parcelable.Creator<ProgressSavedState> CREATOR = new Parcelable.Creator<ProgressSavedState>() { public ProgressSavedState createFromParcel(Parcel in) { return new ProgressSavedState(in); } public ProgressSavedState[] newArray(int size) { return new ProgressSavedState[size]; } }; } /* ===== API methods ===== */ @Override public void setImageDrawable(Drawable drawable) { if (mIcon != drawable) { mIcon = drawable; updateBackground(); } } @Override public void setImageResource(int resId) { Drawable drawable = getResources().getDrawable(resId); if (mIcon != drawable) { mIcon = drawable; updateBackground(); } } @Override public void setOnClickListener(final OnClickListener l) { super.setOnClickListener(l); mClickListener = l; View label = (View) getTag(R.id.fab_label); if (label != null) { label.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mClickListener != null) { mClickListener.onClick(FloatingActionButton.this); } } }); } } /** * Sets the size of the <b>FloatingActionButton</b> and invalidates its layout. * * @param size size of the <b>FloatingActionButton</b>. Accepted values: SIZE_NORMAL, SIZE_MINI. */ public void setButtonSize(int size) { if (size != SIZE_NORMAL && size != SIZE_MINI) { throw new IllegalArgumentException("Use @FabSize constants only!"); } if (mFabSize != size) { mFabSize = size; updateBackground(); } } public int getButtonSize() { return mFabSize; } public void setColorNormal(int color) { if (mColorNormal != color) { mColorNormal = color; updateBackground(); } } public void setColorNormalResId(int colorResId) { setColorNormal(getResources().getColor(colorResId)); } public int getColorNormal() { return mColorNormal; } public void setColorPressed(int color) { if (color != mColorPressed) { mColorPressed = color; updateBackground(); } } public void setColorPressedResId(int colorResId) { setColorPressed(getResources().getColor(colorResId)); } public int getColorPressed() { return mColorPressed; } public void setColorRipple(int color) { if (color != mColorRipple) { mColorRipple = color; updateBackground(); } } public void setColorRippleResId(int colorResId) { setColorRipple(getResources().getColor(colorResId)); } public int getColorRipple() { return mColorRipple; } public void setShowShadow(boolean show) { if (mShowShadow != show) { mShowShadow = show; updateBackground(); } } public boolean hasShadow() { return !mUsingElevation && mShowShadow; } /** * Sets the shadow radius of the <b>FloatingActionButton</b> and invalidates its layout. * * @param dimenResId the resource identifier of the dimension */ public void setShadowRadius(int dimenResId) { int shadowRadius = getResources().getDimensionPixelSize(dimenResId); if (mShadowRadius != shadowRadius) { mShadowRadius = shadowRadius; requestLayout(); updateBackground(); } } /** * Sets the shadow radius of the <b>FloatingActionButton</b> and invalidates its layout. * <p> * Must be specified in density-independent (dp) pixels, which are then converted into actual * pixels (px). * * @param shadowRadiusDp shadow radius specified in density-independent (dp) pixels */ public void setShadowRadius(float shadowRadiusDp) { mShadowRadius = Util.dpToPx(getContext(), shadowRadiusDp); requestLayout(); updateBackground(); } public int getShadowRadius() { return mShadowRadius; } /** * Sets the shadow x offset of the <b>FloatingActionButton</b> and invalidates its layout. * * @param dimenResId the resource identifier of the dimension */ public void setShadowXOffset(int dimenResId) { int shadowXOffset = getResources().getDimensionPixelSize(dimenResId); if (mShadowXOffset != shadowXOffset) { mShadowXOffset = shadowXOffset; requestLayout(); updateBackground(); } } /** * Sets the shadow x offset of the <b>FloatingActionButton</b> and invalidates its layout. * <p> * Must be specified in density-independent (dp) pixels, which are then converted into actual * pixels (px). * * @param shadowXOffsetDp shadow radius specified in density-independent (dp) pixels */ public void setShadowXOffset(float shadowXOffsetDp) { mShadowXOffset = Util.dpToPx(getContext(), shadowXOffsetDp); requestLayout(); updateBackground(); } public int getShadowXOffset() { return mShadowXOffset; } /** * Sets the shadow y offset of the <b>FloatingActionButton</b> and invalidates its layout. * * @param dimenResId the resource identifier of the dimension */ public void setShadowYOffset(int dimenResId) { int shadowYOffset = getResources().getDimensionPixelSize(dimenResId); if (mShadowYOffset != shadowYOffset) { mShadowYOffset = shadowYOffset; requestLayout(); updateBackground(); } } /** * Sets the shadow y offset of the <b>FloatingActionButton</b> and invalidates its layout. * <p> * Must be specified in density-independent (dp) pixels, which are then converted into actual * pixels (px). * * @param shadowYOffsetDp shadow radius specified in density-independent (dp) pixels */ public void setShadowYOffset(float shadowYOffsetDp) { mShadowYOffset = Util.dpToPx(getContext(), shadowYOffsetDp); requestLayout(); updateBackground(); } public int getShadowYOffset() { return mShadowYOffset; } public void setShadowColorResource(int colorResId) { int shadowColor = getResources().getColor(colorResId); if (mShadowColor != shadowColor) { mShadowColor = shadowColor; updateBackground(); } } public void setShadowColor(int color) { if (mShadowColor != color) { mShadowColor = color; updateBackground(); } } public int getShadowColor() { return mShadowColor; } /** * Checks whether <b>FloatingActionButton</b> is hidden * * @return true if <b>FloatingActionButton</b> is hidden, false otherwise */ public boolean isHidden() { return getVisibility() == INVISIBLE; } /** * Makes the <b>FloatingActionButton</b> to appear and sets its visibility to {@link #VISIBLE} * * @param animate if true - plays "show animation" */ public void show(boolean animate) { if (isHidden()) { if (animate) { playShowAnimation(); } super.setVisibility(VISIBLE); } } /** * Makes the <b>FloatingActionButton</b> to disappear and sets its visibility to {@link #INVISIBLE} * * @param animate if true - plays "hide animation" */ public void hide(boolean animate) { if (!isHidden()) { if (animate) { playHideAnimation(); } super.setVisibility(INVISIBLE); } } public void toggle(boolean animate) { if (isHidden()) { show(animate); } else { hide(animate); } } public void setLabelText(String text) { mLabelText = text; TextView labelView = getLabelView(); if (labelView != null) { labelView.setText(text); } } public String getLabelText() { return mLabelText; } public void setShowAnimation(Animation showAnimation) { mShowAnimation = showAnimation; } public void setHideAnimation(Animation hideAnimation) { mHideAnimation = hideAnimation; } public void setLabelVisibility(int visibility) { Label labelView = getLabelView(); if (labelView != null) { labelView.setVisibility(visibility); labelView.setHandleVisibilityChanges(visibility == VISIBLE); } } public int getLabelVisibility() { TextView labelView = getLabelView(); if (labelView != null) { return labelView.getVisibility(); } return -1; } @Override public void setElevation(float elevation) { if (Util.hasLollipop() && elevation > 0) { super.setElevation(elevation); if (!isInEditMode()) { mUsingElevation = true; mShowShadow = false; } updateBackground(); } } /** * Sets the shadow color and radius to mimic the native elevation. * * <p><b>API 21+</b>: Sets the native elevation of this view, in pixels. Updates margins to * make the view hold its position in layout across different platform versions.</p> */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void setElevationCompat(float elevation) { mShadowColor = 0x26000000; mShadowRadius = Math.round(elevation / 2); mShadowXOffset = 0; mShadowYOffset = Math.round(mFabSize == SIZE_NORMAL ? elevation : elevation / 2); if (Util.hasLollipop()) { super.setElevation(elevation); mUsingElevationCompat = true; mShowShadow = false; updateBackground(); ViewGroup.LayoutParams layoutParams = getLayoutParams(); if (layoutParams != null) { setLayoutParams(layoutParams); } } else { mShowShadow = true; updateBackground(); } } /** * <p>Change the indeterminate mode for the progress bar. In indeterminate * mode, the progress is ignored and the progress bar shows an infinite * animation instead.</p> * * @param indeterminate true to enable the indeterminate mode */ public synchronized void setIndeterminate(boolean indeterminate) { if (!indeterminate) { mCurrentProgress = 0.0f; } mProgressBarEnabled = indeterminate; mShouldUpdateButtonPosition = true; mProgressIndeterminate = indeterminate; mLastTimeAnimated = SystemClock.uptimeMillis(); setupProgressBounds(); saveButtonOriginalPosition(); updateBackground(); } public synchronized void setMax(int max) { mProgressMax = max; } public synchronized int getMax() { return mProgressMax; } public synchronized void setProgress(int progress, boolean animate) { if (mProgressIndeterminate) return; mProgress = progress; mAnimateProgress = animate; if (!mButtonPositionSaved) { mShouldSetProgress = true; return; } mProgressBarEnabled = true; mShouldUpdateButtonPosition = true; setupProgressBounds(); saveButtonOriginalPosition(); updateBackground(); if (progress < 0) { progress = 0; } else if (progress > mProgressMax) { progress = mProgressMax; } if (progress == mTargetProgress) { return; } mTargetProgress = mProgressMax > 0 ? (progress / (float) mProgressMax) * 360 : 0; mLastTimeAnimated = SystemClock.uptimeMillis(); if (!animate) { mCurrentProgress = mTargetProgress; } invalidate(); } public synchronized int getProgress() { return mProgressIndeterminate ? 0 : mProgress; } public synchronized void hideProgress() { mProgressBarEnabled = false; mShouldUpdateButtonPosition = true; updateBackground(); } public synchronized void setShowProgressBackground(boolean show) { mShowProgressBackground = show; } public synchronized boolean isProgressBackgroundShown() { return mShowProgressBackground; } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); Label label = (Label) getTag(R.id.fab_label); if (label != null) { label.setEnabled(enabled); } } @Override public void setVisibility(int visibility) { super.setVisibility(visibility); Label label = (Label) getTag(R.id.fab_label); if (label != null) { label.setVisibility(visibility); } } }