mirror of
https://github.com/material-components/material-components-android.git
synced 2026-02-20 08:39:55 +08:00
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:
parent
75204528b9
commit
64f930f2a7
@ -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;
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user