package cgeo.geocaching.ui;
import cgeo.geocaching.R;
import cgeo.geocaching.location.Geopoint;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.AngleUtils;
import android.support.annotation.NonNull;
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 final class CompassMiniView extends View {
private Geopoint targetCoords = null;
private float azimuth = 0;
private float heading = 0;
/**
* remember the last state of drawing so we can avoid repainting for very small changes
*/
private float lastDrawnAzimuth;
/**
* bitmap shared by all instances of the view
*/
private static Bitmap compassArrow;
/**
* bitmap width
*/
private static int compassArrowWidth;
/**
* bitmap height
*/
private static int compassArrowHeight;
/**
* view instance counter for bitmap recycling
*/
private static int instances = 0;
/**
* pixel size of the square arrow bitmap
*/
private static final int ARROW_BITMAP_SIZE = 21;
private static final PaintFlagsDrawFilter FILTER_SET = new PaintFlagsDrawFilter(0, Paint.FILTER_BITMAP_FLAG);
private static final float MINIMUM_ROTATION_DEGREES_FOR_REPAINT = 5;
private float azimuthRelative;
public CompassMiniView(final Context context) {
super(context);
}
public CompassMiniView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
if (instances++ == 0) {
final int drawable = isInEditMode() || !Settings.isLightSkin() ? R.drawable.compass_arrow_mini_white : R.drawable.compass_arrow_mini_black;
compassArrow = BitmapFactory.decodeResource(getResources(), drawable);
compassArrowWidth = compassArrow.getWidth();
compassArrowHeight = compassArrow.getWidth();
}
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (--instances == 0) {
compassArrow.recycle();
}
}
public void setTargetCoords(final Geopoint point) {
targetCoords = point;
}
void updateAzimuth(final float azimuth) {
this.azimuth = azimuth;
updateDirection();
}
void updateHeading(final float heading) {
this.heading = heading;
updateDirection();
}
void updateCurrentCoords(@NonNull final Geopoint currentCoords) {
if (targetCoords == null) {
return;
}
heading = currentCoords.bearingTo(targetCoords);
updateDirection();
}
private void updateDirection() {
if (compassArrow == null || compassArrow.isRecycled()) {
return;
}
azimuthRelative = AngleUtils.normalize(azimuth - heading);
// avoid updates on very small changes, which are not visible to the user
final float change = Math.abs(azimuthRelative - lastDrawnAzimuth);
if (change < MINIMUM_ROTATION_DEGREES_FOR_REPAINT) {
return;
}
// compass margins
final int marginLeft = (getWidth() - compassArrowWidth) / 2;
final int marginTop = (getHeight() - compassArrowHeight) / 2;
invalidate(marginLeft, marginTop, marginLeft + compassArrowWidth, marginTop + compassArrowHeight);
}
@Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
lastDrawnAzimuth = azimuthRelative;
// compass margins
final int canvasCenterX = getWidth() / 2;
final int canvasCenterY = getHeight() / 2;
final int marginLeft = (getWidth() - compassArrowWidth) / 2;
final int marginTop = (getHeight() - compassArrowHeight) / 2;
canvas.setDrawFilter(FILTER_SET);
canvas.rotate(-azimuthRelative, canvasCenterX, canvasCenterY);
canvas.drawBitmap(compassArrow, marginLeft, marginTop, null);
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureWidth(final int measureSpec) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
return specSize;
}
int result = ARROW_BITMAP_SIZE + getPaddingLeft() + getPaddingRight();
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
return result;
}
private int measureHeight(final int measureSpec) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
return specSize;
}
int result = ARROW_BITMAP_SIZE + getPaddingTop() + getPaddingBottom();
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
return result;
}
}