Respect a FloatingActionButton's visibility when anchored

Currently when a FAB is anchored to an AppBarLayout, it
controls the FAB's visibility (for the automatic seam
functionality). This meant that any user defined visibility
was ignored since we had no way of distinguishing what was
user defined or not.

This CL fixes that by recording what the user defined visibility
is, and only updates the FAB's visibility from the ABL if the user
has set it to be visible.

BUG: 24973851

Change-Id: Iee9e95a6eac551934844fbfdbd9ca8fe68bcb28b
diff --git a/design/api/current.txt b/design/api/current.txt
index 24a0b18..add48a3 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -218,7 +218,7 @@
     field public static final android.os.Parcelable.Creator<android.support.design.widget.CoordinatorLayout.SavedState> CREATOR;
   }
 
-  public class FloatingActionButton extends android.widget.ImageButton {
+  public class FloatingActionButton extends android.support.design.widget.VisibilityAwareImageButton {
     ctor public FloatingActionButton(android.content.Context);
     ctor public FloatingActionButton(android.content.Context, android.util.AttributeSet);
     ctor public FloatingActionButton(android.content.Context, android.util.AttributeSet, int);
@@ -446,5 +446,11 @@
     method public boolean setTopAndBottomOffset(int);
   }
 
+   class VisibilityAwareImageButton extends android.support.v7.widget.AppCompatImageButton {
+    ctor public VisibilityAwareImageButton(android.content.Context);
+    ctor public VisibilityAwareImageButton(android.content.Context, android.util.AttributeSet);
+    ctor public VisibilityAwareImageButton(android.content.Context, android.util.AttributeSet, int);
+  }
+
 }
 
diff --git a/design/base/android/support/design/widget/FloatingActionButtonImpl.java b/design/base/android/support/design/widget/FloatingActionButtonImpl.java
index dc7d3af..8c9e0ab 100644
--- a/design/base/android/support/design/widget/FloatingActionButtonImpl.java
+++ b/design/base/android/support/design/widget/FloatingActionButtonImpl.java
@@ -24,7 +24,6 @@
 import android.graphics.drawable.GradientDrawable;
 import android.support.annotation.Nullable;
 import android.support.design.R;
-import android.view.View;
 import android.view.ViewTreeObserver;
 
 abstract class FloatingActionButtonImpl {
@@ -42,12 +41,13 @@
             android.R.attr.state_enabled};
     static final int[] EMPTY_STATE_SET = new int[0];
 
-    final View mView;
+    final VisibilityAwareImageButton mView;
     final ShadowViewDelegate mShadowViewDelegate;
 
     private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
 
-    FloatingActionButtonImpl(View view, ShadowViewDelegate shadowViewDelegate) {
+    FloatingActionButtonImpl(VisibilityAwareImageButton view,
+            ShadowViewDelegate shadowViewDelegate) {
         mView = view;
         mShadowViewDelegate = shadowViewDelegate;
     }
@@ -69,9 +69,9 @@
 
     abstract void jumpDrawableToCurrentState();
 
-    abstract void hide(@Nullable InternalVisibilityChangedListener listener);
+    abstract void hide(@Nullable InternalVisibilityChangedListener listener, boolean fromUser);
 
-    abstract void show(@Nullable InternalVisibilityChangedListener listener);
+    abstract void show(@Nullable InternalVisibilityChangedListener listener, boolean fromUser);
 
     void onAttachedToWindow() {
         if (requirePreDrawListener()) {
diff --git a/design/base/android/support/design/widget/VisibilityAwareImageButton.java b/design/base/android/support/design/widget/VisibilityAwareImageButton.java
new file mode 100644
index 0000000..15bf685
--- /dev/null
+++ b/design/base/android/support/design/widget/VisibilityAwareImageButton.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * 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.
+ */
+
+package android.support.design.widget;
+
+import android.content.Context;
+import android.support.v7.widget.AppCompatImageButton;
+import android.util.AttributeSet;
+
+class VisibilityAwareImageButton extends AppCompatImageButton {
+
+    private int mUserSetVisibility;
+
+    public VisibilityAwareImageButton(Context context) {
+        this(context, null);
+    }
+
+    public VisibilityAwareImageButton(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public VisibilityAwareImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mUserSetVisibility = getVisibility();
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        internalSetVisibility(visibility, true);
+    }
+
+    final void internalSetVisibility(int visibility, boolean fromUser) {
+        super.setVisibility(visibility);
+        if (fromUser) {
+            mUserSetVisibility = visibility;
+        }
+    }
+
+    final int getUserSetVisibility() {
+        return mUserSetVisibility;
+    }
+}
diff --git a/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java b/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
index 253e392..577682f 100644
--- a/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
+++ b/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
@@ -48,7 +48,8 @@
 
     private boolean mIsHiding;
 
-    FloatingActionButtonEclairMr1(View view, ShadowViewDelegate shadowViewDelegate) {
+    FloatingActionButtonEclairMr1(VisibilityAwareImageButton view,
+            ShadowViewDelegate shadowViewDelegate) {
         super(view, shadowViewDelegate);
 
         mAnimationDuration = view.getResources().getInteger(android.R.integer.config_shortAnimTime);
@@ -155,7 +156,7 @@
     }
 
     @Override
-    void hide(@Nullable final InternalVisibilityChangedListener listener) {
+    void hide(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) {
         if (mIsHiding || mView.getVisibility() != View.VISIBLE) {
             // A hide animation is in progress, or we're already hidden. Skip the call
             if (listener != null) {
@@ -177,7 +178,7 @@
             @Override
             public void onAnimationEnd(Animation animation) {
                 mIsHiding = false;
-                mView.setVisibility(View.GONE);
+                mView.internalSetVisibility(View.GONE, fromUser);
                 if (listener != null) {
                     listener.onHidden();
                 }
@@ -187,12 +188,12 @@
     }
 
     @Override
-    void show(@Nullable final InternalVisibilityChangedListener listener) {
+    void show(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) {
         if (mView.getVisibility() != View.VISIBLE || mIsHiding) {
             // If the view is not visible, or is visible and currently being hidden, run
             // the show animation
             mView.clearAnimation();
-            mView.setVisibility(View.VISIBLE);
+            mView.internalSetVisibility(View.VISIBLE, fromUser);
             Animation anim = android.view.animation.AnimationUtils.loadAnimation(
                     mView.getContext(), R.anim.design_fab_in);
             anim.setDuration(SHOW_HIDE_ANIM_DURATION);
diff --git a/design/ics/android/support/design/widget/FloatingActionButtonIcs.java b/design/ics/android/support/design/widget/FloatingActionButtonIcs.java
index 28fcd9a..4c256bc 100644
--- a/design/ics/android/support/design/widget/FloatingActionButtonIcs.java
+++ b/design/ics/android/support/design/widget/FloatingActionButtonIcs.java
@@ -26,7 +26,8 @@
 
     private boolean mIsHiding;
 
-    FloatingActionButtonIcs(View view, ShadowViewDelegate shadowViewDelegate) {
+    FloatingActionButtonIcs(VisibilityAwareImageButton view,
+            ShadowViewDelegate shadowViewDelegate) {
         super(view, shadowViewDelegate);
     }
 
@@ -41,7 +42,7 @@
     }
 
     @Override
-    void hide(@Nullable final InternalVisibilityChangedListener listener) {
+    void hide(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) {
         if (mIsHiding || mView.getVisibility() != View.VISIBLE) {
             // A hide animation is in progress, or we're already hidden. Skip the call
             if (listener != null) {
@@ -52,7 +53,7 @@
 
         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);
+            mView.internalSetVisibility(View.GONE, fromUser);
             if (listener != null) {
                 listener.onHidden();
             }
@@ -71,7 +72,7 @@
                         public void onAnimationStart(Animator animation) {
                             mIsHiding = true;
                             mCancelled = false;
-                            mView.setVisibility(View.VISIBLE);
+                            mView.internalSetVisibility(View.VISIBLE, fromUser);
                         }
 
                         @Override
@@ -84,7 +85,7 @@
                         public void onAnimationEnd(Animator animation) {
                             mIsHiding = false;
                             if (!mCancelled) {
-                                mView.setVisibility(View.GONE);
+                                mView.internalSetVisibility(View.GONE, fromUser);
                                 if (listener != null) {
                                     listener.onHidden();
                                 }
@@ -95,7 +96,7 @@
     }
 
     @Override
-    void show(@Nullable final InternalVisibilityChangedListener listener) {
+    void show(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) {
         if (mIsHiding || mView.getVisibility() != View.VISIBLE) {
             if (ViewCompat.isLaidOut(mView) && !mView.isInEditMode()) {
                 mView.animate().cancel();
@@ -114,7 +115,7 @@
                         .setListener(new AnimatorListenerAdapter() {
                             @Override
                             public void onAnimationStart(Animator animation) {
-                                mView.setVisibility(View.VISIBLE);
+                                mView.internalSetVisibility(View.VISIBLE, fromUser);
                             }
 
                             @Override
@@ -125,7 +126,7 @@
                             }
                         });
             } else {
-                mView.setVisibility(View.VISIBLE);
+                mView.internalSetVisibility(View.VISIBLE, fromUser);
                 mView.setAlpha(1f);
                 mView.setScaleY(1f);
                 mView.setScaleX(1f);
diff --git a/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java b/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
index 49e4b0e..e68102d 100644
--- a/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
+++ b/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
@@ -28,7 +28,6 @@
 import android.os.Build;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.view.ViewCompat;
-import android.view.View;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
@@ -37,7 +36,8 @@
 
     private Interpolator mInterpolator;
 
-    FloatingActionButtonLollipop(View view, ShadowViewDelegate shadowViewDelegate) {
+    FloatingActionButtonLollipop(VisibilityAwareImageButton view,
+            ShadowViewDelegate shadowViewDelegate) {
         super(view, shadowViewDelegate);
 
         if (!view.isInEditMode()) {
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index dd39bc8..337f865 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -33,7 +33,6 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
-import android.widget.ImageButton;
 import android.widget.ImageView;
 
 import java.util.List;
@@ -56,7 +55,7 @@
  * @attr ref android.support.design.R.styleable#FloatingActionButton_fabSize
  */
 @CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
-public class FloatingActionButton extends ImageButton {
+public class FloatingActionButton extends VisibilityAwareImageButton {
 
     private static final String LOG_TAG = "FloatingActionButton";
 
@@ -271,7 +270,7 @@
      * <p>This method will animate the button show if the view has already been laid out.</p>
      */
     public void show() {
-        mImpl.show(null);
+        show(null);
     }
 
     /**
@@ -281,7 +280,11 @@
      * @param listener the listener to notify when this view is shown
      */
     public void show(@Nullable final OnVisibilityChangedListener listener) {
-        mImpl.show(wrapOnVisibilityChangedListener(listener));
+        show(listener, true);
+    }
+
+    private void show(OnVisibilityChangedListener listener, boolean fromUser) {
+        mImpl.show(wrapOnVisibilityChangedListener(listener), fromUser);
     }
 
     /**
@@ -289,7 +292,7 @@
      * <p>This method will animate the button hide if the view has already been laid out.</p>
      */
     public void hide() {
-        mImpl.hide(null);
+        hide(null);
     }
 
     /**
@@ -299,7 +302,11 @@
      * @param listener the listener to notify when this view is hidden
      */
     public void hide(@Nullable OnVisibilityChangedListener listener) {
-        mImpl.hide(wrapOnVisibilityChangedListener(listener));
+        hide(listener, true);
+    }
+
+    private void hide(@Nullable OnVisibilityChangedListener listener, boolean fromUser) {
+        mImpl.hide(wrapOnVisibilityChangedListener(listener), fromUser);
     }
 
     @Nullable
@@ -441,6 +448,11 @@
                 return false;
             }
 
+            if (child.getUserSetVisibility() != VISIBLE) {
+                // The view isn't set to be visible so skip changing it's visibility
+                return false;
+            }
+
             if (mTmpRect == null) {
                 mTmpRect = new Rect();
             }
@@ -451,10 +463,10 @@
 
             if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) {
                 // If the anchor's bottom is below the seam, we'll animate our FAB out
-                child.hide();
+                child.hide(null, false);
             } else {
                 // Else, we'll animate our FAB back in
-                child.show();
+                child.show(null, false);
             }
             return true;
         }