Add a listener to FloatingActionButton show() and hide().

Bug: 24053775
Change-Id: I36495bd56c48afc3249d3e09761c823a9b6c377a
diff --git a/design/api/current.txt b/design/api/current.txt
index a5feb3d..6c0fe9b 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -191,8 +191,10 @@
     ctor public FloatingActionButton(android.content.Context, android.util.AttributeSet);
     ctor public FloatingActionButton(android.content.Context, android.util.AttributeSet, int);
     method public void hide();
+    method public void hide(android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener);
     method public void setRippleColor(int);
     method public void show();
+    method public void show(android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener);
   }
 
   public static class FloatingActionButton.Behavior extends android.support.design.widget.CoordinatorLayout.Behavior {
@@ -203,6 +205,12 @@
     method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, int);
   }
 
+  public static abstract class FloatingActionButton.OnVisibilityChangedListener {
+    ctor public FloatingActionButton.OnVisibilityChangedListener();
+    method public void onHidden(android.support.design.widget.FloatingActionButton);
+    method public void onShown(android.support.design.widget.FloatingActionButton);
+  }
+
    abstract class HeaderBehavior extends android.support.design.widget.ViewOffsetBehavior {
     ctor public HeaderBehavior();
     ctor public HeaderBehavior(android.content.Context, android.util.AttributeSet);
@@ -404,17 +412,17 @@
     ctor public TextInputLayout(android.content.Context);
     ctor public TextInputLayout(android.content.Context, android.util.AttributeSet);
     ctor public TextInputLayout(android.content.Context, android.util.AttributeSet, int);
+    method public int getCounterMaxLength();
     method public android.widget.EditText getEditText();
     method public java.lang.CharSequence getError();
     method public java.lang.CharSequence getHint();
     method public android.graphics.Typeface getTypeface();
     method public boolean isErrorEnabled();
     method public boolean isHintAnimationEnabled();
-    method public void setError(java.lang.CharSequence);
-    method public void setErrorEnabled(boolean);
     method public void setCounterEnabled(boolean);
     method public void setCounterMaxLength(int);
-    method public int getCounterMaxLength();
+    method public void setError(java.lang.CharSequence);
+    method public void setErrorEnabled(boolean);
     method public void setHint(java.lang.CharSequence);
     method public void setHintAnimationEnabled(boolean);
     method public void setHintTextAppearance(int);
diff --git a/design/base/android/support/design/widget/FloatingActionButtonImpl.java b/design/base/android/support/design/widget/FloatingActionButtonImpl.java
index 969e570..18b0d36 100644
--- a/design/base/android/support/design/widget/FloatingActionButtonImpl.java
+++ b/design/base/android/support/design/widget/FloatingActionButtonImpl.java
@@ -20,11 +20,17 @@
 import android.content.res.Resources;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
 import android.support.design.R;
 import android.view.View;
 
 abstract class FloatingActionButtonImpl {
 
+    interface InternalVisibilityChangedListener {
+        public void onShown();
+        public void onHidden();
+    }
+
     static final int SHOW_HIDE_ANIM_DURATION = 200;
 
     static final int[] PRESSED_ENABLED_STATE_SET = {android.R.attr.state_pressed,
@@ -58,9 +64,9 @@
 
     abstract void jumpDrawableToCurrentState();
 
-    abstract void hide();
+    abstract void hide(@Nullable InternalVisibilityChangedListener listener);
 
-    abstract void show();
+    abstract void show(@Nullable InternalVisibilityChangedListener listener);
 
     Drawable createBorderDrawable(int borderWidth, ColorStateList backgroundTint) {
         final Resources resources = mView.getResources();
diff --git a/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java b/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
index fd0b045..8ff74a1 100644
--- a/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
+++ b/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
@@ -24,7 +24,9 @@
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.LayerDrawable;
 import android.os.Build;
+import android.support.annotation.Nullable;
 import android.support.design.R;
+import android.support.design.widget.AnimationUtils.AnimationListenerAdapter;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.view.View;
 import android.view.animation.Animation;
@@ -167,9 +169,12 @@
     }
 
     @Override
-    void hide() {
+    void hide(@Nullable final InternalVisibilityChangedListener listener) {
         if (mIsHiding || mView.getVisibility() != View.VISIBLE) {
             // A hide animation is in progress, or we're already hidden. Skip the call
+            if (listener != null) {
+                listener.onHidden();
+            }
             return;
         }
 
@@ -187,13 +192,16 @@
             public void onAnimationEnd(Animation animation) {
                 mIsHiding = false;
                 mView.setVisibility(View.GONE);
+                if (listener != null) {
+                    listener.onHidden();
+                }
             }
         });
         mView.startAnimation(anim);
     }
 
     @Override
-    void show() {
+    void show(@Nullable final InternalVisibilityChangedListener listener) {
         if (mView.getVisibility() != View.VISIBLE || mIsHiding) {
             // If the view is not visible, or is visible and currently being hidden, run
             // the show animation
@@ -203,7 +211,19 @@
                     mView.getContext(), R.anim.design_fab_in);
             anim.setDuration(SHOW_HIDE_ANIM_DURATION);
             anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
+            anim.setAnimationListener(new AnimationListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animation animation) {
+                    if (listener != null) {
+                        listener.onShown();
+                    }
+                }
+            });
             mView.startAnimation(anim);
+        } else {
+            if (listener != null) {
+                listener.onShown();
+            }
         }
     }
 
diff --git a/design/honeycomb-mr1/android/support/design/widget/FloatingActionButtonHoneycombMr1.java b/design/honeycomb-mr1/android/support/design/widget/FloatingActionButtonHoneycombMr1.java
index 72ae009..786d063 100644
--- a/design/honeycomb-mr1/android/support/design/widget/FloatingActionButtonHoneycombMr1.java
+++ b/design/honeycomb-mr1/android/support/design/widget/FloatingActionButtonHoneycombMr1.java
@@ -18,6 +18,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.support.annotation.Nullable;
 import android.support.v4.view.ViewCompat;
 import android.view.View;
 
@@ -30,15 +31,21 @@
     }
 
     @Override
-    void hide() {
+    void hide(@Nullable final InternalVisibilityChangedListener listener) {
         if (mIsHiding || mView.getVisibility() != View.VISIBLE) {
             // A hide animation is in progress, or we're already hidden. Skip the call
+            if (listener != null) {
+                listener.onHidden();
+            }
             return;
         }
 
         if (!ViewCompat.isLaidOut(mView) || mView.isInEditMode()) {
             // If the view isn't laid out, or we're in the editor, don't run the animation
             mView.setVisibility(View.GONE);
+            if (listener != null) {
+                listener.onHidden();
+            }
         } else {
             mView.animate()
                     .scaleX(0f)
@@ -62,13 +69,16 @@
                         public void onAnimationEnd(Animator animation) {
                             mIsHiding = false;
                             mView.setVisibility(View.GONE);
+                            if (listener != null) {
+                                listener.onHidden();
+                            }
                         }
                     });
         }
     }
 
     @Override
-    void show() {
+    void show(@Nullable final InternalVisibilityChangedListener listener) {
         if (mView.getVisibility() != View.VISIBLE) {
             if (ViewCompat.isLaidOut(mView) && !mView.isInEditMode()) {
                 mView.setAlpha(0f);
@@ -85,12 +95,22 @@
                             public void onAnimationStart(Animator animation) {
                                 mView.setVisibility(View.VISIBLE);
                             }
+
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                if (listener != null) {
+                                    listener.onShown();
+                                }
+                            }
                         });
             } else {
                 mView.setVisibility(View.VISIBLE);
                 mView.setAlpha(1f);
                 mView.setScaleY(1f);
                 mView.setScaleX(1f);
+                if (listener != null) {
+                    listener.onShown();
+                }
             }
         }
     }
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index fda9985..a8f1446 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -28,6 +28,7 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.design.R;
+import android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener;
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.view.View;
@@ -56,6 +57,27 @@
 @CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
 public class FloatingActionButton extends ImageButton {
 
+    /**
+     * Callback to be invoked when the visibility of a FloatingActionButton changes.
+     */
+    public abstract static class OnVisibilityChangedListener {
+        /**
+         * Called when a FloatingActionButton has been
+         * {@link #show(OnVisibilityChangedListener) shown}.
+         *
+         * @param fab the FloatingActionButton that was shown.
+         */
+        public void onShown(FloatingActionButton fab) {}
+
+        /**
+         * Called when a FloatingActionButton has been
+         * {@link #hide(OnVisibilityChangedListener) hidden}.
+         *
+         * @param fab the FloatingActionButton that was hidden.
+         */
+        public void onHidden(FloatingActionButton fab) {}
+    }
+
     // These values must match those in the attrs declaration
     private static final int SIZE_MINI = 1;
     private static final int SIZE_NORMAL = 0;
@@ -242,7 +264,17 @@
      * <p>This method will animate it the button show if the view has already been laid out.</p>
      */
     public void show() {
-        mImpl.show();
+        mImpl.show(null);
+    }
+
+    /**
+     * Shows the button.
+     * <p>This method will animate it the button show if the view has already been laid out.</p>
+     *
+     * @param listener the listener to notify when this view is shown
+     */
+    public void show(@Nullable final OnVisibilityChangedListener listener) {
+        mImpl.show(wrapOnVisibilityChangedListener(listener));
     }
 
     /**
@@ -250,7 +282,37 @@
      * <p>This method will animate the button hide if the view has already been laid out.</p>
      */
     public void hide() {
-        mImpl.hide();
+        mImpl.hide(null);
+    }
+
+    /**
+     * Hides the button.
+     * <p>This method will animate the button hide if the view has already been laid out.</p>
+     *
+     * @param listener the listener to notify when this view is hidden
+     */
+    public void hide(@Nullable OnVisibilityChangedListener listener) {
+        mImpl.hide(wrapOnVisibilityChangedListener(listener));
+    }
+
+    @Nullable
+    private InternalVisibilityChangedListener wrapOnVisibilityChangedListener(
+            @Nullable final OnVisibilityChangedListener listener) {
+        if (listener == null) {
+            return null;
+        }
+
+        return new InternalVisibilityChangedListener() {
+            @Override
+            public void onShown() {
+                listener.onShown(FloatingActionButton.this);
+            }
+
+            @Override
+            public void onHidden() {
+                listener.onHidden(FloatingActionButton.this);
+            }
+        };
     }
 
     final int getSizeDimension() {