summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/ui/motion
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/camera/ui/motion')
-rw-r--r--src/com/android/camera/ui/motion/AnimationClock.java39
-rw-r--r--src/com/android/camera/ui/motion/DampedSpring.java145
-rw-r--r--src/com/android/camera/ui/motion/DynamicAnimation.java41
-rw-r--r--src/com/android/camera/ui/motion/DynamicAnimator.java116
-rw-r--r--src/com/android/camera/ui/motion/InterpolateUtils.java66
-rw-r--r--src/com/android/camera/ui/motion/InterpolatorHelper.java38
-rw-r--r--src/com/android/camera/ui/motion/Invalidator.java28
-rw-r--r--src/com/android/camera/ui/motion/LinearScale.java85
-rw-r--r--src/com/android/camera/ui/motion/UnitBezier.java157
-rw-r--r--src/com/android/camera/ui/motion/UnitCurve.java41
-rw-r--r--src/com/android/camera/ui/motion/UnitCurves.java44
11 files changed, 800 insertions, 0 deletions
diff --git a/src/com/android/camera/ui/motion/AnimationClock.java b/src/com/android/camera/ui/motion/AnimationClock.java
new file mode 100644
index 000000000..d2504de6b
--- /dev/null
+++ b/src/com/android/camera/ui/motion/AnimationClock.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui.motion;
+
+import android.os.SystemClock;
+
+/**
+ * Wraps the SystemClock static time methods so they can be exercised in tests.
+ */
+public abstract class AnimationClock {
+
+ public abstract long getTimeMillis();
+
+ /**
+ * Forwards calls to SystemClock.uptimeMillis() since it is the most consistent clock for
+ * animations.
+ */
+ public static class SystemTimeClock extends AnimationClock {
+
+ @Override
+ public long getTimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+ }
+}
diff --git a/src/com/android/camera/ui/motion/DampedSpring.java b/src/com/android/camera/ui/motion/DampedSpring.java
new file mode 100644
index 000000000..84cbfa6f8
--- /dev/null
+++ b/src/com/android/camera/ui/motion/DampedSpring.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui.motion;
+
+/**
+ * This models a value after the behavior of a spring. The value tracks the current value, a target
+ * value, and the current velocity and applies both a directional force and a damping force to the
+ * value on each update call.
+ */
+public class DampedSpring {
+ public static final float DEFAULT_TIME_TO_90_PERCENT_MILLIS = 200.0f;
+ public static final float DEFAULT_SPRING_STIFFNESS = 3.75f;
+ public static final float EPSILON = 0.01f;
+
+ private final float mSpringStiffness;
+ private final float mTimeTo90PercentMs;
+
+ private float mTarget = 0f;
+ private float mVelocity = 0f;
+ private float mValue = 0f;
+
+ public DampedSpring() {
+ this(DEFAULT_TIME_TO_90_PERCENT_MILLIS, DEFAULT_SPRING_STIFFNESS);
+ }
+
+ public DampedSpring(float timeTo90PercentMs) {
+ this(timeTo90PercentMs, DEFAULT_SPRING_STIFFNESS);
+ }
+
+ public DampedSpring(float timeTo90PercentMs, float springStiffness) {
+ // TODO: Assert timeTo90PercentMs >= 1ms, it might behave badly at low values.
+ // TODO: Assert springStiffness > 2.0f
+
+ mTimeTo90PercentMs = timeTo90PercentMs;
+ mSpringStiffness = springStiffness;
+
+ if (springStiffness > timeTo90PercentMs) {
+ throw new IllegalArgumentException("Creating a spring value with "
+ + "excessive stiffness will oscillate endlessly.");
+ }
+ }
+
+ /**
+ * @return the current value.
+ */
+ public float getValue() {
+ return mValue;
+ }
+
+ /**
+ * @param value the value to set this instance's current state too.
+ */
+ public void setValue(float value) {
+ mValue = value;
+ }
+
+ /**
+ * @return the current target value.
+ */
+ public float getTarget() {
+ return mTarget;
+ }
+
+ /**
+ * Set a target value. The current value will maintain any existing velocity values and will
+ * move towards the new target value. To forcibly stopAt the value use the stopAt() method.
+ *
+ * @param value the new value to move the current value towards.
+ */
+ public void setTarget(float value) {
+ mTarget = value;
+ }
+
+ /**
+ * Update the current value, moving it towards the actual value over the given
+ * time delta (in milliseconds) since the last update. This works off of the
+ * principle of a critically damped spring such that any given current value
+ * will move elastically towards the target value. The current value maintains
+ * and applies velocity, acceleration, and a damping force to give a continuous,
+ * smooth transition towards the target value.
+ *
+ * @param dtMs the time since the last update, or zero.
+ * @return the current value after the update occurs.
+ */
+ public float update(float dtMs) {
+ float dt = dtMs / mTimeTo90PercentMs;
+ float dts = dt * mSpringStiffness;
+
+ // If the dts > 1, and the velocity is zero, the force will exceed the
+ // distance to the target value and it will overshoot the value, causing
+ // weird behavior and unintended oscillation. since a critically damped
+ // spring should never overshoot the value, simply the current value to the
+ // target value.
+ if (dts > 1.0f || dts < 0.0f) {
+ stop();
+ return mValue;
+ }
+
+ float delta = (mTarget - mValue);
+ float force = delta - 2.0f * mVelocity;
+
+ mVelocity += force * dts;
+ mValue += mVelocity * dts;
+
+ // If we get close enough to the actual value, simply set the current value
+ // to the current target value and stop.
+ if (!isActive()) {
+ stop();
+ }
+
+ return mValue;
+ }
+
+ /**
+ * @return true if this instance has velocity or it is not at the target value.
+ */
+ public boolean isActive() {
+ boolean hasVelocity = Math.abs(mVelocity) >= EPSILON;
+ boolean atTarget = Math.abs(mTarget - mValue) < EPSILON;
+ return hasVelocity || !atTarget;
+ }
+
+ /**
+ * Stop the spring motion wherever it is currently at. Sets target to the
+ * current value and sets the velocity to zero.
+ */
+ public void stop() {
+ mTarget = mValue;
+ mVelocity = 0.0f;
+ }
+}
diff --git a/src/com/android/camera/ui/motion/DynamicAnimation.java b/src/com/android/camera/ui/motion/DynamicAnimation.java
new file mode 100644
index 000000000..57d5a1021
--- /dev/null
+++ b/src/com/android/camera/ui/motion/DynamicAnimation.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui.motion;
+
+import android.graphics.Canvas;
+
+/**
+ * Rendering object that can be driven by an animator instance.
+ */
+public interface DynamicAnimation {
+
+ /**
+ * Check to determine if this animation is currently in a stable state.
+ *
+ * @return true if the animation is stable, false if it should continue to be redrawn.
+ */
+ boolean isActive();
+
+ /**
+ * Update and draw the animation onto the given canvas.
+ *
+ * @param t current animation frame time.
+ * @param dt delta since the last update.
+ * @param canvas the canvas to draw the animation onto.
+ */
+ void draw(long t, long dt, Canvas canvas);
+}
diff --git a/src/com/android/camera/ui/motion/DynamicAnimator.java b/src/com/android/camera/ui/motion/DynamicAnimator.java
new file mode 100644
index 000000000..542ac1e37
--- /dev/null
+++ b/src/com/android/camera/ui/motion/DynamicAnimator.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui.motion;
+
+import android.graphics.Canvas;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Designed to handle the lifecycle of a view that needs a continuous update /
+ * redraw cycle that does not have a defined start / end time.
+ *
+ * Fixed length animations should NOT use this class.
+ */
+public class DynamicAnimator implements Invalidator {
+
+ public final List<DynamicAnimation> animations = new ArrayList<>();
+
+ private final Invalidator mInvalidator;
+ private final AnimationClock mClock;
+
+ private boolean mUpdateRequested = false;
+ private boolean mIsDrawing = false;
+ private long mLastDrawTimeMillis = 0;
+ private long mDrawTimeMillis = 0;
+
+ public DynamicAnimator(Invalidator invalidator, AnimationClock clock) {
+ mInvalidator = invalidator;
+ mClock = clock;
+ }
+
+ public void draw(Canvas canvas) {
+ mIsDrawing = true;
+ mUpdateRequested = false;
+
+ mDrawTimeMillis = mClock.getTimeMillis();
+
+ if (mLastDrawTimeMillis <= 0) {
+ mLastDrawTimeMillis = mDrawTimeMillis; // On the initial draw, dt is zero.
+ }
+
+ long dt = mDrawTimeMillis - mLastDrawTimeMillis;
+ mLastDrawTimeMillis = mDrawTimeMillis;
+
+ // Run the animation
+ for (DynamicAnimation renderer : animations) {
+ if (renderer.isActive()) {
+ renderer.draw(mDrawTimeMillis, dt, canvas);
+ }
+ }
+
+ // If either the update or the draw methods requested new frames, then
+ // invalidate the view which should give us another frame to work with.
+ // Otherwise, stopAt the last update time.
+ if (mUpdateRequested) {
+ mInvalidator.invalidate();
+ } else {
+ mLastDrawTimeMillis = -1;
+ }
+
+ mIsDrawing = false;
+ }
+
+ /**
+ * If a scheduleNewFrame request comes in outside of the animation loop,
+ * and we didn't schedule a frame after the previous loop (or it's the
+ * first time we've used this instance), invalidate the view and set the
+ * last update time to the current time. Theoretically, a few milliseconds
+ * have elapsed before the view gets updated.
+ */
+ @Override
+ public void invalidate() {
+ if (!mIsDrawing && !mUpdateRequested) {
+ mInvalidator.invalidate();
+ mLastDrawTimeMillis = mClock.getTimeMillis();
+ }
+
+ mUpdateRequested = true;
+ }
+
+ /**
+ * This will return the "best guess" for the most current animation frame
+ * time. If the loop is currently drawing, then it will return the time the
+ * draw began, and if an update is currently requested it will return the
+ * time that the update was requested at, and if neither of these are true
+ * it will return the current system clock time.
+ *
+ * This method will not trigger a new update.
+ */
+ public long getTimeMillis() {
+ if (mIsDrawing) {
+ return mDrawTimeMillis;
+ }
+
+ if (mUpdateRequested) {
+ return mLastDrawTimeMillis;
+ }
+
+ return mClock.getTimeMillis();
+ }
+}
diff --git a/src/com/android/camera/ui/motion/InterpolateUtils.java b/src/com/android/camera/ui/motion/InterpolateUtils.java
new file mode 100644
index 000000000..3c3cd532f
--- /dev/null
+++ b/src/com/android/camera/ui/motion/InterpolateUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui.motion;
+
+/**
+ * Various static helper functions for interpolating between values.
+ */
+public class InterpolateUtils {
+
+ private InterpolateUtils() {
+ }
+
+ /**
+ * Linear interpolation from v0 to v1 as t goes from 0...1
+ *
+ * @param v0 the value at t=0
+ * @param v1 the value at t=1
+ * @param t value in the range of 0 to 1.
+ * @return the value between v0 and v1 as a ratio between 0 and 1 defined by t.
+ */
+ public static float lerp(float v0, float v1, float t) {
+ return v0 + t * (v1 - v0);
+ }
+
+ /**
+ * Project a value that is within the in(Min/Max) number space into the to(Min/Max) number
+ * space.
+ *
+ * @param v value to scale into the 'to' number space.
+ * @param vMin min value of the values number space.
+ * @param vMax max value of the values number space.
+ * @param pMin min value of the projection number space.
+ * @param pMax max value of the projection number space.
+ * @return the ratio of the value in the source number space as a value in the to(Min/Max)
+ * number space.
+ */
+ public static float scale(float v, float vMin, float vMax, float pMin, float pMax) {
+ return (pMax - pMin) * (v - vMin) / (vMax - vMin) + pMin;
+ }
+
+ /**
+ * Value between 0 and 1 as a ratio between tBegin over tDuration
+ * with no upper bound.
+ */
+ public static float unitRatio(long t, long tBegin, float tDuration) {
+ if (t <= tBegin) {
+ return 0.0f;
+ }
+
+ return (t - tBegin) / tDuration;
+ }
+}
diff --git a/src/com/android/camera/ui/motion/InterpolatorHelper.java b/src/com/android/camera/ui/motion/InterpolatorHelper.java
new file mode 100644
index 000000000..84114cb03
--- /dev/null
+++ b/src/com/android/camera/ui/motion/InterpolatorHelper.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.android.camera.ui.motion;
+
+import android.content.Context;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.camera.util.ApiHelper;
+
+public class InterpolatorHelper {
+ private static Interpolator LINEAR_OUT_SLOW_IN = null;
+
+ public static Interpolator getLinearOutSlowInInterpolator(final Context context) {
+ if (LINEAR_OUT_SLOW_IN != null) {
+ return LINEAR_OUT_SLOW_IN;
+ }
+
+ LINEAR_OUT_SLOW_IN = AnimationUtils.loadInterpolator(
+ context, android.R.interpolator.linear_out_slow_in);
+ return LINEAR_OUT_SLOW_IN;
+ }
+}
diff --git a/src/com/android/camera/ui/motion/Invalidator.java b/src/com/android/camera/ui/motion/Invalidator.java
new file mode 100644
index 000000000..fdb548748
--- /dev/null
+++ b/src/com/android/camera/ui/motion/Invalidator.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui.motion;
+
+/**
+ * Basic interface for objects that can be invalidated.
+ */
+public interface Invalidator {
+ /**
+ * Request that the object should be redrawn whenever it gets
+ * the chance.
+ */
+ void invalidate();
+}
diff --git a/src/com/android/camera/ui/motion/LinearScale.java b/src/com/android/camera/ui/motion/LinearScale.java
new file mode 100644
index 000000000..5886f6882
--- /dev/null
+++ b/src/com/android/camera/ui/motion/LinearScale.java
@@ -0,0 +1,85 @@
+/*
+ * 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 com.android.camera.ui.motion;
+
+/**
+ * Represents a discrete linear scale function.
+ */
+public final class LinearScale {
+ private final float mDomainA;
+ private final float mDomainB;
+ private final float mRangeA;
+ private final float mRangeB;
+
+ private final float mScale;
+
+ public LinearScale(float domainA, float domainB, float rangeA, float rangeB) {
+ mDomainA = domainA;
+ mDomainB = domainB;
+ mRangeA = rangeA;
+ mRangeB = rangeB;
+
+ // Precomputed ratio between input domain and output range.
+ float scale = (mRangeB - mRangeA) / (mDomainB - mDomainA);
+ mScale = Float.isNaN(scale) ? 0.0f : scale;
+ }
+
+ /**
+ * Clamp a given domain value to the given domain.
+ */
+ public float clamp(float domainValue) {
+ if (mDomainA > mDomainB) {
+ return Math.max(mDomainB, Math.min(mDomainA, domainValue));
+ }
+
+ return Math.max(mDomainA, Math.min(mDomainB, domainValue));
+ }
+
+ /**
+ * Returns true if the value is within the domain.
+ */
+ public boolean isInDomain(float domainValue) {
+ if (mDomainA > mDomainB) {
+ return domainValue <= mDomainA && domainValue >= mDomainB;
+ }
+ return domainValue >= mDomainA && domainValue <= mDomainB;
+ }
+
+ /**
+ * Linearly scale a given domain value into the output range.
+ */
+ public float scale(float domainValue) {
+ return mRangeA + (domainValue - mDomainA) * mScale;
+ }
+
+ /**
+ * For the current domain and range parameters produce a new scale function
+ * that is the inverse of the current scale function.
+ */
+ public LinearScale inverse() {
+ return new LinearScale(mRangeA, mRangeB, mDomainA, mDomainB);
+ }
+
+ @Override
+ public String toString() {
+ return "LinearScale{" +
+ "mDomainA=" + mDomainA +
+ ", mDomainB=" + mDomainB +
+ ", mRangeA=" + mRangeA +
+ ", mRangeB=" + mRangeB + "}";
+ }
+} \ No newline at end of file
diff --git a/src/com/android/camera/ui/motion/UnitBezier.java b/src/com/android/camera/ui/motion/UnitBezier.java
new file mode 100644
index 000000000..242f54556
--- /dev/null
+++ b/src/com/android/camera/ui/motion/UnitBezier.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui.motion;
+
+/**
+ * This represents is a precomputed cubic bezier curve starting at (0,0) and
+ * going to (1,1) with two configurable control points. Once the instance is
+ * created, the control points cannot be modified.
+ *
+ * Generally, this will be used for computing timing curves for with control
+ * points where an x value will be provide from 0.0 - 1.0, and the y value will
+ * be solved for where y is used as the timing value in some linear
+ * interpolation of a value.
+ */
+public class UnitBezier implements UnitCurve {
+
+ private static final float EPSILON = 1e-6f;
+
+ private final DerivableFloatFn mXFn;
+ private final DerivableFloatFn mYFn;
+
+ /**
+ * Build and pre-compute a unit bezier. This assumes a starting point of
+ * (0, 0) and end point of (1.0, 1.0).
+ *
+ * @param c0x control point x value for p0
+ * @param c0y control point y value for p0
+ * @param c1x control point x value for p1
+ * @param c1y control point y value for p1
+ */
+ public UnitBezier(float c0x, float c0y, float c1x, float c1y) {
+ mXFn = new CubicBezierFn(c0x, c1x);
+ mYFn = new CubicBezierFn(c0y, c1y);
+ }
+
+ /**
+ * Given a unit bezier curve find the height of the curve at t (which is
+ * internally represented as the xAxis).
+ *
+ * @param t the x position between 0 and 1 to solve for y.
+ * @return the closest approximate height of the curve at x.
+ */
+ @Override
+ public float valueAt(float t) {
+ return mYFn.value(solve(t, mXFn));
+ }
+
+ /**
+ * Given a unit bezier curve find a value along the x axis such that
+ * valueAt(result) produces the input value.
+ *
+ * @param value the y position between 0 and 1 to solve for x
+ * @return the closest approximate input that will produce value when provided
+ * to the valueAt function.
+ */
+ @Override
+ public float tAt(float value) {
+ return mXFn.value(solve(value, mYFn));
+ }
+
+ private float solve(float target, DerivableFloatFn fn) {
+ // For a linear fn, t = value. This makes value a good starting guess.
+ float input = target;
+
+ // Newton's method (Faster than bisection)
+ for (int i = 0; i < 8; i++) {
+ float value = fn.value(input) - target;
+ if (Math.abs(value) < EPSILON) {
+ return input;
+ }
+ float derivative = fn.derivative(input);
+ if (Math.abs(derivative) < EPSILON) {
+ break;
+ }
+ input = input - value / derivative;
+ }
+
+ // Fallback on bi-section
+ float min = 0.0f;
+ float max = 1.0f;
+ input = target;
+
+ if (input < min) {
+ return min;
+ }
+ if (input > max) {
+ return max;
+ }
+
+ while (min < max) {
+ float value = fn.value(input);
+ if (Math.abs(value - target) < EPSILON) {
+ return input;
+ }
+
+ if (target > value) {
+ min = input;
+ } else {
+ max = input;
+ }
+
+ input = (max - min) * .5f + min;
+ }
+
+ // Give up, return the closest match we got too.
+ return input;
+ }
+
+ private interface DerivableFloatFn {
+ float value(float x);
+ float derivative(float x);
+ }
+
+ /**
+ * Precomputed constants for a given set of control points along a given
+ * cubic bezier axis.
+ */
+ private static class CubicBezierFn implements DerivableFloatFn {
+ private final float c;
+ private final float a;
+ private final float b;
+
+ /**
+ * Build and pre-compute a single axis for a unit bezier. This assumes p0
+ * is 0 and p1 is 1.
+ *
+ * @param c0 start control point.
+ * @param c1 end control point.
+ */
+ public CubicBezierFn(float c0, float c1) {
+ c = 3.0f * c0;
+ b = 3.0f * (c1 - c0) - c;
+ a = 1.0f - c - b;
+ }
+
+ public float value(float x) {
+ return ((a * x + b) * x + c) * x;
+ }
+ public float derivative(float x) {
+ return (3.0f * a * x + 2.0f * b) * x + c;
+ }
+ }
+}
diff --git a/src/com/android/camera/ui/motion/UnitCurve.java b/src/com/android/camera/ui/motion/UnitCurve.java
new file mode 100644
index 000000000..d89f1fa4d
--- /dev/null
+++ b/src/com/android/camera/ui/motion/UnitCurve.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui.motion;
+
+/**
+ * Simple functions that produce values along a curve for any given input and can compute input
+ * times for a given output value.
+ */
+public interface UnitCurve {
+
+ /**
+ * Produce a unit value of this curve at time t. The function should always return a valid
+ * return value for any valid t input.
+ *
+ * @param t ratio of time passed from (0..1)
+ * @return the unit value at t.
+ */
+ float valueAt(float t);
+
+ /**
+ * If possible, find a value for t such that valueAt(t) == value or best guess.
+ *
+ * @param value to match to the output of valueAt(t)
+ * @return t where valueAt(t) == value or throw.
+ */
+ float tAt(float value);
+}
diff --git a/src/com/android/camera/ui/motion/UnitCurves.java b/src/com/android/camera/ui/motion/UnitCurves.java
new file mode 100644
index 000000000..a1117fa96
--- /dev/null
+++ b/src/com/android/camera/ui/motion/UnitCurves.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui.motion;
+
+/**
+ * Predefined material curves and animations.
+ */
+public class UnitCurves {
+ public static final UnitCurve FAST_OUT_SLOW_IN = new UnitBezier(0.4f, 0.0f, 0.2f, 1.0f);
+ public static final UnitCurve LINEAR_OUT_SLOW_IN = new UnitBezier(0.0f, 0.0f, 0.2f, 1.0f);
+ public static final UnitCurve FAST_OUT_LINEAR_IN = new UnitBezier(0.4f, 0.0f, 1.0f, 1.0f);
+ public static final UnitCurve LINEAR = new UnitBezier(0.0f, 0.0f, 1.0f, 1.0f);
+
+ /**
+ * Given two curves (from and to) and a time along the from curve, compute
+ * the time at t, and find a t along the 'toCurve' that will produce the
+ * same output. This is useful when interpolating between two different curves
+ * when the animation is not at the beginning or end.
+ *
+ * @param enterCurve the curve to compute the value from
+ * @param exitCurve the curve to find a time t on that matches output of
+ * enterCurve at T.
+ * @param t the time at which to compute the value (0..1)
+ * @return the time along the exitCurve.
+ */
+ public static float mapEnterCurveToExitCurveAtT(UnitCurve enterCurve, UnitCurve exitCurve,
+ float t) {
+ return exitCurve.tAt(1 - enterCurve.valueAt(t));
+ }
+}