blob: 3bf6499328c198da1a5fc5a66ed883adf9b4af2b [file] [log] [blame]
Dmitri Plotnikovdaa2d5c2010-01-29 17:44:20 -08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.contacts;
17
18import com.android.internal.R;
19
20import android.database.CharArrayBuffer;
21import android.graphics.Color;
22import android.os.Handler;
23import android.text.Spanned;
24import android.text.TextPaint;
25import android.text.style.CharacterStyle;
26import android.view.animation.AccelerateInterpolator;
27import android.view.animation.DecelerateInterpolator;
28
29/**
30 * An animation that alternately dims and brightens the non-highlighted portion of text.
31 */
32public abstract class TextHighlightingAnimation implements Runnable {
33
34 private static final int MAX_ALPHA = 255;
35 private static final int MIN_ALPHA = 50;
36
37 private AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
38 private DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
39
40 private final static DimmingSpan[] sEmptySpans = new DimmingSpan[0];
41
42 private DimmingSpan mDimmingSpan;
43 private Handler mHandler;
44 private boolean mAnimating;
45 private boolean mDimming;
46 private long mTargetTime;
47 private final int mDuration;
48
49 /**
50 * A Spanned that highlights a part of text by dimming another part of that text.
51 */
52 public class TextWithHighlighting implements Spanned {
53
54 private final DimmingSpan[] mSpans;
55 private boolean mDimmingEnabled;
56 private CharArrayBuffer mText;
57 private int mDimmingSpanStart;
58 private int mDimmingSpanEnd;
59 private String mString;
60
61 public TextWithHighlighting() {
62 mSpans = new DimmingSpan[] { mDimmingSpan };
63 }
64
65 public void setText(CharArrayBuffer baseText, CharArrayBuffer highlightedText) {
66 mText = baseText;
67
68 // TODO figure out a way to avoid string allocation
69 mString = new String(mText.data, 0, mText.sizeCopied);
70
71 int index = indexOf(baseText, highlightedText);
72
73 if (index == 0 || index == -1) {
74 mDimmingEnabled = false;
75 } else {
76 mDimmingEnabled = true;
77 mDimmingSpanStart = 0;
78 mDimmingSpanEnd = index;
79 }
80 }
81
82 /**
83 * An implementation of indexOf on CharArrayBuffers that finds the first match of
84 * the start of buffer2 in buffer1. For example, indexOf("abcd", "cdef") == 2
85 */
86 private int indexOf(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
87 char[] string1 = buffer1.data;
88 char[] string2 = buffer2.data;
89 int count1 = buffer1.sizeCopied;
90 int count2 = buffer2.sizeCopied;
91 int size = count2;
92 for (int i = 0; i < count1; i++) {
93 if (i + size > count1) {
94 size = count1 - i;
95 }
96 int j;
97 for (j = 0; j < size; j++) {
98 if (string1[i+j] != string2[j]) {
99 break;
100 }
101 }
102 if (j == size) {
103 return i;
104 }
105 }
106
107 return -1;
108 }
109
110
111 @SuppressWarnings("unchecked")
112 public <T> T[] getSpans(int start, int end, Class<T> type) {
113 if (mDimmingEnabled) {
114 return (T[])mSpans;
115 } else {
116 return (T[])sEmptySpans;
117 }
118 }
119
120 public int getSpanStart(Object tag) {
121 // We only have one span - no need to check the tag parameter
122 return mDimmingSpanStart;
123 }
124
125 public int getSpanEnd(Object tag) {
126 // We only have one span - no need to check the tag parameter
127 return mDimmingSpanEnd;
128 }
129
130 public int getSpanFlags(Object tag) {
131 // String is immutable - flags not needed
132 return 0;
133 }
134
135 public int nextSpanTransition(int start, int limit, Class type) {
136 // Never called since we only have one span
137 return 0;
138 }
139
140 public char charAt(int index) {
141 return mText.data[index];
142 }
143
144 public int length() {
145 return mText.sizeCopied;
146 }
147
148 public CharSequence subSequence(int start, int end) {
149 // Never called - implementing for completeness
150 return new String(mText.data, start, end);
151 }
152
153 @Override
154 public String toString() {
155 return mString;
156 }
157 }
158
159 /**
160 * A Span that modifies alpha of the default foreground color.
161 */
162 private static class DimmingSpan extends CharacterStyle {
163 private int mAlpha;
164
165 public void setAlpha(int alpha) {
166 mAlpha = alpha;
167 }
168
169 @Override
170 public void updateDrawState(TextPaint ds) {
171
172 // Only dim the text in the basic state; not selected, focused or pressed
173 int[] states = ds.drawableState;
174 if (states != null) {
175 int count = states.length;
176 for (int i = 0; i < count; i++) {
177 switch (states[i]) {
178 case R.attr.state_pressed:
179 case R.attr.state_selected:
180 case R.attr.state_focused:
181 // We can simply return, because the supplied text
182 // paint is already configured with defaults.
183 return;
184 }
185 }
186 }
187
188 int color = ds.getColor();
189 color = Color.argb(mAlpha, Color.red(color), Color.green(color), Color.blue(color));
190 ds.setColor(color);
191 }
192 }
193
194 /**
195 * Constructor.
196 */
197 public TextHighlightingAnimation(int duration) {
198 mDuration = duration;
199 mHandler = new Handler();
200 mDimmingSpan = new DimmingSpan();
201 mDimmingSpan.setAlpha(MAX_ALPHA);
202 }
203
204 /**
205 * Returns a Spanned that can be used by a text view to show text with highlighting.
206 */
207 public TextWithHighlighting createTextWithHighlighting() {
208 return new TextWithHighlighting();
209 }
210
211 /**
212 * Override and invalidate (redraw) TextViews showing {@link TextWithHighlighting}.
213 */
214 protected abstract void invalidate();
215
216 /**
217 * Starts the highlighting animation, which will dim portions of text.
218 */
219 public void startHighlighting() {
220 startAnimation(true);
221 }
222
223 /**
224 * Starts un-highlighting animation, which will brighten the dimmed portions of text
225 * to the brightness level of the rest of text.
226 */
227 public void stopHighlighting() {
228 startAnimation(false);
229 }
230
231 /**
232 * Called when the animation starts.
233 */
234 protected void onAnimationStarted() {
235 }
236
237 /**
238 * Called when the animation has stopped.
239 */
240 protected void onAnimationEnded() {
241 }
242
243 private void startAnimation(boolean dim) {
244 if (mDimming != dim) {
245 mDimming = dim;
246 long now = System.currentTimeMillis();
247 if (!mAnimating) {
248 mAnimating = true;
249 mTargetTime = now + mDuration;
250 onAnimationStarted();
251 mHandler.post(this);
252 } else {
253
254 // If we have started dimming, reverse the direction and adjust the target
255 // time accordingly.
256 mTargetTime = (now + mDuration) - (mTargetTime - now);
257 }
258 }
259 }
260
261 /**
262 * Animation step.
263 */
264 public void run() {
265 long now = System.currentTimeMillis();
266 long timeLeft = mTargetTime - now;
267 if (timeLeft < 0) {
268 mDimmingSpan.setAlpha(mDimming ? MIN_ALPHA : MAX_ALPHA);
269 mAnimating = false;
270 onAnimationEnded();
271 return;
272 }
273
274 // Start=1, end=0
275 float virtualTime = (float)timeLeft / mDuration;
276 if (mDimming) {
277 float interpolatedTime = DECELERATE_INTERPOLATOR.getInterpolation(virtualTime);
278 mDimmingSpan.setAlpha((int)(MIN_ALPHA + (MAX_ALPHA-MIN_ALPHA) * interpolatedTime));
279 } else {
280 float interpolatedTime = ACCELERATE_INTERPOLATOR.getInterpolation(virtualTime);
281 mDimmingSpan.setAlpha((int)(MIN_ALPHA + (MAX_ALPHA-MIN_ALPHA) * (1-interpolatedTime)));
282 }
283
284 invalidate();
285
286 // Repeat
287 mHandler.post(this);
288 }
289}