diff --git a/Android.mk b/Android.mk index 08ac299ed..6bbbef0fe 100644 --- a/Android.mk +++ b/Android.mk @@ -63,12 +63,23 @@ LOCAL_JAVA_LIBRARIES := android-support-design-res \ 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/api/current.txt b/api/current.txt index 8dd4fd8e6..94c518868 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3,18 +3,17 @@ package android.support.design.widget { 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 @@ package android.support.design.widget { 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 @@ package android.support.design.widget { 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/base/android/support/design/widget/ValueAnimatorCompat.java b/base/android/support/design/widget/ValueAnimatorCompat.java new file mode 100644 index 000000000..1e33f661a --- /dev/null +++ b/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. + *
+ * You shouldn't not instantiate this directly. Instead use {@code ViewUtils.createAnimator()}. + */ +class ValueAnimatorCompat { + + interface AnimatorUpdateListener { + /** + *
Notifies the occurrence of another frame of the animation.
+ * + * @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 { + /** + *Notifies the start of the animation.
+ * + * @param animator The started animation. + */ + void onAnimationStart(ValueAnimatorCompat animator); + /** + *Notifies the end of the animation. This callback is not invoked + * for animations with repeat count set to INFINITE.
+ * + * @param animator The animation which reached its end. + */ + void onAnimationEnd(ValueAnimatorCompat animator); + /** + *Notifies the cancellation of the animation. This callback is not invoked + * for animations with repeat count set to INFINITE.
+ * + * @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/build.gradle b/build.gradle index 1740fce33..ca8018c7b 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ android { 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/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java b/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java new file mode 100644 index 000000000..42cf086ed --- /dev/null +++ b/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/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java b/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java new file mode 100644 index 000000000..4f9ea39e2 --- /dev/null +++ b/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/res/anim/fab_in.xml b/res/anim/fab_in.xml new file mode 100644 index 000000000..294050f54 --- /dev/null +++ b/res/anim/fab_in.xml @@ -0,0 +1,30 @@ + + + +* Children should provide their desired scrolling behavior through * {@link LayoutParams#setScrollFlags(int)} and the associated layout xml attribute: @@ -89,48 +93,19 @@ import java.util.List; 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). * - *
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 @@ public class AppBarLayout extends LinearLayout {
private float mTargetElevation;
+ private final List
+ * 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 @@ public class AppBarLayout extends LinearLayout {
* scroll handling with offsetting.
*/
public static class Behavior extends ViewOffsetBehavior