1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
|
package cgeo.geocaching.ui;
import cgeo.geocaching.R;
import cgeo.geocaching.utils.PeriodicHandler;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.util.AttributeSet;
import android.view.View;
public class CompassView extends View {
private Context context = null;
private Bitmap compassUnderlay = null;
private Bitmap compassRose = null;
private Bitmap compassArrow = null;
private Bitmap compassOverlay = null;
/**
* North direction currently SHOWN on compass (not measured)
*/
private double azimuthShown = 0.0;
/**
* cache direction currently SHOWN on compass (not measured)
*/
private double cacheHeadingShown = 0.0;
/**
* cache direction measured from device, or 0.0
*/
private double cacheHeadingMeasured = 0.0;
/**
* North direction measured from device, or 0.0
*/
private double northMeasured = 0.0;
private PaintFlagsDrawFilter setfil = null;
private PaintFlagsDrawFilter remfil = null;
private int compassUnderlayWidth = 0;
private int compassUnderlayHeight = 0;
private int compassRoseWidth = 0;
private int compassRoseHeight = 0;
private int compassArrowWidth = 0;
private int compassArrowHeight = 0;
private int compassOverlayWidth = 0;
private int compassOverlayHeight = 0;
private boolean initialDisplay;
private final RedrawHandler redrawHandler = new RedrawHandler();
public CompassView(Context contextIn) {
super(contextIn);
context = contextIn;
}
public CompassView(Context contextIn, AttributeSet attrs) {
super(contextIn, attrs);
context = contextIn;
}
@Override
public void onAttachedToWindow() {
compassUnderlay = BitmapFactory.decodeResource(context.getResources(), R.drawable.compass_underlay);
compassRose = BitmapFactory.decodeResource(context.getResources(), R.drawable.compass_rose);
compassArrow = BitmapFactory.decodeResource(context.getResources(), R.drawable.compass_arrow);
compassOverlay = BitmapFactory.decodeResource(context.getResources(), R.drawable.compass_overlay);
compassUnderlayWidth = compassUnderlay.getWidth();
compassUnderlayHeight = compassUnderlay.getWidth();
compassRoseWidth = compassRose.getWidth();
compassRoseHeight = compassRose.getWidth();
compassArrowWidth = compassArrow.getWidth();
compassArrowHeight = compassArrow.getWidth();
compassOverlayWidth = compassOverlay.getWidth();
compassOverlayHeight = compassOverlay.getWidth();
setfil = new PaintFlagsDrawFilter(0, Paint.FILTER_BITMAP_FLAG);
remfil = new PaintFlagsDrawFilter(Paint.FILTER_BITMAP_FLAG, 0);
initialDisplay = true;
redrawHandler.start();
}
@Override
public void onDetachedFromWindow() {
redrawHandler.stop();
if (compassUnderlay != null) {
compassUnderlay.recycle();
}
if (compassRose != null) {
compassRose.recycle();
}
if (compassArrow != null) {
compassArrow.recycle();
}
if (compassOverlay != null) {
compassOverlay.recycle();
}
}
public synchronized void updateNorth(double northHeadingIn, double cacheHeadingIn) {
if (initialDisplay) {
// We will force the compass to move brutally if this is the first
// update since it is visible.
azimuthShown = northHeadingIn;
cacheHeadingShown = cacheHeadingIn;
// it may take some time to get an initial direction measurement for the device
if (northHeadingIn != 0.0) {
initialDisplay = false;
}
}
northMeasured = northHeadingIn;
cacheHeadingMeasured = cacheHeadingIn;
}
/**
* Compute the new value, moving by small increments.
*
* @param goal
* the goal to reach
* @param actual
* the actual value
* @return the new value
*/
static protected double smoothUpdate(double goal, double actual) {
double diff = goal - actual;
double offset = 0.0;
if (diff < 0.0) {
diff += 360.0;
} else if (diff >= 360.0) {
diff -= 360.0;
}
// If the difference is smaller than 1 degree, do nothing as it
// causes the arrow to vibrate.
if (diff > 1.0 && diff <= 180.0) {
offset = Math.ceil(diff / 10.0); // for larger angles, rotate faster
} else if (diff > 180.0 && diff < 359.0) {
offset = Math.floor((diff - 360.0) / 10.0);
}
return actual + offset;
}
private class RedrawHandler extends PeriodicHandler {
public RedrawHandler() {
super(40);
}
@Override
public void act() {
final double newAzimuthShown = smoothUpdate(northMeasured, azimuthShown);
final double newCacheHeadingShown = smoothUpdate(cacheHeadingMeasured, cacheHeadingShown);
if (Math.abs(newAzimuthShown - azimuthShown) >= 2 || Math.abs(newCacheHeadingShown - cacheHeadingShown) >= 2) {
synchronized(CompassView.this) {
azimuthShown = newAzimuthShown;
cacheHeadingShown = newCacheHeadingShown;
}
invalidate();
}
}
}
@Override
protected void onDraw(Canvas canvas) {
// use local synchronized variables to avoid them being changed from the device during drawing
double azimuthDrawn;
double headingDrawn;
synchronized (this) {
azimuthDrawn = azimuthShown;
headingDrawn = cacheHeadingShown;
}
double azimuthTemp = azimuthDrawn;
double azimuthRelative = azimuthTemp - headingDrawn;
if (azimuthRelative < 0) {
azimuthRelative += 360;
} else if (azimuthRelative >= 360) {
azimuthRelative -= 360;
}
// compass margins
int canvasCenterX = (compassRoseWidth / 2) + ((getWidth() - compassRoseWidth) / 2);
int canvasCenterY = (compassRoseHeight / 2) + ((getHeight() - compassRoseHeight) / 2);
int marginLeftTemp;
int marginTopTemp;
super.onDraw(canvas);
canvas.save();
canvas.setDrawFilter(setfil);
marginLeftTemp = (getWidth() - compassUnderlayWidth) / 2;
marginTopTemp = (getHeight() - compassUnderlayHeight) / 2;
canvas.drawBitmap(compassUnderlay, marginLeftTemp, marginTopTemp, null);
marginLeftTemp = (getWidth() - compassRoseWidth) / 2;
marginTopTemp = (getHeight() - compassRoseHeight) / 2;
canvas.rotate((float) -azimuthTemp, canvasCenterX, canvasCenterY);
canvas.drawBitmap(compassRose, marginLeftTemp, marginTopTemp, null);
canvas.rotate((float) azimuthTemp, canvasCenterX, canvasCenterY);
marginLeftTemp = (getWidth() - compassArrowWidth) / 2;
marginTopTemp = (getHeight() - compassArrowHeight) / 2;
canvas.rotate((float) -azimuthRelative, canvasCenterX, canvasCenterY);
canvas.drawBitmap(compassArrow, marginLeftTemp, marginTopTemp, null);
canvas.rotate((float) azimuthRelative, canvasCenterX, canvasCenterY);
marginLeftTemp = (getWidth() - compassOverlayWidth) / 2;
marginTopTemp = (getHeight() - compassOverlayHeight) / 2;
canvas.drawBitmap(compassOverlay, marginLeftTemp, marginTopTemp, null);
canvas.setDrawFilter(remfil);
canvas.restore();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureWidth(int measureSpec) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = compassArrow.getWidth() + getPaddingLeft() + getPaddingRight();
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
private int measureHeight(int measureSpec) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = compassArrow.getHeight() + getPaddingTop() + getPaddingBottom();
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
}
|