2828import android .graphics .PorterDuff ;
2929import android .graphics .RadialGradient ;
3030import android .graphics .Shader ;
31+ import android .support .annotation .AnyThread ;
32+ import android .support .annotation .WorkerThread ;
3133import android .util .AttributeSet ;
3234import android .view .MotionEvent ;
3335import android .view .View ;
@@ -136,10 +138,13 @@ public class HeatMap extends View implements View.OnTouchListener {
136138
137139 private Canvas mShadowCanvas = null ;
138140
141+ private final Object tryRefreshLock = new Object ();
142+
139143 /**
140144 * Set the blur factor for the heat map. Must be between 0 and 1.
141145 * @param blur The blur factor
142146 */
147+ @ AnyThread
143148 public void setBlur (double blur ) {
144149 if (blur > 1.0 || blur < 0.0 )
145150 throw new IllegalArgumentException ("Blur must be between 0 and 1." );
@@ -148,6 +153,7 @@ public void setBlur(double blur) {
148153 /**
149154 * Get the heat map's blur factor.
150155 */
156+ @ AnyThread
151157 public double getBlur () { return mBlur ; }
152158
153159 /**
@@ -156,6 +162,7 @@ public void setBlur(double blur) {
156162 * This should be greater than the minimum value.
157163 * @param max The maximum value.
158164 */
165+ @ AnyThread
159166 public void setMaximum (double max ) { this .max = max ; }
160167
161168 /**
@@ -164,37 +171,43 @@ public void setBlur(double blur) {
164171 * This should be less than the maximum value.
165172 * @param min The minimum value.
166173 */
174+ @ AnyThread
167175 public void setMinimum (double min ) { this .min = min ; }
168176
169177 /**
170178 * Set the opacity to be used in the heat map. This opacity will be used for the entire map.
171179 * @param opacity The opacity in the range [0,255].
172180 */
181+ @ AnyThread
173182 public void setOpacity (int opacity ) { this .opacity = opacity ; }
174183
175184 /**
176185 * Set the minimum opacity to be used in the map. Only used when {@link HeatMap#opacity} is 0.
177186 * @param min The minimum opacity in the range [0,255].
178187 */
188+ @ AnyThread
179189 public void setMinimumOpactity (int min ) { this .minOpacity = min ; }
180190
181191 /**
182192 * Set the maximum opacity to be used in the map. Only used when {@link HeatMap#opacity} is 0.
183193 * @param max The maximum opacity in the range [0,255].
184194 */
195+ @ AnyThread
185196 public void setMaximumOpactity (int max ) { this .maxOpacity = max ; }
186197
187198 /**
188199 * Set the circles radius when drawing data points.
189200 * @param radius The radius in pixels.
190201 */
202+ @ AnyThread
191203 public void setRadius (double radius ) { this .mRadius = radius ; }
192204
193205 /**
194206 * Set the color stops used for the heat map's gradient. There needs to be at least 2 stops
195207 * and there should be one at a position of 0 and one at a position of 1.
196208 * @param stops A map from stop positions (as fractions of the width in [0,1]) to ARGB colors.
197209 */
210+ @ AnyThread
198211 public void setColorStops (Map <Float , Integer > stops ) {
199212 if (stops .size () < 2 )
200213 throw new IllegalArgumentException ("There must be at least 2 color stops" );
@@ -214,6 +227,7 @@ public void setColorStops(Map<Float, Integer> stops) {
214227 * Does not refresh the display. See {@link HeatMap#forceRefresh()} in order to redraw the heat map.
215228 * @param point A new data point.
216229 */
230+ @ AnyThread
217231 public void addData (DataPoint point ) {
218232 dataBuffer .add (point );
219233 dataModified = true ;
@@ -224,6 +238,7 @@ public void addData(DataPoint point) {
224238 *
225239 * Does not refresh the display. See {@link HeatMap#forceRefresh()} in order to redraw the heat map.
226240 */
241+ @ AnyThread
227242 public void clearData () {
228243 dataBuffer .clear ();
229244 dataModified = true ;
@@ -315,23 +330,69 @@ private void initialize() {
315330 this .setDrawingCacheBackgroundColor (Color .TRANSPARENT );
316331 }
317332
318- private void redrawShadow () {
333+ @ AnyThread
334+ private void redrawShadow (Bitmap drawingCache , int width , int height ) {
319335 mRenderBoundaries [0 ] = 10000 ;
320336 mRenderBoundaries [1 ] = 10000 ;
321337 mRenderBoundaries [2 ] = 0 ;
322338 mRenderBoundaries [3 ] = 0 ;
323339
324- mShadow = getDrawingCache () ;
340+ mShadow = drawingCache ;
325341 mShadowCanvas = new Canvas (mShadow );
326342
327- drawTransparent (mShadowCanvas );
343+ drawTransparent (mShadowCanvas , width , height );
344+ }
345+
346+ /**
347+ * Draws the heatmap from a background thread.
348+ *
349+ * This allows offloading some of the work that would usualy be done in
350+ * {@link #onDraw(Canvas)} into a background thread. If the view is redrawn
351+ * for some reason while this operation is still ongoing, the UI thread
352+ * will block until this call is finished.
353+ *
354+ * The caller should take care to invalidate the view on the UI thread
355+ * afterwards, but not before this call has finished.
356+ *
357+ * <pre>{@code
358+ * final HeatMap heatmap = (HeatMap) findViewById(R.id.heatmap);
359+ * new AsyncTask<Void,Void,Void>() {
360+ * protected Void doInBackground(Void... params) {
361+ * Random rand = new Random();
362+ * //add 20 random points of random intensity
363+ * for (int i = 0; i < 20; i++) {
364+ * heatmap.addData(getRandomDataPoint());
365+ * }
366+ *
367+ * heatmap.refreshImmediateInBackgroundThread();
368+ *
369+ * return null;
370+ * }
371+ *
372+ * protected void onPostExecute(Void aVoid) {
373+ * heatmap.invalidate();
374+ * heatmap.setAlpha(0.0f);
375+ * heatmap.animate().alpha(1.0f).setDuration(700L).start();
376+ * }
377+ * }.execute();
378+ * }</pre>
379+ */
380+ @ WorkerThread
381+ public void forceRefreshOnWorkerThread () {
382+ synchronized (tryRefreshLock ) {
383+ // These getters are in fact available on this thread. The caller will have to
384+ // take care that the view is in an acceptable state here.
385+ // noinspection WrongThread
386+ tryRefresh (true , getDrawingCache (), getWidth (), getHeight ());
387+ }
328388 }
329389
330390 /**
331391 * If needed, refresh the palette.
332392 */
333- private void tryRefresh () {
334- if (needsRefresh ) {
393+ @ AnyThread
394+ private void tryRefresh (boolean forceRefresh , Bitmap drawingCache , int width , int height ) {
395+ if (forceRefresh || needsRefresh ) {
335396 Bitmap bit = Bitmap .createBitmap (256 , 1 , Bitmap .Config .ARGB_8888 );
336397 Canvas canvas = new Canvas (bit );
337398 LinearGradient grad ;
@@ -350,13 +411,13 @@ private void tryRefresh() {
350411 dataModified = false ;
351412 }
352413
353- redrawShadow ();
354- }
355- else if ( sizeChange ) {
356- redrawShadow ();
414+ redrawShadow (drawingCache , width , height );
415+ } else if ( sizeChange ) {
416+ sizeChange = false ;
417+ redrawShadow (drawingCache , width , height );
357418 }
419+
358420 needsRefresh = false ;
359- sizeChange = false ;
360421 }
361422
362423 @ Override
@@ -372,7 +433,9 @@ public void onSizeChanged(int w, int h, int oldw, int oldh) {
372433 */
373434 @ Override
374435 protected void onDraw (Canvas canvas ) {
375- tryRefresh ();
436+ synchronized (tryRefreshLock ) {
437+ tryRefresh (false , getDrawingCache (), getWidth (), getHeight ());
438+ }
376439
377440 drawColour (canvas );
378441 }
@@ -388,6 +451,7 @@ protected void onDraw(Canvas canvas) {
388451 * @param blurFactor A factor to scale the circles width by.
389452 * @param alpha The transparency of the gradient.
390453 */
454+ @ AnyThread
391455 private void drawDataPoint (Canvas canvas , float x , float y , double radius , double blurFactor , double alpha ) {
392456 if (blurFactor == 1 ) {
393457 canvas .drawCircle (x , y , (float )radius , mBlack );
@@ -407,8 +471,11 @@ private void drawDataPoint(Canvas canvas, float x, float y, double radius, doubl
407471 * version.
408472 *
409473 * @param canvas Canvas to draw into.
474+ * @param width The width of the view
475+ * @param height The height of the view
410476 */
411- private void drawTransparent (Canvas canvas ) {
477+ @ AnyThread
478+ private void drawTransparent (Canvas canvas , int width , int height ) {
412479 //invert the blur factor
413480 double blur = 1 - mBlur ;
414481
@@ -417,8 +484,8 @@ private void drawTransparent(Canvas canvas) {
417484
418485 //loop through the data points
419486 for (DataPoint point : data ) {
420- float x = point .x * getWidth () ;
421- float y = point .y * getHeight () ;
487+ float x = point .x * width ;
488+ float y = point .y * height ;
422489 double value = Math .max (min , Math .min (point .value , max ));
423490 //the edge of the bounding rectangle for the circle
424491 double rectX = x - mRadius ;
0 commit comments