/*
* Copyright (C) 2014 Sean J. Barbeau (sjbarbeau@gmail.com), University of South Florida
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onebusaway.android.view;
import org.onebusaway.android.R;
import org.onebusaway.android.util.LocationHelper;
import org.onebusaway.android.util.MathUtils;
import org.onebusaway.android.util.OrientationHelper;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.location.Location;
import android.util.AttributeSet;
import android.view.View;
import java.util.ArrayList;
/**
* View that draws an arrow that points towards the given bus mStop
*/
public class ArrowView extends View implements OrientationHelper.Listener, LocationHelper.Listener {
public interface Listener {
/**
* Called when the ArrowView is showing information to the user
*/
void onInitializationComplete();
}
ArrayList<Listener> mListeners = new ArrayList<Listener>();
private float mHeading;
private Paint mArrowPaint;
private Paint mArrowFillPaint;
private Location mLastLocation;
Location mStopLocation = new Location("stopLocation");
float mBearingToStop;
boolean mInitialized = false;
public ArrowView(Context context, AttributeSet attrs) {
super(context, attrs);
mArrowPaint = new Paint();
mArrowPaint.setColor(Color.WHITE);
mArrowPaint.setStyle(Paint.Style.STROKE);
mArrowPaint.setStrokeWidth(4.0f);
mArrowPaint.setAntiAlias(true);
mArrowFillPaint = new Paint();
mArrowFillPaint.setColor(Color.WHITE);
mArrowFillPaint.setStyle(Paint.Style.FILL);
mArrowFillPaint.setStrokeWidth(4.0f);
mArrowFillPaint.setAntiAlias(true);
}
public void setStopLocation(Location location) {
mStopLocation = location;
}
public synchronized void registerListener(Listener listener) {
if (!mListeners.contains(listener)) {
mListeners.add(listener);
}
}
public synchronized void unregisterListener(Listener listener) {
if (mListeners.contains(listener)) {
mListeners.remove(listener);
}
}
/**
* Returns true if the view is initialized and ready to draw to the screen, false if it is not
*
* @return true if the view is initialized and ready to draw to the screen, false if it is not
*/
public boolean isInitialized() {
return mInitialized;
}
@Override
protected void onDraw(Canvas canvas) {
if (mStopLocation == null || mLastLocation == null) {
return;
}
drawArrow(canvas);
}
@Override
public void onOrientationChanged(float heading, float pitch, float xDelta, float yDelta) {
mHeading = heading;
invalidate();
}
@Override
public void onLocationChanged(Location location) {
mLastLocation = location;
if (mStopLocation != null) {
if (!mInitialized) {
mInitialized = true;
// Notify listeners that we have both stop and real-time location and can draw
for (Listener l : mListeners) {
l.onInitializationComplete();
}
}
mBearingToStop = location.bearingTo(mStopLocation);
// Result of bearingTo() can be from -180 to 180. If negative, convert to 181-360 range
// See http://stackoverflow.com/a/8043485/937715
if (mBearingToStop < 0) {
mBearingToStop += 360;
}
invalidate();
}
}
private void drawArrow(Canvas c) {
int height = getHeight();
int width = getWidth();
// Create a buffer around the arrow so when it rotates it doesn't get clipped by view edge
final float BUFFER = width / 5;
// Height of the cutout in the bottom of the triangle that makes it an arrow (0=triangle)
final float CUTOUT_HEIGHT = getHeight() / 5;
float x1, y1; // Tip of arrow
x1 = width / 2;
y1 = BUFFER;
float x2, y2; // lower left
x2 = BUFFER;
y2 = height - BUFFER;
float x3, y3; // cutout in arrow bottom
x3 = width / 2;
y3 = height - CUTOUT_HEIGHT - BUFFER;
float x4, y4; // lower right
x4 = width - BUFFER;
y4 = height - BUFFER;
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x3, y3);
path.lineTo(x4, y4);
path.lineTo(x1, y1);
path.close();
float direction = mHeading - mBearingToStop;
// Make sure value is between 0-360
direction = MathUtils.mod(direction, 360.0f);
// Rotate arrow around center point
Matrix matrix = new Matrix();
matrix.postRotate((float) -direction, width / 2, height / 2);
path.transform(matrix);
c.drawPath(path, mArrowPaint);
c.drawPath(path, mArrowFillPaint);
// Update content description, so screen readers can announce direction to stop
String[] spokenDirections = getResources()
.getStringArray(R.array.spoken_compass_directions);
String directionName = spokenDirections[MathUtils.getHalfWindIndex(direction)];
setContentDescription(directionName);
}
}