summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDoris Liu <tianliu@google.com>2015-04-10 12:39:35 -0700
committerDoris Liu <tianliu@google.com>2015-04-10 14:09:30 -0700
commit0084e370955cfa1efbf8ab447ac5e71a5529f5d3 (patch)
tree5d3c79d27d11c35e2b2ab55008169e7bfda3e45c
parent94bf0d9157418d398325e7e8c1662923b848842e (diff)
downloadframeworks_base-0084e370955cfa1efbf8ab447ac5e71a5529f5d3.zip
frameworks_base-0084e370955cfa1efbf8ab447ac5e71a5529f5d3.tar.gz
frameworks_base-0084e370955cfa1efbf8ab447ac5e71a5529f5d3.tar.bz2
Distance based animation duration
In order to preserve the same look and feel of an animation across different devices, we need to maintain the same angular velocity for the animation in users' field of view. Since the animation path may span different angles on different devices, we need to therefore adjust the duration accordingly. Change-Id: Ia37f213e5a894a046edbb1a45a4ced04e406d85d
-rw-r--r--api/current.txt6
-rw-r--r--api/system-current.txt6
-rw-r--r--core/java/android/animation/Animator.java118
-rw-r--r--core/java/android/animation/AnimatorInflater.java13
-rw-r--r--core/java/android/animation/AnimatorSet.java1
-rw-r--r--core/java/android/animation/ValueAnimator.java5
-rw-r--r--core/java/android/view/animation/AnimationUtils.java85
-rw-r--r--core/res/res/values/attrs.xml13
-rw-r--r--core/res/res/values/public.xml3
9 files changed, 249 insertions, 1 deletions
diff --git a/api/current.txt b/api/current.txt
index 9713b3c..0f36163 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -525,6 +525,7 @@ package android {
field public static final int dropDownWidth = 16843362; // 0x1010262
field public static final int duplicateParentState = 16842985; // 0x10100e9
field public static final int duration = 16843160; // 0x1010198
+ field public static final int durationScaleHint = 16844014; // 0x10104ee
field public static final int editTextBackground = 16843602; // 0x1010352
field public static final int editTextColor = 16843601; // 0x1010351
field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -2879,6 +2880,7 @@ package android.animation {
method public void cancel();
method public android.animation.Animator clone();
method public void end();
+ method public long getDistanceBasedDuration();
method public abstract long getDuration();
method public android.animation.TimeInterpolator getInterpolator();
method public java.util.ArrayList<android.animation.Animator.AnimatorListener> getListeners();
@@ -2892,12 +2894,16 @@ package android.animation {
method public void removePauseListener(android.animation.Animator.AnimatorPauseListener);
method public void resume();
method public abstract android.animation.Animator setDuration(long);
+ method public void setDurationScaleHint(int, android.content.res.Resources);
method public abstract void setInterpolator(android.animation.TimeInterpolator);
method public abstract void setStartDelay(long);
method public void setTarget(java.lang.Object);
method public void setupEndValues();
method public void setupStartValues();
method public void start();
+ field public static final int HINT_DISTANCE_DEFINED_IN_DP = 2; // 0x2
+ field public static final int HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE = 1; // 0x1
+ field public static final int HINT_NO_SCALE = 0; // 0x0
}
public static abstract interface Animator.AnimatorListener {
diff --git a/api/system-current.txt b/api/system-current.txt
index 131b7f6..60a95e3 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -598,6 +598,7 @@ package android {
field public static final int dropDownWidth = 16843362; // 0x1010262
field public static final int duplicateParentState = 16842985; // 0x10100e9
field public static final int duration = 16843160; // 0x1010198
+ field public static final int durationScaleHint = 16844014; // 0x10104ee
field public static final int editTextBackground = 16843602; // 0x1010352
field public static final int editTextColor = 16843601; // 0x1010351
field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -2959,6 +2960,7 @@ package android.animation {
method public void cancel();
method public android.animation.Animator clone();
method public void end();
+ method public long getDistanceBasedDuration();
method public abstract long getDuration();
method public android.animation.TimeInterpolator getInterpolator();
method public java.util.ArrayList<android.animation.Animator.AnimatorListener> getListeners();
@@ -2972,12 +2974,16 @@ package android.animation {
method public void removePauseListener(android.animation.Animator.AnimatorPauseListener);
method public void resume();
method public abstract android.animation.Animator setDuration(long);
+ method public void setDurationScaleHint(int, android.content.res.Resources);
method public abstract void setInterpolator(android.animation.TimeInterpolator);
method public abstract void setStartDelay(long);
method public void setTarget(java.lang.Object);
method public void setupEndValues();
method public void setupStartValues();
method public void start();
+ field public static final int HINT_DISTANCE_DEFINED_IN_DP = 2; // 0x2
+ field public static final int HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE = 1; // 0x1
+ field public static final int HINT_NO_SCALE = 0; // 0x0
}
public static abstract interface Animator.AnimatorListener {
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index da48709..02a329d 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -16,7 +16,12 @@
package android.animation;
+import android.content.res.Configuration;
import android.content.res.ConstantState;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.animation.AnimationUtils;
import java.util.ArrayList;
@@ -25,6 +30,29 @@ import java.util.ArrayList;
* started, ended, and have <code>AnimatorListeners</code> added to them.
*/
public abstract class Animator implements Cloneable {
+ /**
+ * Set this hint when duration for the animation does not need to be scaled. By default, no
+ * scaling is applied to the duration.
+ */
+ public static final int HINT_NO_SCALE = 0;
+
+ /**
+ * Set this scale hint (using {@link #setDurationScaleHint(int, Resources)} when the animation's
+ * moving distance is proportional to the screen size. (e.g. a view coming in from the bottom of
+ * the screen to top/center). With this scale hint set, the animation duration will be
+ * automatically scaled based on screen size.
+ */
+ public static final int HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE = 1;
+
+ /**
+ * Set this scale hint (using {@link #setDurationScaleHint(int, Resources)}) if the animation
+ * has pre-defined moving distance in dp that does not vary from device to device. This is
+ * extremely useful when the animation needs to run on both phones/tablets and TV, because TV
+ * has inflated dp and therefore will have a longer visual arc for the same animation than on
+ * the phone. This hint is used to calculate a scaling factor to compensate for different
+ * visual arcs while maintaining the same angular velocity for the animation.
+ */
+ public static final int HINT_DISTANCE_DEFINED_IN_DP = 2;
/**
* The set of listeners to be sent events through the life of an animation.
@@ -55,6 +83,24 @@ public abstract class Animator implements Cloneable {
private AnimatorConstantState mConstantState;
/**
+ * Scaling factor for an animation that moves across the whole screen.
+ */
+ float mScreenSizeBasedDurationScale = 1.0f;
+
+ /**
+ * Scaling factor for an animation that is defined to move the same amount of dp across all
+ * devices.
+ */
+ float mDpBasedDurationScale = 1.0f;
+
+ /**
+ * By default, the scaling assumes the animation moves across the entire screen.
+ */
+ int mDurationScaleHint = HINT_NO_SCALE;
+
+ private final static boolean ANIM_DEBUG = false;
+
+ /**
* Starts this animation. If the animation has a nonzero startDelay, the animation will start
* running after that delay elapses. A non-delayed animation will have its initial
* value(s) set immediately, followed by calls to
@@ -184,6 +230,78 @@ public abstract class Animator implements Cloneable {
public abstract long getDuration();
/**
+ * Hints how duration scaling factor should be calculated. The duration will not be scaled when
+ * hint is set to {@link #HINT_NO_SCALE}. Otherwise, the duration will be automatically scaled
+ * per device to achieve the same look and feel across different devices. In order to do
+ * that, the same angular velocity of the animation will be needed on different devices in
+ * users' field of view. Therefore, the duration scale factor is determined by the ratio of the
+ * angular movement on current devices to that on the baseline device (i.e. Nexus 5).
+ *
+ * @param hint an indicator on how the animation is defined. The hint could be
+ * {@link #HINT_NO_SCALE}, {@link #HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE} or
+ * {@link #HINT_DISTANCE_DEFINED_IN_DP}.
+ * @param res The resources {@see android.content.res.Resources} for getting display metrics
+ */
+ public void setDurationScaleHint(int hint, Resources res) {
+ if (ANIM_DEBUG) {
+ Log.d("ANIM_DEBUG", "distance based duration hint: " + hint);
+ }
+ if (hint == mDurationScaleHint) {
+ return;
+ }
+ mDurationScaleHint = hint;
+ if (hint != HINT_NO_SCALE) {
+ int uiMode = res.getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK;
+ DisplayMetrics metrics = res.getDisplayMetrics();
+ float width = metrics.widthPixels / metrics.xdpi;
+ float height = metrics.heightPixels / metrics.ydpi;
+ float viewingDistance = AnimationUtils.getViewingDistance(width, height, uiMode);
+ if (ANIM_DEBUG) {
+ Log.d("ANIM_DEBUG", "width, height, viewing distance, uimode: "
+ + width + ", " + height + ", " + viewingDistance + ", " + uiMode);
+ }
+ mScreenSizeBasedDurationScale = AnimationUtils
+ .getScreenSizeBasedDurationScale(width, height, viewingDistance);
+ mDpBasedDurationScale = AnimationUtils.getDpBasedDurationScale(
+ metrics.density, metrics.xdpi, viewingDistance);
+ if (ANIM_DEBUG) {
+ Log.d("ANIM_DEBUG", "screen based scale, dp based scale: " +
+ mScreenSizeBasedDurationScale + ", " + mDpBasedDurationScale);
+ }
+ }
+ }
+
+ // Copies duration scale hint and scaling factors to the new animation.
+ void copyDurationScaleInfoTo(Animator anim) {
+ anim.mDurationScaleHint = mDurationScaleHint;
+ anim.mScreenSizeBasedDurationScale = mScreenSizeBasedDurationScale;
+ anim.mDpBasedDurationScale = mDpBasedDurationScale;
+ }
+
+ /**
+ * @return The scaled duration calculated based on distance of movement (as defined by the
+ * animation) and perceived velocity (derived from the duration set on the animation for
+ * baseline device)
+ */
+ public long getDistanceBasedDuration() {
+ return (long) (getDuration() * getDistanceBasedDurationScale());
+ }
+
+ /**
+ * @return scaling factor of duration based on the duration scale hint. A scaling factor of 1
+ * means no scaling will be applied to the duration.
+ */
+ float getDistanceBasedDurationScale() {
+ if (mDurationScaleHint == HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE) {
+ return mScreenSizeBasedDurationScale;
+ } else if (mDurationScaleHint == HINT_DISTANCE_DEFINED_IN_DP) {
+ return mDpBasedDurationScale;
+ } else {
+ return 1f;
+ }
+ }
+
+ /**
* The time interpolator used in calculating the elapsed fraction of the
* animation. The interpolator determines whether the animation runs with
* linear or non-linear motion, such as acceleration and deceleration. The
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 4a9ba3b..df5a4cb 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -70,6 +70,13 @@ public class AnimatorInflater {
private static final int VALUE_TYPE_COLOR = 3;
private static final int VALUE_TYPE_UNDEFINED = 4;
+ /**
+ * Enum values used in XML attributes to indicate the duration scale hint.
+ */
+ private static final int HINT_NO_SCALE = 0;
+ private static final int HINT_PROPORTIONAL_TO_SCREEN = 1;
+ private static final int HINT_DEFINED_IN_DP = 2;
+
private static final boolean DBG_ANIMATOR_INFLATER = false;
// used to calculate changing configs for resource references
@@ -691,6 +698,9 @@ public class AnimatorInflater {
int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER);
createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering,
pixelSize);
+ final int hint = a.getInt(R.styleable.Animator_durationScaleHint,
+ HINT_NO_SCALE);
+ anim.setDurationScaleHint(hint, res);
a.recycle();
} else if (name.equals("propertyValuesHolder")) {
PropertyValuesHolder[] values = loadValues(res, theme, parser,
@@ -1027,6 +1037,9 @@ public class AnimatorInflater {
anim.setInterpolator(interpolator);
}
+ final int hint = arrayAnimator.getInt(R.styleable.Animator_durationScaleHint,
+ HINT_NO_SCALE);
+ anim.setDurationScaleHint(hint, res);
arrayAnimator.recycle();
if (arrayObjectAnimator != null) {
arrayObjectAnimator.recycle();
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 53d5237..dd5f18e 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -519,6 +519,7 @@ public final class AnimatorSet extends Animator {
for (Node node : mNodes) {
node.animation.setAllowRunningAsynchronously(false);
+ copyDurationScaleInfoTo(node.animation);
}
if (mDuration >= 0) {
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 2386007..b0944ff 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -17,9 +17,12 @@
package android.animation;
import android.annotation.CallSuper;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.os.Looper;
import android.os.Trace;
import android.util.AndroidRuntimeException;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Choreographer;
import android.view.animation.AccelerateDecelerateInterpolator;
@@ -561,7 +564,7 @@ public class ValueAnimator extends Animator {
}
private void updateScaledDuration() {
- mDuration = (long)(mUnscaledDuration * sDurationScale);
+ mDuration = (long)(mUnscaledDuration * sDurationScale * getDistanceBasedDurationScale());
}
/**
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 4d1209a..0417921 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -16,6 +16,7 @@
package android.view.animation;
+import android.content.res.Configuration;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -44,6 +45,16 @@ public class AnimationUtils {
private static final int TOGETHER = 0;
private static final int SEQUENTIALLY = 1;
+ private static final float RECOMMENDED_FIELD_OF_VIEW_FOR_TV = 40f;
+ private static final float ESTIMATED_VIEWING_DISTANCE_FOR_WATCH = 11f;
+ private static final float AVERAGE_VIEWING_DISTANCE_FOR_PHONES = 14.2f;
+ private static final float N5_DIAGONAL_VIEW_ANGLE = 19.58f;
+ private static final float N5_DENSITY = 3.0f;
+ private static final float N5_DPI = 443f;
+
+ private static final float COTANGENT_OF_HALF_TV_ANGLE = (float) (1 / Math.tan(Math.toRadians
+ (RECOMMENDED_FIELD_OF_VIEW_FOR_TV / 2)));
+
/**
* Returns the current animation time in milliseconds. This time should be used when invoking
@@ -367,4 +378,78 @@ public class AnimationUtils {
}
return interpolator;
}
+
+ /**
+ * Derives the viewing distance of a device based on the device size (in inches), and the
+ * device type.
+ * @hide
+ */
+ public static float getViewingDistance(float width, float height, int uiMode) {
+ if (uiMode == Configuration.UI_MODE_TYPE_TELEVISION) {
+ // TV
+ return (width / 2) * COTANGENT_OF_HALF_TV_ANGLE;
+ } else if (uiMode == Configuration.UI_MODE_TYPE_WATCH) {
+ // Watch
+ return ESTIMATED_VIEWING_DISTANCE_FOR_WATCH;
+ } else {
+ // Tablet, phone, etc
+ return AVERAGE_VIEWING_DISTANCE_FOR_PHONES;
+ }
+ }
+
+ /**
+ * Calculates the duration scaling factor of an animation based on the hint that the animation
+ * will move across the entire screen. A scaling factor of 1 means the duration on this given
+ * device will be the same as the duration set through
+ * {@link android.animation.Animator#setDuration(long)}. The calculation uses Nexus 5 as a
+ * baseline device. That is, the duration of the animation on a given device will scale its
+ * duration so that it has the same look and feel as the animation on Nexus 5. In order to
+ * achieve the same perceived effect of the animation across different devices, we maintain
+ * the same angular speed of the same animation in users' field of view. Therefore, the
+ * duration scale factor is determined by the ratio of the angular movement on current
+ * devices to that on the baseline device.
+ *
+ * @param width width of the screen (in inches)
+ * @param height height of the screen (in inches)
+ * @param viewingDistance the viewing distance of the device (i.e. watch, phone, TV, etc) in
+ * inches
+ * @return scaling factor (or multiplier) of the duration set through
+ * {@link android.animation.Animator#setDuration(long)} on current device.
+ * @hide
+ */
+ public static float getScreenSizeBasedDurationScale(float width, float height,
+ float viewingDistance) {
+ // Animation's moving distance is proportional to the screen size.
+ float diagonal = (float) Math.sqrt(width * width + height * height);
+ float diagonalViewAngle = (float) Math.toDegrees(Math.atan((diagonal / 2f)
+ / viewingDistance) * 2);
+ return diagonalViewAngle / N5_DIAGONAL_VIEW_ANGLE;
+ }
+
+ /**
+ * Calculates the duration scaling factor of an animation under the assumption that the
+ * animation is defined to move the same amount of distance (in dp) across all devices. A
+ * scaling factor of 1 means the duration on this given device will be the same as the
+ * duration set through {@link android.animation.Animator#setDuration(long)}. The calculation
+ * uses Nexus 5 as a baseline device. That is, the duration of the animation on a given
+ * device will scale its duration so that it has the same look and feel as the animation on
+ * Nexus 5. In order to achieve the same perceived effect of the animation across different
+ * devices, we maintain the same angular velocity of the same animation in users' field of
+ * view. Therefore, the duration scale factor is determined by the ratio of the angular
+ * movement on current devices to that on the baseline device.
+ *
+ * @param density logical density of the display. {@link android.util.DisplayMetrics#density}
+ * @param dpi pixels per inch
+ * @param viewingDistance viewing distance of the device (in inches)
+ * @return the scaling factor of duration
+ * @hide
+ */
+ public static float getDpBasedDurationScale(float density, float dpi,
+ float viewingDistance) {
+ // Angle in users' field of view per dp:
+ float anglePerDp = (float) Math.atan2((density / dpi) / 2, viewingDistance) * 2;
+ float baselineAnglePerDp = (float) Math.atan2((N5_DENSITY / N5_DPI) / 2,
+ AVERAGE_VIEWING_DISTANCE_FOR_PHONES) * 2;
+ return anglePerDp / baselineAnglePerDp;
+ }
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index aefe79d..cbd74cd 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5981,6 +5981,19 @@
<!-- values are colors, which are integers starting with "#". -->
<enum name="colorType" value="3" />
</attr>
+ <!-- Defines whether the animation should adjust duration in order to achieve the same
+ perceived effects on different devices. -->
+ <attr name="durationScaleHint" >
+ <!-- Default value for scale hint. When set, duration will not be scaled.-->
+ <enum name="noScale" value="0"/>
+ <!-- This should be used when the animation's moving distance is proportional to screen,
+ as the scaling is based on screen size. -->
+ <enum name="screen" value="1"/>
+ <!-- This is for animations that have a distance defined in dp, which will be the same
+ across different devices. In this case, scaling is based on the physical distance
+ per dp on the current device. -->
+ <enum name="dp" value="2"/>
+ </attr>
</declare-styleable>
<declare-styleable name="PropertyValuesHolder">
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 7349d23..c2f2c6d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2660,4 +2660,7 @@
<public type="attr" name="supportsAssistGesture" />
<public type="attr" name="thumbPosition" />
+
+ <!-- Animation -->
+ <public type="attr" name="durationScaleHint" />
</resources>