diff options
| author | Jorim Jaggi <jjaggi@google.com> | 2014-07-01 14:59:34 +0200 |
|---|---|---|
| committer | Jorim Jaggi <jjaggi@google.com> | 2014-07-01 20:15:01 +0200 |
| commit | 3aa422033abad9d97d278913c6f63d8700a55b50 (patch) | |
| tree | 7e71ac331ab0e96dfe9a5d65606839a1b57318f7 /core/java/android/widget/TextView.java | |
| parent | 1e2b2ba4b39e4d2fceb355347f6eaf51b76819be (diff) | |
Fix jank in TextView.Marquee
Use Choreographer animation callbacks to update the scrolling and
make the delta dependent on the time passed since the last update.
Change-Id: If6ce365014e8d32dadfc93bba0d4733050f673f7
Diffstat (limited to 'core/java/android/widget/TextView.java')
| -rw-r--r-- | core/java/android/widget/TextView.java | 82 |
1 files changed, 45 insertions, 37 deletions
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8567bedd7322..5743882d5122 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -37,8 +37,6 @@ import android.graphics.drawable.Drawable; import android.inputmethodservice.ExtractEditText; import android.os.AsyncTask; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; @@ -97,6 +95,7 @@ import android.util.Log; import android.util.TypedValue; import android.view.AccessibilityIterators.TextSegmentIterator; import android.view.ActionMode; +import android.view.Choreographer; import android.view.DragEvent; import android.view.Gravity; import android.view.HapticFeedbackConstants; @@ -9088,26 +9087,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private static final class Marquee extends Handler { + private static final class Marquee { // TODO: Add an option to configure this private static final float MARQUEE_DELTA_MAX = 0.07f; private static final int MARQUEE_DELAY = 1200; private static final int MARQUEE_RESTART_DELAY = 1200; - private static final int MARQUEE_RESOLUTION = 1000 / 30; - private static final int MARQUEE_PIXELS_PER_SECOND = 30; + private static final int MARQUEE_DP_PER_SECOND = 30; private static final byte MARQUEE_STOPPED = 0x0; private static final byte MARQUEE_STARTING = 0x1; private static final byte MARQUEE_RUNNING = 0x2; - private static final int MESSAGE_START = 0x1; - private static final int MESSAGE_TICK = 0x2; - private static final int MESSAGE_RESTART = 0x3; - private final WeakReference<TextView> mView; + private final Choreographer mChoreographer; private byte mStatus = MARQUEE_STOPPED; - private final float mScrollUnit; + private final float mPixelsPerSecond; private float mMaxScroll; private float mMaxFadeScroll; private float mGhostStart; @@ -9116,49 +9111,62 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mRepeatLimit; private float mScroll; + private long mLastAnimationMs; Marquee(TextView v) { final float density = v.getContext().getResources().getDisplayMetrics().density; - mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION; + mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density; mView = new WeakReference<TextView>(v); + mChoreographer = Choreographer.getInstance(); } - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_START: - mStatus = MARQUEE_RUNNING; - tick(); - break; - case MESSAGE_TICK: - tick(); - break; - case MESSAGE_RESTART: - if (mStatus == MARQUEE_RUNNING) { - if (mRepeatLimit >= 0) { - mRepeatLimit--; - } - start(mRepeatLimit); + private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + tick(); + } + }; + + private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + mStatus = MARQUEE_RUNNING; + mLastAnimationMs = mChoreographer.getFrameTime(); + tick(); + } + }; + + private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + if (mStatus == MARQUEE_RUNNING) { + if (mRepeatLimit >= 0) { + mRepeatLimit--; } - break; + start(mRepeatLimit); + } } - } + }; void tick() { if (mStatus != MARQUEE_RUNNING) { return; } - removeMessages(MESSAGE_TICK); + mChoreographer.removeFrameCallback(mTickCallback); final TextView textView = mView.get(); if (textView != null && (textView.isFocused() || textView.isSelected())) { - mScroll += mScrollUnit; + long currentMs = mChoreographer.getFrameTime(); + long deltaMs = currentMs - mLastAnimationMs; + mLastAnimationMs = currentMs; + float deltaPx = deltaMs / 1000f * mPixelsPerSecond; + mScroll += deltaPx; if (mScroll > mMaxScroll) { mScroll = mMaxScroll; - sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY); + mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); } else { - sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION); + mChoreographer.postFrameCallback(mTickCallback); } textView.invalidate(); } @@ -9166,9 +9174,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener void stop() { mStatus = MARQUEE_STOPPED; - removeMessages(MESSAGE_START); - removeMessages(MESSAGE_RESTART); - removeMessages(MESSAGE_TICK); + mChoreographer.removeFrameCallback(mStartCallback); + mChoreographer.removeFrameCallback(mRestartCallback); + mChoreographer.removeFrameCallback(mTickCallback); resetScroll(); } @@ -9199,7 +9207,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; textView.invalidate(); - sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY); + mChoreographer.postFrameCallback(mStartCallback); } } |
