• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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  */
16 package com.android.contacts.widget;
17 
18 import com.android.contacts.format.FormatUtils;
19 import com.android.internal.R;
20 
21 import android.database.CharArrayBuffer;
22 import android.graphics.Color;
23 import android.os.Handler;
24 import android.text.TextPaint;
25 import android.text.style.CharacterStyle;
26 import android.view.animation.AccelerateInterpolator;
27 import android.view.animation.DecelerateInterpolator;
28 
29 /**
30  * An animation that alternately dims and brightens the non-highlighted portion of text.
31  */
32 public abstract class TextHighlightingAnimation implements Runnable, TextWithHighlightingFactory {
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     /**
43      * Frame rate expressed a number of millis between frames.
44      */
45     private static final long FRAME_RATE = 50;
46 
47     private DimmingSpan mDimmingSpan;
48     private Handler mHandler;
49     private boolean mAnimating;
50     private boolean mDimming;
51     private long mTargetTime;
52     private final int mDuration;
53 
54     /**
55      * A Spanned that highlights a part of text by dimming another part of that text.
56      */
57     public class TextWithHighlightingImpl implements TextWithHighlighting {
58 
59         private final DimmingSpan[] mSpans;
60         private boolean mDimmingEnabled;
61         private CharArrayBuffer mText;
62         private int mDimmingSpanStart;
63         private int mDimmingSpanEnd;
64         private String mString;
65 
TextWithHighlightingImpl()66         public TextWithHighlightingImpl() {
67             mSpans = new DimmingSpan[] { mDimmingSpan };
68         }
69 
setText(CharArrayBuffer baseText, CharArrayBuffer highlightedText)70         public void setText(CharArrayBuffer baseText, CharArrayBuffer highlightedText) {
71             mText = baseText;
72 
73             // TODO figure out a way to avoid string allocation
74             mString = new String(mText.data, 0, mText.sizeCopied);
75 
76             int index = FormatUtils.overlapPoint(baseText, highlightedText);
77 
78             if (index == 0 || index == -1) {
79                 mDimmingEnabled = false;
80             } else {
81                 mDimmingEnabled = true;
82                 mDimmingSpanStart = 0;
83                 mDimmingSpanEnd = index;
84             }
85         }
86 
87         @SuppressWarnings("unchecked")
getSpans(int start, int end, Class<T> type)88         public <T> T[] getSpans(int start, int end, Class<T> type) {
89             if (mDimmingEnabled) {
90                 return (T[])mSpans;
91             } else {
92                 return (T[])sEmptySpans;
93             }
94         }
95 
getSpanStart(Object tag)96         public int getSpanStart(Object tag) {
97             // We only have one span - no need to check the tag parameter
98             return mDimmingSpanStart;
99         }
100 
getSpanEnd(Object tag)101         public int getSpanEnd(Object tag) {
102             // We only have one span - no need to check the tag parameter
103             return mDimmingSpanEnd;
104         }
105 
getSpanFlags(Object tag)106         public int getSpanFlags(Object tag) {
107             // String is immutable - flags not needed
108             return 0;
109         }
110 
nextSpanTransition(int start, int limit, Class type)111         public int nextSpanTransition(int start, int limit, Class type) {
112             // Never called since we only have one span
113             return 0;
114         }
115 
charAt(int index)116         public char charAt(int index) {
117             return mText.data[index];
118         }
119 
length()120         public int length() {
121             return mText.sizeCopied;
122         }
123 
subSequence(int start, int end)124         public CharSequence subSequence(int start, int end) {
125             // Never called - implementing for completeness
126             return new String(mText.data, start, end);
127         }
128 
129         @Override
toString()130         public String toString() {
131             return mString;
132         }
133     }
134 
135     /**
136      * A Span that modifies alpha of the default foreground color.
137      */
138     private static class DimmingSpan extends CharacterStyle {
139         private int mAlpha;
140 
setAlpha(int alpha)141         public void setAlpha(int alpha) {
142             mAlpha = alpha;
143         }
144 
145         @Override
updateDrawState(TextPaint ds)146         public void updateDrawState(TextPaint ds) {
147 
148             // Only dim the text in the basic state; not selected, focused or pressed
149             int[] states = ds.drawableState;
150             if (states != null) {
151                 int count = states.length;
152                 for (int i = 0; i < count; i++) {
153                     switch (states[i]) {
154                         case R.attr.state_pressed:
155                         case R.attr.state_selected:
156                         case R.attr.state_focused:
157                             // We can simply return, because the supplied text
158                             // paint is already configured with defaults.
159                             return;
160                     }
161                 }
162             }
163 
164             int color = ds.getColor();
165             color = Color.argb(mAlpha, Color.red(color), Color.green(color), Color.blue(color));
166             ds.setColor(color);
167         }
168     }
169 
170     /**
171      * Constructor.
172      */
TextHighlightingAnimation(int duration)173     public TextHighlightingAnimation(int duration) {
174         mDuration = duration;
175         mHandler = new Handler();
176         mDimmingSpan = new DimmingSpan();
177         mDimmingSpan.setAlpha(MAX_ALPHA);
178     }
179 
180     /**
181      * Returns a Spanned that can be used by a text view to show text with highlighting.
182      */
createTextWithHighlighting()183     public TextWithHighlightingImpl createTextWithHighlighting() {
184         return new TextWithHighlightingImpl();
185     }
186 
187     /**
188      * Override and invalidate (redraw) TextViews showing {@link TextWithHighlightingImpl}.
189      */
invalidate()190     protected abstract void invalidate();
191 
192     /**
193      * Starts the highlighting animation, which will dim portions of text.
194      */
startHighlighting()195     public void startHighlighting() {
196         startAnimation(true);
197     }
198 
199     /**
200      * Starts un-highlighting animation, which will brighten the dimmed portions of text
201      * to the brightness level of the rest of text.
202      */
stopHighlighting()203     public void stopHighlighting() {
204         startAnimation(false);
205     }
206 
207     /**
208      * Called when the animation starts.
209      */
onAnimationStarted()210     protected void onAnimationStarted() {
211     }
212 
213     /**
214      * Called when the animation has stopped.
215      */
onAnimationEnded()216     protected void onAnimationEnded() {
217     }
218 
startAnimation(boolean dim)219     private void startAnimation(boolean dim) {
220         if (mDimming != dim) {
221             mDimming = dim;
222             long now = System.currentTimeMillis();
223             if (!mAnimating) {
224                 mAnimating = true;
225                 mTargetTime = now + mDuration;
226                 onAnimationStarted();
227                 mHandler.post(this);
228             } else  {
229 
230                 // If we have started dimming, reverse the direction and adjust the target
231                 // time accordingly.
232                 mTargetTime = (now + mDuration) - (mTargetTime - now);
233             }
234         }
235     }
236 
237     /**
238      * Animation step.
239      */
run()240     public void run() {
241         long now = System.currentTimeMillis();
242         long timeLeft = mTargetTime - now;
243         if (timeLeft < 0) {
244             mDimmingSpan.setAlpha(mDimming ? MIN_ALPHA : MAX_ALPHA);
245             mAnimating = false;
246             onAnimationEnded();
247             return;
248         }
249 
250         // Start=1, end=0
251         float virtualTime = (float)timeLeft / mDuration;
252         if (mDimming) {
253             float interpolatedTime = DECELERATE_INTERPOLATOR.getInterpolation(virtualTime);
254             mDimmingSpan.setAlpha((int)(MIN_ALPHA + (MAX_ALPHA-MIN_ALPHA) * interpolatedTime));
255         } else {
256             float interpolatedTime = ACCELERATE_INTERPOLATOR.getInterpolation(virtualTime);
257             mDimmingSpan.setAlpha((int)(MIN_ALPHA + (MAX_ALPHA-MIN_ALPHA) * (1-interpolatedTime)));
258         }
259 
260         invalidate();
261 
262         // Repeat
263         mHandler.postDelayed(this, FRAME_RATE);
264     }
265 }
266