Separate ExpandableWidget and ExpandableTransformationWidget.

Separate ExpandableBehavior and ExpandableTransformationBehavior.

ExpandableWidget - for widgets that have expanded state, like BackLayerLayout:
{g,s}etExpanded()
on{Save,Restore}InstanceState()

ExpandableBehavior - for behaviors that want to react to expanded state change, like BackLayerSiblingBehavior:
onLayoutChild() // Layouts the content, and animates the content to a new expanded position when BLL's expanded children change size.
onExpandedStateChange() // Animates the content position when expanded state changes.

ExpandableTransformationWidget - for widgets that transform during expansion, like FAB:
{g,s}etExpandedComponentIdHint()
Will also include these in the future:
getBackgroundDrawable() // Shape drawable includes color, bounds, shape, elevation
getForegroundDrawable()
etc

ExpandableTransformationBehavior - for behaviors that want to react to expanded state change by transforming, like FabTransformationSheetBehavior:
onCreateExpandedStateChangeAnimation() // Animates the FAB and sheet when expanded state changes.

PiperOrigin-RevId: 162809090
This commit is contained in:
markwei 2017-07-21 21:39:20 -04:00 committed by Miguel Yanez Barreto
parent 75204528b9
commit 64f930f2a7
9 changed files with 369 additions and 211 deletions

View File

@ -16,23 +16,21 @@
package android.support.design.stateful;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.RestrictTo;
import android.support.v4.util.SimpleArrayMap;
import android.support.v4.view.AbsSavedState;
/**
* Class for widgets that want to save and restore their own state in {@link
* SavedState for widgets that want to save and restore their own state in {@link
* android.view.View#onSaveInstanceState()}. Supports widgets whose state is composed or delegated
* out to multiple components.
*
* @hide
* <p>Widgets with only composed or delegated state can directly instantiate this class and write to
* {@link #extendableStates}. Widgets with additional state should subclass ExtendableSavedState
* rather than trying to force the additional state into {@link #extendableStates}.
*/
@RestrictTo(LIBRARY_GROUP)
public class ExtendableSavedState extends AbsSavedState {
public final SimpleArrayMap<String, Bundle> extendableStates;

View File

@ -38,7 +38,7 @@ import android.support.annotation.VisibleForTesting;
import android.support.design.R;
import android.support.design.stateful.ExtendableSavedState;
import android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener;
import android.support.design.widget.expandable.ExpandableWidget;
import android.support.design.widget.expandable.ExpandableTransformationWidget;
import android.support.design.widget.expandable.ExpandableWidgetHelper;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.AppCompatImageHelper;
@ -69,9 +69,11 @@ import java.util.List;
* #setBackgroundTintList(ColorStateList)}.
*/
@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
public class FloatingActionButton extends VisibilityAwareImageButton implements ExpandableWidget {
public class FloatingActionButton extends VisibilityAwareImageButton
implements ExpandableTransformationWidget {
private static final String LOG_TAG = "FloatingActionButton";
private static final String EXPANDABLE_WIDGET_HELPER_KEY = "expandableWidgetHelper";
/** Callback to be invoked when the visibility of a FloatingActionButton changes. */
public abstract static class OnVisibilityChangedListener {
@ -359,8 +361,8 @@ public class FloatingActionButton extends VisibilityAwareImageButton implements
}
@Override
public void setExpanded(boolean expanded) {
expandableWidgetHelper.setExpanded(expanded);
public boolean setExpanded(boolean expanded) {
return expandableWidgetHelper.setExpanded(expanded);
}
@Override
@ -501,18 +503,18 @@ public class FloatingActionButton extends VisibilityAwareImageButton implements
}
@Override
protected Parcelable onSaveInstanceState() {
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
ExtendableSavedState state = new ExtendableSavedState(superState);
state.extendableStates.put(
"expandableWidgetHelper", expandableWidgetHelper.onSaveInstanceState());
EXPANDABLE_WIDGET_HELPER_KEY, expandableWidgetHelper.onSaveInstanceState());
return state;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof ExtendableSavedState)) {
super.onRestoreInstanceState(state);
return;
@ -522,7 +524,7 @@ public class FloatingActionButton extends VisibilityAwareImageButton implements
super.onRestoreInstanceState(ess.getSuperState());
expandableWidgetHelper.onRestoreInstanceState(
ess.extendableStates.get("expandableWidgetHelper"));
ess.extendableStates.get(EXPANDABLE_WIDGET_HELPER_KEY));
}
/**

View File

@ -0,0 +1,35 @@
/*
* Copyright 2017 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.expandable;
import android.support.annotation.IdRes;
/**
* An ExpandableWidget that visually transforms into another component when expanded.
*/
public interface ExpandableTransformationWidget extends ExpandableWidget {
/** Returns the expanded component id hint. */
@IdRes
int getExpandedComponentIdHint();
/**
* Sets the expanded component id hint, which may be used by a Behavior to determine whether it
* should handle this widget's state change.
*/
void setExpandedComponentIdHint(@IdRes int expandedComponentIdHint);
}

View File

@ -16,29 +16,85 @@
package android.support.design.widget.expandable;
import android.support.annotation.IdRes;
import android.os.Bundle;
import android.os.Parcelable;
/**
* A widget that has expanded/collapsed state.
* A widget that has expanded/collapsed state. The state is saved across configuration changes. When
* the expanded state changes, {@link
* android.support.design.widget.CoordinatorLayout#dispatchDependentViewsChanged an event is
* dispatched} so that other widgets may react via a {@link
* android.support.design.widget.CoordinatorLayout.Behavior}.
*
* <p>When the expanded state changes, an event is dispatched so that other widgets may react via a
* {@link android.support.design.widget.CoordinatorLayout.Behavior}.
* <p>Implementations of this interface should create an instance of {@link ExpandableWidgetHelper}
* and forward all calls to it.
*/
public interface ExpandableWidget {
/** Returns whether this widget is expanded. */
/**
* Returns whether this widget is expanded.
*
* <p>Implementations should call {@link ExpandableWidgetHelper#isExpanded()}.
*/
boolean isExpanded();
/** Sets the expanded state on this widget. */
void setExpanded(boolean expanded);
/** Returns the expanded component id hint. */
@IdRes
int getExpandedComponentIdHint();
/**
* Sets the expanded state on this widget.
*
* <p>Implementations should call {@link ExpandableWidgetHelper#setExpanded(boolean)}.
*
* @return true if the expanded state changed as a result of this call.
*/
boolean setExpanded(boolean expanded);
/**
* Sets the expanded component id hint, which may be used by a Behavior to determine whether it
* should handle this widget's state change.
* Saves the expanded state on configuration change.
*
* <p>Implementations should call {@link ExpandableWidgetHelper#onSaveInstanceState()} and save
* the returned state along with the widget state.
*
* <p>For a correct implementation of this, use an {@link
* android.support.design.stateful.ExtendableSavedState}. See its class javadoc for more details.
*
* <pre><code>
* {@literal @}Override
* public Parcelable onSaveInstanceState() {
* Parcelable superState = super.onSaveInstanceState();
* ExtendableSavedState state = new ExtendableSavedState(superState);
*
* state.extendableStates.put(
* "expandableWidgetHelper", expandableWidgetHelper.onSaveInstanceState());
*
* return state;
* }
* </code></pre>
*/
void setExpandedComponentIdHint(@IdRes int expandedComponentIdHint);
Parcelable onSaveInstanceState();
/**
* Restores the expanded state on configuration change.
*
* <p>Implementations should call {@link ExpandableWidgetHelper#onRestoreInstanceState(Bundle)} to
* restore the saved state along with the widget state.
*
* <p>For a correct implementation of this, use an {@link
* android.support.design.stateful.ExtendableSavedState}. See its class javadoc for more details.
*
* <pre><code>
* {@literal @}Override
* public void onRestoreInstanceState(Parcelable state) {
* if (!(state instanceof ExtendableSavedState)) {
* super.onRestoreInstanceState(state);
* return;
* }
*
* ExtendableSavedState ess = (ExtendableSavedState) state;
* super.onRestoreInstanceState(ess.getSuperState());
*
* expandableWidgetHelper.onRestoreInstanceState(
* ess.extendableStates.get("expandableWidgetHelper"));
* }
* </code></pre>
*/
void onRestoreInstanceState(Parcelable state);
}

View File

@ -24,27 +24,30 @@ import android.view.View;
import android.view.ViewParent;
/**
* ExpandableWidgetHelper is a helper class for writing custom {@link ExpandableWidget
* ExpandableWidgets}. Please see each method's documentation when implementing your custom class.
* ExpandableWidgetHelper is a helper class for writing custom {@link ExpandableWidget}s and {@link
* ExpandableTransformationWidget}. Please see the interface documentation when implementing your
* custom class.
*/
public final class ExpandableWidgetHelper {
private final View widget;
private boolean expanded;
@IdRes private int expandedComponentIdHint;
private boolean expanded = false;
@IdRes private int expandedComponentIdHint = 0;
/** Call this from the constructor. */
public <T extends View & ExpandableWidget> ExpandableWidgetHelper(T widget) {
this.widget = widget;
public ExpandableWidgetHelper(ExpandableWidget widget) {
this.widget = (View) widget;
}
/** Call this from {@link ExpandableWidget#setExpanded(boolean)}. */
public void setExpanded(boolean expanded) {
public boolean setExpanded(boolean expanded) {
if (this.expanded != expanded) {
this.expanded = expanded;
dispatchExpandedStateChanged();
return true;
}
return false;
}
/** Call this from {@link ExpandableWidget#isExpanded()}. */
@ -52,20 +55,7 @@ public final class ExpandableWidgetHelper {
return expanded;
}
/** Call this from {@link ExpandableWidget#setExpandedComponentIdHint(int)}. */
public void setExpandedComponentIdHint(@IdRes int expandedComponentIdHint) {
this.expandedComponentIdHint = expandedComponentIdHint;
}
/** Call this from {@link ExpandableWidget#getExpandedComponentIdHint()}. */
@IdRes
public int getExpandedComponentIdHint() {
return expandedComponentIdHint;
}
/**
* Call this from {@link View#onSaveInstanceState()} after your widget's custom state is saved.
*/
/** Call this from {@link ExpandableWidget#onSaveInstanceState()}. */
public Bundle onSaveInstanceState() {
Bundle state = new Bundle();
state.putBoolean("expanded", expanded);
@ -74,10 +64,7 @@ public final class ExpandableWidgetHelper {
return state;
}
/**
* Call this from {@link View#onRestoreInstanceState(Parcelable)} after your widget's custom state
* is restored.
*/
/** Call this from {@link ExpandableWidget#onRestoreInstanceState(Parcelable)}. */
public void onRestoreInstanceState(Bundle state) {
expanded = state.getBoolean("expanded", false);
expandedComponentIdHint = state.getInt("expandedComponentIdHint", 0);
@ -87,6 +74,17 @@ public final class ExpandableWidgetHelper {
}
}
/** Call this from {@link ExpandableTransformationWidget#setExpandedComponentIdHint(int)}. */
public void setExpandedComponentIdHint(@IdRes int expandedComponentIdHint) {
this.expandedComponentIdHint = expandedComponentIdHint;
}
/** Call this from {@link ExpandableTransformationWidget#getExpandedComponentIdHint()}. */
@IdRes
public int getExpandedComponentIdHint() {
return expandedComponentIdHint;
}
private void dispatchExpandedStateChanged() {
ViewParent parent = widget.getParent();
if (parent instanceof CoordinatorLayout) {

View File

@ -0,0 +1,146 @@
/*
* Copyright 2017 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.transformation;
import android.content.Context;
import android.support.annotation.CallSuper;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.CoordinatorLayout.Behavior;
import android.support.design.widget.expandable.ExpandableWidget;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* Base Behavior for views that can react to an {@link ExpandableWidget}'s {@link
* ExpandableWidget#setExpanded(boolean)} state changes.
*/
public abstract class ExpandableBehavior extends Behavior<View> {
/** Unknown expanded state. */
private static final int STATE_UNKNOWN = 0;
/** Expanded state. */
private static final int STATE_EXPANDED = 1;
/** Collapsed state. */
private static final int STATE_COLLAPSED = 2;
@IntDef({STATE_UNKNOWN, STATE_EXPANDED, STATE_COLLAPSED})
@Retention(RetentionPolicy.SOURCE)
private @interface State {}
/**
* The current expanded state of this behavior. This state follows the expanded state of the
* {@link ExpandableWidget} dependency, and is updated in {@link #onLayoutChild(CoordinatorLayout,
* View, int)} and {@link #onDependentViewChanged(CoordinatorLayout, View, View)}.
*
* <p>This state may be {@link #STATE_UNKNOWN} before either of those callbacks have been invoked.
*/
@State private int currentState = STATE_UNKNOWN;
public ExpandableBehavior() {}
public ExpandableBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public abstract boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency);
/**
* Reacts to a change in expanded state. This callback is guaranteed to be called only once even
* if {@link ExpandableWidget#setExpanded(boolean)} is called multiple times with the same value.
* Upon configuration change, this callback is called with {@code animated} set to false.
*
* @param dependency the {@link ExpandableWidget} dependency containing the new expanded state.
* @param child the view that should react to the change in expanded state.
* @param expanded the new expanded state.
* @param animated true if {@link ExpandableWidget#setExpanded(boolean)} was called, false if
* restoring from a configuration change.
* @return true if the Behavior changed the child view's size or position, false otherwise.
*/
protected abstract boolean onExpandedStateChange(
ExpandableWidget dependency, View child, boolean expanded, boolean animated);
@CallSuper
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
if (!ViewCompat.isLaidOut(child)) {
ExpandableWidget dep = findExpandableWidget(parent, child);
if (dep != null) {
currentState = dep.isExpanded() ? STATE_EXPANDED : STATE_COLLAPSED;
onExpandedStateChange(dep, child, dep.isExpanded(), false);
}
}
return false;
}
@CallSuper
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
ExpandableWidget dep = (ExpandableWidget) dependency;
boolean expanded = dep.isExpanded();
if (didStateChange(expanded)) {
currentState = dep.isExpanded() ? STATE_EXPANDED : STATE_COLLAPSED;
return onExpandedStateChange(dep, child, dep.isExpanded(), true);
}
return false;
}
@Nullable
protected ExpandableWidget findExpandableWidget(CoordinatorLayout parent, View child) {
List<View> dependencies = parent.getDependencies(child);
for (int i = 0, size = dependencies.size(); i < size; i++) {
View dependency = dependencies.get(i);
if (layoutDependsOn(parent, child, dependency)) {
return (ExpandableWidget) dependency;
}
}
return null;
}
private boolean didStateChange(boolean expanded) {
return (expanded && ((currentState == STATE_UNKNOWN) || (currentState == STATE_COLLAPSED)))
|| (!expanded && ((currentState == STATE_UNKNOWN) || (currentState == STATE_EXPANDED)));
}
/**
* A utility function to get the {@link ExpandableBehavior} attached to the {@code view}.
*
* @param view The {@link View} that the {@link ExpandableBehavior} is attached to.
* @param klass The expected {@link Class} of the attached {@link ExpandableBehavior}.
* @return The {@link ExpandableBehavior} attached to the {@code view}.
*/
public static <T extends ExpandableBehavior> T from(View view, Class<T> klass) {
ViewGroup.LayoutParams params = view.getLayoutParams();
if (!(params instanceof CoordinatorLayout.LayoutParams)) {
throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
}
CoordinatorLayout.Behavior<?> behavior =
((CoordinatorLayout.LayoutParams) params).getBehavior();
if (!(behavior instanceof ExpandableBehavior)) {
throw new IllegalArgumentException("The view is not associated with ExpandableBehavior");
}
return klass.cast(behavior);
}
}

View File

@ -19,44 +19,22 @@ package android.support.design.widget.transformation;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.support.annotation.IntDef;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.CoordinatorLayout.Behavior;
import android.support.design.widget.expandable.ExpandableWidget;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* Base Behavior for views that a {@link ExpandableWidget} can transform into. Reacts to {@link
* ExpandableWidget#setExpanded(boolean)} state changes with an animation.
* Base Behavior for views that can react to an {@link ExpandableWidget}'s {@link
* ExpandableWidget#setExpanded(boolean)} state changes by transforming the ExpandableWidget into
* itself.
*
* <p>Behaviors should override {@link #onCreateExpandedStateChangeAnimation(ExpandableWidget, View,
* boolean)} to return an animation
*/
public abstract class ExpandableTransformationBehavior extends Behavior<View> {
/** Unknown expanded state. */
private static final int STATE_UNKNOWN = 0;
/** Expanded state. */
private static final int STATE_EXPANDED = 1;
/** Collapsed state. */
private static final int STATE_COLLAPSED = 2;
@IntDef({STATE_UNKNOWN, STATE_EXPANDED, STATE_COLLAPSED})
@Retention(RetentionPolicy.SOURCE)
private @interface State {}
/**
* The current expanded state of this behavior. This state follows the expanded state of the
* {@link ExpandableWidget} dependency, and is updated in {@link #onLayoutChild(CoordinatorLayout,
* View, int)} and {@link #onDependentViewChanged(CoordinatorLayout, View, View)}.
*
* <p>This state may be {@link #STATE_UNKNOWN} before either of those callbacks have been invoked.
*/
@State private int currentState = STATE_UNKNOWN;
public abstract class ExpandableTransformationBehavior extends ExpandableBehavior {
@Nullable private Animator currentAnimation;
@ -66,109 +44,46 @@ public abstract class ExpandableTransformationBehavior extends Behavior<View> {
super(context, attrs);
}
/**
* Creates an Animator to be played for this expanded state change.
*
* <p>If the new {@code expanded} state is true, the {@code dependency} should be hidden and the
* {@code child} should be shown.
*
* <p>If the new {@code expanded} state is false, the {@code dependency} should be shown and the
* {@code child} should be hidden.
*
* @param dependency the {@link ExpandableWidget} dependency containing the new expanded state.
* @param child the view that should react to the change in expanded state.
* @param expanded the new expanded state.
*/
@NonNull
protected abstract Animator onCreateExpandedStateChangeAnimation(
ExpandableWidget dependency, View child, boolean expanded);
@CallSuper
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof ExpandableWidget;
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
if (!ViewCompat.isLaidOut(child)) {
List<View> dependencies = parent.getDependencies(child);
ExpandableWidget dependency = findExpandableWidget(dependencies);
jumpToState(dependency, child);
}
return false;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
ExpandableWidget dep = (ExpandableWidget) dependency;
boolean expanded = dep.isExpanded();
if (!didStateChange(expanded)) {
return false;
}
protected boolean onExpandedStateChange(
ExpandableWidget dependency, View child, boolean expanded, boolean animated) {
if (currentAnimation != null) {
currentAnimation.cancel();
}
currentState = expanded ? STATE_EXPANDED : STATE_COLLAPSED;
currentAnimation = createAnimation(dep, child);
currentAnimation = onCreateExpandedStateChangeAnimation(dependency, child, expanded);
currentAnimation.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (expanded) {
child.setVisibility(View.VISIBLE);
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (!expanded) {
child.setVisibility(View.INVISIBLE);
}
currentAnimation = null;
}
});
currentAnimation.start();
if (!animated) {
// Synchronously end the animation, jumping to the end state.
currentAnimation.end();
}
return true;
}
protected void jumpToState(ExpandableWidget dep, View child) {
boolean expanded = dep.isExpanded();
if (!didStateChange(expanded)) {
return;
}
currentState = expanded ? STATE_EXPANDED : STATE_COLLAPSED;
if (expanded) {
child.setVisibility(View.VISIBLE);
} else {
child.setVisibility(View.INVISIBLE);
}
}
protected abstract Animator createAnimation(ExpandableWidget dep, View child);
private ExpandableWidget findExpandableWidget(List<View> dependencies) {
for (int i = 0, size = dependencies.size(); i < size; i++) {
View dependency = dependencies.get(i);
if (dependency instanceof ExpandableWidget) {
return (ExpandableWidget) dependency;
}
}
return null;
}
private boolean didStateChange(boolean expanded) {
return (expanded && ((currentState == STATE_UNKNOWN) || (currentState == STATE_COLLAPSED)))
|| (!expanded && ((currentState == STATE_UNKNOWN) || (currentState == STATE_EXPANDED)));
}
/**
* A utility function to get the {@link ExpandableTransformationBehavior} attached to the {@code
* view}.
*
* @param view The {@link View} that the {@link ExpandableTransformationBehavior} is attached to.
* @param klass The expected {@link Class} of the attached {@link
* ExpandableTransformationBehavior}.
* @return The {@link ExpandableTransformationBehavior} attached to the {@code view}.
*/
public static <T extends ExpandableTransformationBehavior> T from(View view, Class<T> klass) {
ViewGroup.LayoutParams params = view.getLayoutParams();
if (!(params instanceof CoordinatorLayout.LayoutParams)) {
throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
}
CoordinatorLayout.Behavior<?> behavior =
((CoordinatorLayout.LayoutParams) params).getBehavior();
if (!(behavior instanceof ExpandableTransformationBehavior)) {
throw new IllegalArgumentException(
"The view is not associated with ExpandableTransformationBehavior");
}
return klass.cast(view);
}
}

View File

@ -17,9 +17,11 @@
package android.support.design.widget.transformation;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.expandable.ExpandableWidget;
@ -30,8 +32,8 @@ import java.util.ArrayList;
import java.util.List;
/**
* Behavior that should be attached to a scrim that should appear when a {@link ExpandableWidget} is
* {@link ExpandableWidget#setExpanded(boolean)} expanded}.
* Behavior that should be attached to a scrim that should appear when a {@link
* FloatingActionButton} is {@link FloatingActionButton#setExpanded(boolean)} expanded}.
*/
public class FabTransformationScrimBehavior extends ExpandableTransformationBehavior {
@ -44,8 +46,7 @@ public class FabTransformationScrimBehavior extends ExpandableTransformationBeha
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return super.layoutDependsOn(parent, child, dependency)
&& dependency instanceof FloatingActionButton;
return dependency instanceof FloatingActionButton;
}
@Override
@ -54,17 +55,35 @@ public class FabTransformationScrimBehavior extends ExpandableTransformationBeha
return super.onTouchEvent(parent, child, ev);
}
@NonNull
@Override
protected Animator createAnimation(ExpandableWidget dep, View child) {
protected Animator onCreateExpandedStateChangeAnimation(
ExpandableWidget dep, View child, boolean expanded) {
List<Animator> animations = new ArrayList<>();
if (dep.isExpanded()) {
if (expanded) {
animations.add(ObjectAnimator.ofFloat(child, View.ALPHA, 0f, 1f));
} else {
animations.add(ObjectAnimator.ofFloat(child, View.ALPHA, 1f, 0f));
}
AnimatorSet set = new AnimatorSet();
set.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (expanded) {
child.setVisibility(View.VISIBLE);
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (!expanded) {
child.setVisibility(View.INVISIBLE);
}
}
});
set.playTogether(animations);
return set;
}

View File

@ -32,8 +32,8 @@ import java.util.ArrayList;
import java.util.List;
/**
* Behavior that should be attached to any sheet that should appear when a {@link ExpandableWidget}
* is {@link ExpandableWidget#setExpanded(boolean)} expanded}.
* Behavior that should be attached to any sheet that should appear when a {@link
* FloatingActionButton} is {@link FloatingActionButton#setExpanded(boolean)} expanded}.
*
* <p>A sheet usually has some width and height that's smaller than the screen, has an elevation,
* and may have a scrim underneath.
@ -49,8 +49,7 @@ public class FabTransformationSheetBehavior extends ExpandableTransformationBeha
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return super.layoutDependsOn(parent, child, dependency)
&& dependency instanceof FloatingActionButton;
return dependency instanceof FloatingActionButton;
}
@Override
@ -62,32 +61,18 @@ public class FabTransformationSheetBehavior extends ExpandableTransformationBeha
}
}
@NonNull
@Override
protected void jumpToState(ExpandableWidget dep, View child) {
super.jumpToState(dep, child);
View dependency = (View) dep;
if (dep.isExpanded()) {
// A bug exists in 4.4.4 where setVisibility() did not invalidate the view.
dependency.setAlpha(0f);
dependency.setVisibility(View.INVISIBLE);
} else {
// A bug exists in 4.4.4 where setVisibility() did not invalidate the view.
dependency.setAlpha(1f);
dependency.setVisibility(View.VISIBLE);
}
}
@Override
protected Animator createAnimation(ExpandableWidget dep, View child) {
protected Animator onCreateExpandedStateChangeAnimation(
ExpandableWidget dependency, View child, boolean expanded) {
List<Animator> animations = new ArrayList<>();
View dependency = (View) dep;
if (dep.isExpanded()) {
animations.add(ObjectAnimator.ofFloat(dependency, View.ALPHA, 1f, 0f));
View dep = (View) dependency;
if (expanded) {
animations.add(ObjectAnimator.ofFloat(dep, View.ALPHA, 1f, 0f));
animations.add(ObjectAnimator.ofFloat(child, View.ALPHA, 0f, 1f));
} else {
animations.add(ObjectAnimator.ofFloat(dependency, View.ALPHA, 0f, 1f));
animations.add(ObjectAnimator.ofFloat(dep, View.ALPHA, 0f, 1f));
animations.add(ObjectAnimator.ofFloat(child, View.ALPHA, 1f, 0f));
}
@ -97,19 +82,23 @@ public class FabTransformationSheetBehavior extends ExpandableTransformationBeha
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (!dep.isExpanded()) {
if (expanded) {
child.setVisibility(View.VISIBLE);
} else {
// A bug exists in 4.4.4 where setVisibility() did not invalidate the view.
dependency.setAlpha(1f);
dependency.setVisibility(View.VISIBLE);
dep.setAlpha(1f);
dep.setVisibility(View.VISIBLE);
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (dep.isExpanded()) {
if (expanded) {
// A bug exists in 4.4.4 where setVisibility() did not invalidate the view.
dependency.setAlpha(0f);
dependency.setVisibility(View.INVISIBLE);
dep.setAlpha(0f);
dep.setVisibility(View.INVISIBLE);
} else {
child.setVisibility(View.INVISIBLE);
}
}
});