AppBarLayout improvements
- Tidy up listener implementation
- Nested fling support
- Animate FAB pre-v11
- Added internal ValueAnimatorCompat
Change-Id: I3ee6630177015f2bccbf29e5316ef8afe557c5a8
diff --git a/design/Android.mk b/design/Android.mk
index 08ac299..6bbbef0 100644
--- a/design/Android.mk
+++ b/design/Android.mk
@@ -63,12 +63,23 @@
android-support-v7-appcompat
include $(BUILD_STATIC_JAVA_LIBRARY)
+# A helper sub-library that makes direct use of Honeycomb MR1 APIs
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-design-honeycomb-mr1
+LOCAL_SDK_VERSION := 12
+LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb-mr1)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-honeycomb
+LOCAL_JAVA_LIBRARIES := android-support-design-res \
+ android-support-v4 \
+ android-support-v7-appcompat
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
# A helper sub-library that makes direct use of Lollipop APIs
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-design-lollipop
LOCAL_SDK_VERSION := 21
LOCAL_SRC_FILES := $(call all-java-files-under, lollipop)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-honeycomb
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-honeycomb-mr1
LOCAL_JAVA_LIBRARIES := android-support-design-res \
android-support-v4 \
android-support-v7-appcompat
diff --git a/design/api/current.txt b/design/api/current.txt
index 8dd4fd8..94c5188 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -3,18 +3,17 @@
public class AppBarLayout extends android.widget.LinearLayout {
ctor public AppBarLayout(android.content.Context);
ctor public AppBarLayout(android.content.Context, android.util.AttributeSet);
- }
-
- public static abstract interface AppBarLayout.AppBarLayoutChild {
- method public abstract int onOffsetUpdate(int);
- field public static final int STATE_ELEVATED_ABOVE = 1; // 0x1
- field public static final int STATE_ELEVATED_INLINE = 0; // 0x0
+ method public void addOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
+ method public float getTargetElevation();
+ method public void removeOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
+ method public void setTargetElevation(float);
}
public static class AppBarLayout.Behavior extends android.support.design.widget.ViewOffsetBehavior {
ctor public AppBarLayout.Behavior();
ctor public AppBarLayout.Behavior(android.content.Context, android.util.AttributeSet);
method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, int);
+ method public boolean onNestedFling(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, float, float, boolean);
method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int[]);
method public void onNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int, int);
method public boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, android.view.View, int);
@@ -39,6 +38,10 @@
field public static final int SCROLL_FLAG_SCROLL = 1; // 0x1
}
+ public static abstract interface AppBarLayout.OnOffsetChangedListener {
+ method public abstract void onOffsetChanged(android.support.design.widget.AppBarLayout, int);
+ }
+
public static class AppBarLayout.ScrollingViewBehavior extends android.support.design.widget.ViewOffsetBehavior {
ctor public AppBarLayout.ScrollingViewBehavior();
ctor public AppBarLayout.ScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
@@ -49,12 +52,11 @@
method public void setOverlayTop(int);
}
- public class CollapsingToolbarLayout extends android.widget.FrameLayout implements android.support.design.widget.AppBarLayout.AppBarLayoutChild {
+ public class CollapsingToolbarLayout extends android.widget.FrameLayout {
ctor public CollapsingToolbarLayout(android.content.Context);
ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet);
ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet, int);
method public int getForegroundScrimColor();
- method public int onOffsetUpdate(int);
method public void setCollapsedTitleTextAppearance(int);
method public void setCollapsedTitleTextColor(int);
method public void setExpandedTitleColor(int);
diff --git a/design/base/android/support/design/widget/ValueAnimatorCompat.java b/design/base/android/support/design/widget/ValueAnimatorCompat.java
new file mode 100644
index 0000000..1e33f66
--- /dev/null
+++ b/design/base/android/support/design/widget/ValueAnimatorCompat.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2015 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.view.animation.Interpolator;
+
+/**
+ * This class offers a very small subset of {@code ValueAnimator}'s API, but works pre-v11 too.
+ * <p>
+ * You shouldn't not instantiate this directly. Instead use {@code ViewUtils.createAnimator()}.
+ */
+class ValueAnimatorCompat {
+
+ interface AnimatorUpdateListener {
+ /**
+ * <p>Notifies the occurrence of another frame of the animation.</p>
+ *
+ * @param animator The animation which was repeated.
+ */
+ void onAnimationUpdate(ValueAnimatorCompat animator);
+ }
+
+ /**
+ * An animation listener receives notifications from an animation.
+ * Notifications indicate animation related events, such as the end or the
+ * repetition of the animation.
+ */
+ interface AnimatorListener {
+ /**
+ * <p>Notifies the start of the animation.</p>
+ *
+ * @param animator The started animation.
+ */
+ void onAnimationStart(ValueAnimatorCompat animator);
+ /**
+ * <p>Notifies the end of the animation. This callback is not invoked
+ * for animations with repeat count set to INFINITE.</p>
+ *
+ * @param animator The animation which reached its end.
+ */
+ void onAnimationEnd(ValueAnimatorCompat animator);
+ /**
+ * <p>Notifies the cancellation of the animation. This callback is not invoked
+ * for animations with repeat count set to INFINITE.</p>
+ *
+ * @param animator The animation which was canceled.
+ */
+ void onAnimationCancel(ValueAnimatorCompat animator);
+ }
+
+ static class AnimatorListenerAdapter implements AnimatorListener {
+ @Override
+ public void onAnimationStart(ValueAnimatorCompat animator) {
+ }
+
+ @Override
+ public void onAnimationEnd(ValueAnimatorCompat animator) {
+ }
+
+ @Override
+ public void onAnimationCancel(ValueAnimatorCompat animator) {
+ }
+ }
+
+ interface Creator {
+ ValueAnimatorCompat createAnimator();
+ }
+
+ static abstract class Impl {
+ interface AnimatorUpdateListenerProxy {
+ void onAnimationUpdate();
+ }
+
+ interface AnimatorListenerProxy {
+ void onAnimationStart();
+ void onAnimationEnd();
+ void onAnimationCancel();
+ }
+
+ abstract void start();
+ abstract boolean isRunning();
+ abstract void setInterpolator(Interpolator interpolator);
+ abstract void setListener(AnimatorListenerProxy listener);
+ abstract void setUpdateListener(AnimatorUpdateListenerProxy updateListener);
+ abstract void setIntValues(int from, int to);
+ abstract int getAnimatedIntValue();
+ abstract void setFloatValues(float from, float to);
+ abstract float getAnimatedFloatValue();
+ abstract void setDuration(int duration);
+ abstract void cancel();
+ abstract float getAnimatedFraction();
+ abstract void end();
+ }
+
+ private final Impl mImpl;
+
+ ValueAnimatorCompat(Impl impl) {
+ mImpl = impl;
+ }
+
+ public void start() {
+ mImpl.start();
+ }
+
+ public boolean isRunning() {
+ return mImpl.isRunning();
+ }
+
+ public void setInterpolator(Interpolator interpolator) {
+ mImpl.setInterpolator(interpolator);
+ }
+
+ public void setUpdateListener(final AnimatorUpdateListener updateListener) {
+ if (updateListener != null) {
+ mImpl.setUpdateListener(new Impl.AnimatorUpdateListenerProxy() {
+ @Override
+ public void onAnimationUpdate() {
+ updateListener.onAnimationUpdate(ValueAnimatorCompat.this);
+ }
+ });
+ } else {
+ mImpl.setUpdateListener(null);
+ }
+ }
+
+ public void setListener(final AnimatorListener listener) {
+ if (listener != null) {
+ mImpl.setListener(new Impl.AnimatorListenerProxy() {
+ @Override
+ public void onAnimationStart() {
+ listener.onAnimationStart(ValueAnimatorCompat.this);
+ }
+
+ @Override
+ public void onAnimationEnd() {
+ listener.onAnimationEnd(ValueAnimatorCompat.this);
+ }
+
+ @Override
+ public void onAnimationCancel() {
+ listener.onAnimationCancel(ValueAnimatorCompat.this);
+ }
+ });
+ } else {
+ mImpl.setListener(null);
+ }
+ }
+
+ public void setIntValues(int from, int to) {
+ mImpl.setIntValues(from, to);
+ }
+
+ public int getAnimatedIntValue() {
+ return mImpl.getAnimatedIntValue();
+ }
+
+ public void setFloatValues(float from, float to) {
+ mImpl.setFloatValues(from, to);
+ }
+
+ public float getAnimatedFloatValue() {
+ return mImpl.getAnimatedFloatValue();
+ }
+
+ public void setDuration(int duration) {
+ mImpl.setDuration(duration);
+ }
+
+ public void cancel() {
+ mImpl.cancel();
+ }
+
+ public float getAnimatedFraction() {
+ return mImpl.getAnimatedFraction();
+ }
+
+ public void end() {
+ mImpl.end();
+ }
+}
diff --git a/design/build.gradle b/design/build.gradle
index 1740fce..ca8018c 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -12,7 +12,7 @@
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
- main.java.srcDirs = ['base', 'eclair-mr1', 'honeycomb', 'lollipop', 'src']
+ main.java.srcDirs = ['base', 'eclair-mr1', 'honeycomb', 'honeycomb-mr1', 'lollipop', 'src']
main.res.srcDir 'res'
main.assets.srcDir 'assets'
main.resources.srcDir 'src'
diff --git a/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java b/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java
new file mode 100644
index 0000000..42cf086
--- /dev/null
+++ b/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2015 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.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * A 'fake' ValueAnimator implementation which uses a Runnable.
+ */
+class ValueAnimatorCompatImplEclairMr1 extends ValueAnimatorCompat.Impl {
+
+ private static final int HANDLER_DELAY = 10;
+ private static final int DEFAULT_DURATION = 200;
+
+ private static final Handler sHandler = new Handler(Looper.getMainLooper());
+
+ private long mStartTime;
+ private boolean mIsRunning;
+
+ private final int[] mIntValues = new int[2];
+ private final float[] mFloatValues = new float[2];
+
+ private int mDuration = DEFAULT_DURATION;
+ private Interpolator mInterpolator;
+ private AnimatorListenerProxy mListener;
+ private AnimatorUpdateListenerProxy mUpdateListener;
+
+ private float mAnimatedFraction;
+
+ @Override
+ public void start() {
+ if (mIsRunning) {
+ // If we're already running, ignore
+ return;
+ }
+
+ if (mInterpolator == null) {
+ mInterpolator = new AccelerateDecelerateInterpolator();
+ }
+
+ mStartTime = SystemClock.uptimeMillis();
+ mIsRunning = true;
+
+ if (mListener != null) {
+ mListener.onAnimationStart();
+ }
+
+ sHandler.postDelayed(mRunnable, HANDLER_DELAY);
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mIsRunning;
+ }
+
+ @Override
+ public void setInterpolator(Interpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ @Override
+ public void setListener(AnimatorListenerProxy listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void setUpdateListener(AnimatorUpdateListenerProxy updateListener) {
+ mUpdateListener = updateListener;
+ }
+
+ @Override
+ public void setIntValues(int from, int to) {
+ mIntValues[0] = from;
+ mIntValues[1] = to;
+ }
+
+ @Override
+ public int getAnimatedIntValue() {
+ return AnimationUtils.lerp(mIntValues[0], mIntValues[1], getAnimatedFraction());
+ }
+
+ @Override
+ public void setFloatValues(float from, float to) {
+ mFloatValues[0] = from;
+ mFloatValues[1] = to;
+ }
+
+ @Override
+ public float getAnimatedFloatValue() {
+ return AnimationUtils.lerp(mFloatValues[0], mFloatValues[1], getAnimatedFraction());
+ }
+
+ @Override
+ public void setDuration(int duration) {
+ mDuration = duration;
+ }
+
+ @Override
+ public void cancel() {
+ mIsRunning = false;
+ sHandler.removeCallbacks(mRunnable);
+
+ if (mListener != null) {
+ mListener.onAnimationCancel();
+ }
+ }
+
+ @Override
+ public float getAnimatedFraction() {
+ return mAnimatedFraction;
+ }
+
+ @Override
+ public void end() {
+ if (mIsRunning) {
+ mIsRunning = false;
+ sHandler.removeCallbacks(mRunnable);
+
+ // Set our animated fraction to 1
+ mAnimatedFraction = 1f;
+
+ if (mUpdateListener != null) {
+ mUpdateListener.onAnimationUpdate();
+ }
+
+ if (mListener != null) {
+ mListener.onAnimationEnd();
+ }
+ }
+ }
+
+ private void update() {
+ if (mIsRunning) {
+ // Update the animated fraction
+ final long elapsed = SystemClock.uptimeMillis() - mStartTime;
+ final float linearFraction = elapsed / (float) mDuration;
+ mAnimatedFraction = mInterpolator != null
+ ? mInterpolator.getInterpolation(linearFraction)
+ : linearFraction;
+
+ // If we're running, dispatch tp the listener
+ if (mUpdateListener != null) {
+ mUpdateListener.onAnimationUpdate();
+ }
+
+ // Check to see if we've passed the animation duration
+ if (SystemClock.uptimeMillis() >= (mStartTime + mDuration)) {
+ mIsRunning = false;
+
+ if (mListener != null) {
+ mListener.onAnimationEnd();
+ }
+ }
+ }
+
+ if (mIsRunning) {
+ // If we're still running, post another delayed runnable
+ sHandler.postDelayed(mRunnable, HANDLER_DELAY);
+ }
+ }
+
+ private final Runnable mRunnable = new Runnable() {
+ public void run() {
+ update();
+ }
+ };
+}
diff --git a/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java b/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java
new file mode 100644
index 0000000..4f9ea39
--- /dev/null
+++ b/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.view.animation.Interpolator;
+
+class ValueAnimatorCompatImplHoneycombMr1 extends ValueAnimatorCompat.Impl {
+
+ final ValueAnimator mValueAnimator;
+
+ ValueAnimatorCompatImplHoneycombMr1() {
+ mValueAnimator = new ValueAnimator();
+ }
+
+ @Override
+ public void start() {
+ mValueAnimator.start();
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mValueAnimator.isRunning();
+ }
+
+ @Override
+ public void setInterpolator(Interpolator interpolator) {
+ mValueAnimator.setInterpolator(interpolator);
+ }
+
+ @Override
+ public void setUpdateListener(final AnimatorUpdateListenerProxy updateListener) {
+ mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ updateListener.onAnimationUpdate();
+ }
+ });
+ }
+
+ @Override
+ public void setListener(final AnimatorListenerProxy listener) {
+ mValueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ listener.onAnimationStart();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ listener.onAnimationEnd();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ listener.onAnimationCancel();
+ }
+ });
+ }
+
+ @Override
+ public void setIntValues(int from, int to) {
+ mValueAnimator.setIntValues(from, to);
+ }
+
+ @Override
+ public int getAnimatedIntValue() {
+ return (int) mValueAnimator.getAnimatedValue();
+ }
+
+ @Override
+ public void setFloatValues(float from, float to) {
+ mValueAnimator.setFloatValues(from, to);
+ }
+
+ @Override
+ public float getAnimatedFloatValue() {
+ return (float) mValueAnimator.getAnimatedValue();
+ }
+
+ @Override
+ public void setDuration(int duration) {
+ mValueAnimator.setDuration(duration);
+ }
+
+ @Override
+ public void cancel() {
+ mValueAnimator.cancel();
+ }
+
+ @Override
+ public float getAnimatedFraction() {
+ return mValueAnimator.getAnimatedFraction();
+ }
+
+ @Override
+ public void end() {
+ mValueAnimator.end();
+ }
+}
diff --git a/design/res/anim/fab_in.xml b/design/res/anim/fab_in.xml
new file mode 100644
index 0000000..294050f
--- /dev/null
+++ b/design/res/anim/fab_in.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <alpha android:fromAlpha="0.0"
+ android:toAlpha="1.0"/>
+
+ <scale android:fromXScale="0.0"
+ android:fromYScale="0.0"
+ android:toXScale="1.0"
+ android:toYScale="1.0"
+ android:pivotX="50%"
+ android:pivotY="50%"/>
+
+</set>
diff --git a/design/res/anim/fab_out.xml b/design/res/anim/fab_out.xml
new file mode 100644
index 0000000..0f80a9a
--- /dev/null
+++ b/design/res/anim/fab_out.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <alpha android:fromAlpha="1.0"
+ android:toAlpha="0.0"/>
+
+ <scale android:fromXScale="1.0"
+ android:fromYScale="1.0"
+ android:toXScale="0.0"
+ android:toYScale="0.0"
+ android:pivotX="50%"
+ android:pivotY="50%"/>
+
+</set>
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 29216b2..a9d170f 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -21,6 +21,7 @@
import android.support.annotation.IntDef;
import android.support.design.R;
import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ScrollerCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -29,11 +30,14 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
/**
* AppBarLayout is a vertical {@link LinearLayout} which implements many of the features of
- * Material Design's App bar concept, namely scrolling gestures.
+ * material design's app bar concept, namely scrolling gestures.
* <p>
* Children should provide their desired scrolling behavior through
* {@link LayoutParams#setScrollFlags(int)} and the associated layout xml attribute:
@@ -89,48 +93,19 @@
public class AppBarLayout extends LinearLayout {
/**
- * Interface which allows an implementing child {@link View} of this {@link AppBarLayout} to
- * receive offset updates, and provide extra information.
+ * Interface definition for a callback to be invoked when an {@link AppBarLayout}'s vertical
+ * offset changes.
*/
- public interface AppBarLayoutChild {
-
- /** @hide */
- @IntDef({
- STATE_ELEVATED_ABOVE,
- STATE_ELEVATED_INLINE
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface ElevatedState {}
-
- /**
- * The {@link AppBarLayout} should be elevated above any scrolling content, and this cast
- * a shadow.
- *
- * @see #onOffsetUpdate(int)
- */
- int STATE_ELEVATED_ABOVE = 1;
-
- /**
- * The {@link AppBarLayout} should not be elevated above any scrolling content.
- *
- * @see #onOffsetUpdate(int)
- */
- int STATE_ELEVATED_INLINE = 0;
-
+ public interface OnOffsetChangedListener {
/**
* Called when the {@link AppBarLayout}'s layout offset has been changed. This allows
* child views to implement custom behavior based on the offset (for instance pinning a
* view at a certain y value).
*
- * <p>You can influence the elevation of the {@link AppBarLayout} by returning one of
- * {@link #STATE_ELEVATED_INLINE} or {@link #STATE_ELEVATED_ABOVE}.
- *
+ * @param appBarLayout the {@link AppBarLayout} which offset has changed
* @param verticalOffset the vertical offset for the parent {@link AppBarLayout}, in px
- *
- * @return one of {@link #STATE_ELEVATED_INLINE} or {@link #STATE_ELEVATED_ABOVE}.
*/
- @ElevatedState
- int onOffsetUpdate(int verticalOffset);
+ void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset);
}
private static final int INVALID_SCROLL_RANGE = -1;
@@ -143,6 +118,8 @@
private float mTargetElevation;
+ private final List<WeakReference<OnOffsetChangedListener>> mListeners;
+
public AppBarLayout(Context context) {
this(context, null);
}
@@ -159,6 +136,45 @@
// Use the bounds view outline provider so that we cast a shadow, even without a background
ViewUtils.setBoundsViewOutlineProvider(this);
+
+ mListeners = new ArrayList<>();
+
+ ViewCompat.setElevation(this, mTargetElevation);
+ }
+
+ /**
+ * Add a listener that will be called when the offset of this {@link AppBarLayout} changes.
+ *
+ * @param listener The listener that will be called when the offset changes.]
+ *
+ * @see #removeOnOffsetChangedListener(OnOffsetChangedListener)
+ */
+ public void addOnOffsetChangedListener(OnOffsetChangedListener listener) {
+ for (int i = 0, z = mListeners.size(); i < z; i++) {
+ final WeakReference<OnOffsetChangedListener> ref = mListeners.get(i);
+ if (ref != null && ref.get() == listener) {
+ // Listener already added
+ return;
+ }
+ }
+ mListeners.add(new WeakReference<>(listener));
+ }
+
+ /**
+ * Remove the previously added {@link OnOffsetChangedListener}.
+ *
+ * @param listener the listener to remove.
+ */
+ public void removeOnOffsetChangedListener(OnOffsetChangedListener listener) {
+ final Iterator<WeakReference<OnOffsetChangedListener>> i = mListeners.iterator();
+ while (i.hasNext()) {
+ final WeakReference<OnOffsetChangedListener> ref = i.next();
+ final OnOffsetChangedListener item = ref.get();
+ if (item == listener || item == null) {
+ // If the item is null, or is our given listener, remove
+ i.remove();
+ }
+ }
}
@Override
@@ -359,9 +375,26 @@
}
/**
- * The elevation value to use when {@link AppBarLayout} is elevated above content.
+ * Set the elevation value to use when this {@link AppBarLayout} should be elevated
+ * above content.
+ * <p>
+ * This method does not do anything itself. A typical use for this method is called from within
+ * an {@link OnOffsetChangedListener} when the offset has changed in such a way to require an
+ * elevation change.
+ *
+ * @param elevation the elevation value to use.
+ *
+ * @see ViewCompat#setElevation(View, float)
*/
- final float getTargetElevation() {
+ public void setTargetElevation(float elevation) {
+ mTargetElevation = elevation;
+ }
+
+ /**
+ * Returns the elevation value to use when this {@link AppBarLayout} should be elevated
+ * above content.
+ */
+ public float getTargetElevation() {
return mTargetElevation;
}
@@ -515,9 +548,13 @@
* scroll handling with offsetting.
*/
public static class Behavior extends ViewOffsetBehavior<AppBarLayout> {
- private int mSiblingOffsetTop;
+ private int mLogicalOffsetTop;
private boolean mSkipNestedPreScroll;
+ private Runnable mFlingRunnable;
+ private ScrollerCompat mScroller;
+
+ private ValueAnimatorCompat mAnimator;
public Behavior() {}
@@ -529,8 +566,15 @@
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View directTargetChild, View target, int nestedScrollAxes) {
// Return true if we're nested scrolling vertically and we have scrollable children
- return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
+ final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
&& child.hasScrollableChildren();
+
+ if (started && mAnimator != null) {
+ // Cancel any offset animation
+ mAnimator.cancel();
+ }
+
+ return started;
}
@Override
@@ -576,13 +620,119 @@
}
@Override
+ public boolean onNestedFling(final CoordinatorLayout coordinatorLayout,
+ final AppBarLayout child, View target, float velocityX, float velocityY,
+ boolean consumed) {
+ if (!consumed) {
+ // It has been consumed so let's fling ourselves
+ return fling(coordinatorLayout, child, -child.getTotalScrollRange(), 0, -velocityY);
+ } else {
+ // If we're scrolling up and the child also consumed the fling. We'll fake scroll
+ // upto our 'collapsed' offset
+ int targetScroll;
+ if (velocityY < 0) {
+ // We're scrolling down
+ targetScroll = -child.getTotalScrollRange()
+ + child.getDownNestedPreScrollRange();
+
+ if (getTopBottomOffsetForScrollingSibling() > targetScroll) {
+ // If we're currently expanded more than the target scroll, we'll return false
+ // now. This is so that we don't 'scroll' the wrong way.
+ return false;
+ }
+ } else {
+ // We're scrolling up
+ targetScroll = -child.getUpNestedPreScrollRange();
+
+ if (getTopBottomOffsetForScrollingSibling() < targetScroll) {
+ // If we're currently expanded less than the target scroll, we'll return
+ // false now. This is so that we don't 'scroll' the wrong way.
+ return false;
+ }
+ }
+
+ if (mLogicalOffsetTop != targetScroll) {
+ animateOffsetTo(coordinatorLayout, child, targetScroll);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void animateOffsetTo(final CoordinatorLayout coordinatorLayout,
+ final AppBarLayout child, int offset) {
+ if (mAnimator == null) {
+ mAnimator = ViewUtils.createAnimator();
+ mAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
+ mAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimatorCompat animator) {
+ setAppBarTopBottomOffset(coordinatorLayout, child,
+ animator.getAnimatedIntValue());
+ }
+ });
+ } else {
+ mAnimator.cancel();
+ }
+
+ mAnimator.setIntValues(getTopBottomOffsetForScrollingSibling(), offset);
+ mAnimator.start();
+ }
+
+ private boolean fling(CoordinatorLayout coordinatorLayout, AppBarLayout layout, int minOffset,
+ int maxOffset, float velocityY) {
+ if (mFlingRunnable != null) {
+ layout.removeCallbacks(mFlingRunnable);
+ }
+
+ if (mScroller == null) {
+ mScroller = ScrollerCompat.create(layout.getContext());
+ }
+
+ mScroller.fling(
+ 0, mLogicalOffsetTop, // curr
+ 0, Math.round(velocityY), // velocity.
+ 0, 0, // x
+ minOffset, maxOffset); // y
+
+ if (mScroller.computeScrollOffset()) {
+ mFlingRunnable = new FlingRunnable(coordinatorLayout, layout);
+ ViewCompat.postOnAnimation(layout, mFlingRunnable);
+ return true;
+ } else {
+ mFlingRunnable = null;
+ return false;
+ }
+ }
+
+ private class FlingRunnable implements Runnable {
+ private final CoordinatorLayout mParent;
+ private final AppBarLayout mLayout;
+
+ FlingRunnable(CoordinatorLayout parent, AppBarLayout layout) {
+ mParent = parent;
+ mLayout = layout;
+ }
+
+ @Override
+ public void run() {
+ if (mLayout != null && mScroller != null && mScroller.computeScrollOffset()) {
+ setAppBarTopBottomOffset(mParent, mLayout, mScroller.getCurrY());
+
+ // Post ourselves so that we run on the next animation
+ ViewCompat.postOnAnimation(mLayout, this);
+ }
+ }
+ }
+
+ @Override
public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout child,
int layoutDirection) {
boolean handled = super.onLayoutChild(parent, child, layoutDirection);
// Make sure we update the elevation
- final int elevationState = dispatchOffsetUpdates(child);
- checkElevation(child, getTopAndBottomOffset(), elevationState);
+ dispatchOffsetUpdates(child);
return handled;
}
@@ -590,15 +740,23 @@
private int scroll(CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout,
int dy, int minOffset, int maxOffset) {
return setAppBarTopBottomOffset(coordinatorLayout, appBarLayout,
- mSiblingOffsetTop - dy, minOffset, maxOffset);
+ mLogicalOffsetTop - dy, minOffset, maxOffset);
}
- private int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout,
+ final int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout,
+ AppBarLayout appBarLayout, int newOffset) {
+ return setAppBarTopBottomOffset(coordinatorLayout, appBarLayout, newOffset,
+ Integer.MIN_VALUE, Integer.MAX_VALUE);
+ }
+
+ final int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout,
AppBarLayout appBarLayout, int newOffset, int minOffset, int maxOffset) {
- final int curOffset = mSiblingOffsetTop;
+ final int curOffset = mLogicalOffsetTop;
int consumed = 0;
- if (minOffset != 0) {
+ if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
+ // If we have some scrolling range, and we're currently within the min and max
+ // offsets, calculate a new offset
newOffset = MathUtils.constrain(newOffset, minOffset, maxOffset);
if (curOffset != newOffset) {
@@ -606,10 +764,11 @@
appBarLayout.hasChildWithInterpolator()
? interpolateOffset(appBarLayout, newOffset)
: newOffset);
+
// Update how much dy we have consumed
consumed = curOffset - newOffset;
// Update the stored sibling offset
- mSiblingOffsetTop = newOffset;
+ mLogicalOffsetTop = newOffset;
if (!offsetChanged && appBarLayout.hasChildWithInterpolator()) {
// If the offset hasn't changed and we're using an interpolated scroll
@@ -619,44 +778,29 @@
coordinatorLayout.dispatchDependentViewsChanged(appBarLayout);
}
- // Dispatch the updates to any AppBarLayoutChild children
- final int childState = dispatchOffsetUpdates(appBarLayout);
- checkElevation(appBarLayout, newOffset, childState);
+ // Dispatch the updates to any listeners
+ dispatchOffsetUpdates(appBarLayout);
}
}
return consumed;
}
- private void checkElevation(AppBarLayout appBarLayout, int offset, int childState) {
- if (appBarLayout.getHeight() + offset == 0) {
- // If we're not visible, clear out the elevation
- ViewCompat.setElevation(appBarLayout, 0f);
- } else {
- if (childState == AppBarLayoutChild.STATE_ELEVATED_ABOVE) {
- ViewCompat.setElevation(appBarLayout, appBarLayout.getTargetElevation());
- } else {
- ViewCompat.setElevation(appBarLayout, 0f);
+ private void dispatchOffsetUpdates(AppBarLayout layout) {
+ final List<WeakReference<OnOffsetChangedListener>> listeners = layout.mListeners;
+
+ // Iterate backwards through the list so that most recently added listeners
+ // get the first chance to decide
+ for (int i = 0, z = listeners.size(); i < z; i++) {
+ final WeakReference<OnOffsetChangedListener> ref = listeners.get(i);
+ final OnOffsetChangedListener listener = ref != null ? ref.get() : null;
+
+ if (listener != null) {
+ listener.onOffsetChanged(layout, getTopAndBottomOffset());
}
}
}
- private int dispatchOffsetUpdates(AppBarLayout layout) {
- for (int i = 0, z = layout.getChildCount(); i < z; i++) {
- View child = layout.getChildAt(i);
- if (child instanceof AppBarLayoutChild) {
- final int childState = ((AppBarLayoutChild) child)
- .onOffsetUpdate(getTopAndBottomOffset());
-
- if (childState == AppBarLayoutChild.STATE_ELEVATED_INLINE) {
- return childState;
- }
- }
- }
-
- return AppBarLayoutChild.STATE_ELEVATED_ABOVE;
- }
-
private int interpolateOffset(AppBarLayout layout, final int offset) {
final int absOffset = Math.abs(offset);
@@ -698,7 +842,7 @@
}
final int getTopBottomOffsetForScrollingSibling() {
- return mSiblingOffsetTop;
+ return mLogicalOffsetTop;
}
}
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index ab36ed5..24eb439 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -31,6 +31,7 @@
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.FrameLayout;
@@ -71,7 +72,7 @@
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
*/
-public class CollapsingToolbarLayout extends FrameLayout implements AppBarLayout.AppBarLayoutChild {
+public class CollapsingToolbarLayout extends FrameLayout {
private static final int SCRIM_ANIMATION_DURATION = 600;
@@ -89,6 +90,8 @@
private int mCurrentForegroundColor;
private boolean mScrimIsShown;
+ private AppBarLayout.OnOffsetChangedListener mOnOffsetChangedListener;
+
public CollapsingToolbarLayout(Context context) {
this(context, null);
}
@@ -155,6 +158,31 @@
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ // Add an OnOffsetChangedListener if possible
+ final ViewParent parent = getParent();
+ if (parent instanceof AppBarLayout) {
+ if (mOnOffsetChangedListener == null) {
+ mOnOffsetChangedListener = new OffsetUpdateListener();
+ }
+ ((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ // Remove our OnOffsetChangedListener if possible and it exists
+ final ViewParent parent = getParent();
+ if (mOnOffsetChangedListener != null && parent instanceof AppBarLayout) {
+ ((AppBarLayout) parent).removeOnOffsetChangedListener(mOnOffsetChangedListener);
+ }
+
+ super.onDetachedFromWindow();
+ }
+
+ @Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
@@ -234,55 +262,6 @@
mCollapsingTextHelper.setText(title);
}
- /**
- * @hide
- */
- @Override
- public int onOffsetUpdate(int verticalOffset) {
- int pinnedHeight = 0;
-
- for (int i = 0, z = getChildCount(); i < z; i++) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
-
- switch (lp.mCollapseMode) {
- case LayoutParams.COLLAPSE_MODE_PIN:
- if (getHeight() + verticalOffset >= child.getHeight()) {
- offsetHelper.setTopAndBottomOffset(-verticalOffset);
- }
- pinnedHeight += child.getHeight();
- break;
- case LayoutParams.COLLAPSE_MODE_PARALLAX:
- offsetHelper.setTopAndBottomOffset(
- Math.round(-verticalOffset * lp.mParallaxMult));
- break;
- }
- }
-
- // Show or hide the scrim if needed
- if (Color.alpha(mForegroundScrimColor) > 0) {
- if (getHeight() + verticalOffset < getScrimTriggerOffset()) {
- showScrim();
- } else {
- hideScrim();
- }
- }
-
- // Update the collapsing text's fraction
- mCollapsingTextHelper.setExpansionFraction(Math.abs(verticalOffset) /
- (float) (getHeight() - ViewCompat.getMinimumHeight(this)));
-
- if (pinnedHeight > 0 && (getHeight() + verticalOffset) == pinnedHeight) {
- // If we have some pinned children, and we're offset to only show those views,
- // we want to be elevate
- return STATE_ELEVATED_ABOVE;
- } else {
- // Otherwise, we're inline with the content
- return STATE_ELEVATED_INLINE;
- }
- }
-
private void showScrim() {
if (mScrimIsShown) return;
@@ -542,4 +521,54 @@
return mParallaxMult;
}
}
+
+ private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener {
+ @Override
+ public void onOffsetChanged(AppBarLayout layout, int verticalOffset) {
+ int pinnedHeight = 0;
+
+ for (int i = 0, z = getChildCount(); i < z; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
+
+ switch (lp.mCollapseMode) {
+ case LayoutParams.COLLAPSE_MODE_PIN:
+ if (getHeight() + verticalOffset >= child.getHeight()) {
+ offsetHelper.setTopAndBottomOffset(-verticalOffset);
+ }
+ pinnedHeight += child.getHeight();
+ break;
+ case LayoutParams.COLLAPSE_MODE_PARALLAX:
+ offsetHelper.setTopAndBottomOffset(
+ Math.round(-verticalOffset * lp.mParallaxMult));
+ break;
+ }
+ }
+
+ // Show or hide the scrim if needed
+ if (Color.alpha(mForegroundScrimColor) > 0) {
+ if (getHeight() + verticalOffset < getScrimTriggerOffset()) {
+ showScrim();
+ } else {
+ hideScrim();
+ }
+ }
+
+ // Update the collapsing text's fraction
+ final int expandRange = getHeight() - ViewCompat.getMinimumHeight(
+ CollapsingToolbarLayout.this);
+ mCollapsingTextHelper.setExpansionFraction(
+ Math.abs(verticalOffset) / (float) expandRange);
+
+ if (pinnedHeight > 0 && (getHeight() + verticalOffset) == pinnedHeight) {
+ // If we have some pinned children, and we're offset to only show those views,
+ // we want to be elevate
+ ViewCompat.setElevation(layout, layout.getTargetElevation());
+ } else {
+ // Otherwise, we're inline with the content
+ ViewCompat.setElevation(layout, 0f);
+ }
+ }
+ }
}
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index d405de0..c2db0cf 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -30,6 +30,7 @@
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.util.AttributeSet;
import android.view.View;
+import android.view.animation.Animation;
import android.widget.ImageView;
import java.util.List;
@@ -369,40 +370,68 @@
private void animateIn(FloatingActionButton button) {
button.setVisibility(View.VISIBLE);
- ViewCompat.animate(button)
- .scaleX(1f)
- .scaleY(1f)
- .alpha(1f)
- .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
- .withLayer()
- .setListener(null)
- .start();
+ if (Build.VERSION.SDK_INT >= 14) {
+ ViewCompat.animate(button)
+ .scaleX(1f)
+ .scaleY(1f)
+ .alpha(1f)
+ .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
+ .withLayer()
+ .setListener(null)
+ .start();
+ } else {
+ Animation anim = android.view.animation.AnimationUtils.loadAnimation(
+ button.getContext(), R.anim.fab_in);
+ anim.setDuration(200);
+ anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
+ button.startAnimation(anim);
+ }
}
- private void animateOut(FloatingActionButton button) {
- ViewCompat.animate(button)
- .scaleX(0f)
- .scaleY(0f)
- .alpha(0f)
- .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
- .withLayer()
- .setListener(new ViewPropertyAnimatorListener() {
- @Override
- public void onAnimationStart(View view) {
- mIsAnimatingOut = true;
- }
+ private void animateOut(final FloatingActionButton button) {
+ if (Build.VERSION.SDK_INT >= 14) {
+ ViewCompat.animate(button)
+ .scaleX(0f)
+ .scaleY(0f)
+ .alpha(0f)
+ .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
+ .withLayer()
+ .setListener(new ViewPropertyAnimatorListener() {
+ @Override
+ public void onAnimationStart(View view) {
+ mIsAnimatingOut = true;
+ }
- @Override
- public void onAnimationCancel(View view) {
- mIsAnimatingOut = false;
- }
+ @Override
+ public void onAnimationCancel(View view) {
+ mIsAnimatingOut = false;
+ }
- @Override
- public void onAnimationEnd(View view) {
- mIsAnimatingOut = false;
- view.setVisibility(View.GONE);
- }
- }).start();
+ @Override
+ public void onAnimationEnd(View view) {
+ mIsAnimatingOut = false;
+ view.setVisibility(View.GONE);
+ }
+ }).start();
+ } else {
+ Animation anim = android.view.animation.AnimationUtils.loadAnimation(
+ button.getContext(), R.anim.fab_out);
+ anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
+ anim.setDuration(200);
+ anim.setAnimationListener(new AnimationUtils.AnimationListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ mIsAnimatingOut = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ mIsAnimatingOut = false;
+ button.setVisibility(View.GONE);
+ }
+ });
+ button.startAnimation(anim);
+ }
}
}
}
diff --git a/design/src/android/support/design/widget/ViewUtils.java b/design/src/android/support/design/widget/ViewUtils.java
index 34fdc68..29a4522 100644
--- a/design/src/android/support/design/widget/ViewUtils.java
+++ b/design/src/android/support/design/widget/ViewUtils.java
@@ -21,6 +21,16 @@
class ViewUtils {
+ static final ValueAnimatorCompat.Creator DEFAULT_ANIMATOR_CREATOR
+ = new ValueAnimatorCompat.Creator() {
+ @Override
+ public ValueAnimatorCompat createAnimator() {
+ return new ValueAnimatorCompat(Build.VERSION.SDK_INT >= 12
+ ? new ValueAnimatorCompatImplHoneycombMr1()
+ : new ValueAnimatorCompatImplEclairMr1());
+ }
+ };
+
private interface ViewUtilsImpl {
void setBoundsViewOutlineProvider(View view);
}
@@ -54,4 +64,8 @@
IMPL.setBoundsViewOutlineProvider(view);
}
+ static ValueAnimatorCompat createAnimator() {
+ return DEFAULT_ANIMATOR_CREATOR.createAnimator();
+ }
+
}