alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Android example source code file (RadarView.java)

This example Android source code file (RadarView.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Android by Example" TM.

Java - Android tags/keywords

android, attributeset, drawing, graphics, km_per_meters, km_per_miles, km_per_yards, location, meters_per_km, miles_per_km, override, paint, radarview, rect, retain_gps_millis, sensorlistener, service, string, ui, yards_per_km

The RadarView.java Android example source code

/*
 * Copyright (C) 2008 Google Inc.
 *
 * 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 com.google.android.radar;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.drawable.BitmapDrawable;
import android.hardware.SensorListener;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;

public class RadarView extends View implements SensorListener, LocationListener {
    
    private static final long RETAIN_GPS_MILLIS = 10000L;

    private Paint mGridPaint;
    
    private Paint mErasePaint;

    private float mOrientation;

    private double mTargetLat;

    private double mTargetLon;

    private double mMyLocationLat;

    private double mMyLocationLon;
    
    private int mLastScale = -1;
    private String[] mDistanceScale = new String[4];
    
    private static float KM_PER_METERS = 0.001f;
    private static float METERS_PER_KM = 1000f;
    
    /**
     * These are the list of choices for the radius of the outer circle on the
     * screen when using metric units. All items are in kilometers. This array is 
     * used to choose the scale of the radar display.
     */
    private static double mMetricScaleChoices[] = { 
        100 * KM_PER_METERS,
        200 * KM_PER_METERS,
        400 * KM_PER_METERS, 
        1, 
        2,
        4, 
        8, 
        20,
        40, 
        100, 
        200,
        400,
        1000, 
        2000,
        4000,
        10000, 
        20000,
        40000, 
        80000 };

    /**
     * Once the scale is chosen, this array is used to convert the number of
     * kilometers on the screen to an integer. (Note that for short distances we
     * use meters, so we multiply the distance by {@link #METERS_PER_KM}. (This
     * array is for metric measurements.)
     */
    private static float mMetricDisplayUnitsPerKm[] = { 
        METERS_PER_KM, 
        METERS_PER_KM,
        METERS_PER_KM,
        METERS_PER_KM, 
        METERS_PER_KM, 
        1.0f, 
        1.0f, 
        1.0f, 
        1.0f,
        1.0f,
        1.0f, 
        1.0f, 
        1.0f,
        1.0f,
        1.0f, 
        1.0f, 
        1.0f,
        1.0f,
        1.0f };

    /**
     * This array holds the formatting string used to display the distance to
     * the target. (This array is for metric measurements.)
     */
    private static String mMetricDisplayFormats[] = {
        "%.0fm",
        "%.0fm",
        "%.0fm", 
        "%.0fm", 
        "%.0fm", 
        "%.1fkm", 
        "%.1fkm", 
        "%.0fkm",
        "%.0fkm",
        "%.0fkm",
        "%.0fkm",
        "%.0fkm",
        "%.0fkm", 
        "%.0fkm",
        "%.0fkm",
        "%.0fkm",
        "%.0fkm",
        "%.0fkm", 
        "%.0fkm" };
    
    /**
     * This array holds the formatting string used to display the distance on
     * each ring of the radar screen. (This array is for metric measurements.)
     */
    private static String mMetricScaleFormats[] = {
        "%.0fm",
        "%.0fm", 
        "%.0fm", 
        "%.0fm", 
        "%.0fm", 
        "%.0fkm", 
        "%.0fkm", 
        "%.0fkm",
        "%.0fkm",
        "%.0fkm",
        "%.0fkm", 
        "%.0fkm", 
        "%.0fkm",
        "%.0fkm",
        "%.0fkm",
        "%.0fkm", 
        "%.0fkm",
        "%.0fkm",
        "%.0fkm", 
        "%.0fkm" };
    
    private static float KM_PER_YARDS = 0.0009144f;
    private static float KM_PER_MILES = 1.609344f;
    private static float YARDS_PER_KM = 1093.6133f;
    private static float MILES_PER_KM = 0.621371192f;
    
    /**
     * These are the list of choices for the radius of the outer circle on the
     * screen when using standard units. All items are in kilometers. This array is 
     * used to choose the scale of the radar display.
     */
    private static double mEnglishScaleChoices[] = { 
        100 * KM_PER_YARDS,
        200 * KM_PER_YARDS,
        400 * KM_PER_YARDS, 
        1000 * KM_PER_YARDS, 
        1 * KM_PER_MILES, 
        2 * KM_PER_MILES, 
        4 * KM_PER_MILES, 
        8 * KM_PER_MILES, 
        20 * KM_PER_MILES, 
        40 * KM_PER_MILES, 
        100 * KM_PER_MILES, 
        200 * KM_PER_MILES, 
        400 * KM_PER_MILES, 
        1000 * KM_PER_MILES, 
        2000 * KM_PER_MILES, 
        4000 * KM_PER_MILES, 
        10000 * KM_PER_MILES, 
        20000 * KM_PER_MILES, 
        40000 * KM_PER_MILES, 
        80000 * KM_PER_MILES };

    /**
     * Once the scale is chosen, this array is used to convert the number of
     * kilometers on the screen to an integer. (Note that for short distances we
     * use meters, so we multiply the distance by {@link #YARDS_PER_KM}. (This
     * array is for standard measurements.)
     */
    private static float mEnglishDisplayUnitsPerKm[] = { 
        YARDS_PER_KM, 
        YARDS_PER_KM,
        YARDS_PER_KM,
        YARDS_PER_KM, 
        MILES_PER_KM, 
        MILES_PER_KM, 
        MILES_PER_KM, 
        MILES_PER_KM,
        MILES_PER_KM,
        MILES_PER_KM, 
        MILES_PER_KM, 
        MILES_PER_KM,
        MILES_PER_KM,
        MILES_PER_KM,
        MILES_PER_KM,
        MILES_PER_KM, 
        MILES_PER_KM, 
        MILES_PER_KM,
        MILES_PER_KM,
        MILES_PER_KM };

    /**
     * This array holds the formatting string used to display the distance to
     * the target. (This array is for standard measurements.)
     */
    private static String mEnglishDisplayFormats[] = {
        "%.0fyd",
        "%.0fyd", 
        "%.0fyd", 
        "%.0fyd", 
        "%.1fmi", 
        "%.1fmi", 
        "%.1fmi", 
        "%.1fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi", 
        "%.0fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi", 
        "%.0fmi",
        "%.0fmi" };
    
    /**
     * This array holds the formatting string used to display the distance on
     * each ring of the radar screen. (This array is for standard measurements.)
     */
    private static String mEnglishScaleFormats[] = {
        "%.0fyd",
        "%.0fyd", 
        "%.0fyd", 
        "%.0fyd", 
        "%.2fmi", 
        "%.1fmi", 
        "%.0fmi", 
        "%.0fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi",
        "%.0fmi", 
        "%.0fmi", 
        "%.0fmi" };
    
    /**
     * True when we have know our own location
     */
    private boolean mHaveLocation = false;

    /**
     * The view that will display the distance text
     */
    private TextView mDistanceView;

    /**
     * Distance to target, in KM
     */
    private double mDistance;

    /**
     * Bearing to target, in degrees
     */
    private double mBearing;

    /**
     * Ratio of the distance to the target to the radius of the outermost ring on the radar screen
     */
    private float mDistanceRatio;
    
    /**
     * Utility rect for calculating the ring labels
     */
    private Rect mTextBounds = new Rect();
    
    /**
     * The bitmap used to draw the target
     */
    private Bitmap mBlip;

    /**
     * Used to draw the animated ring that sweeps out from the center
     */
    private Paint mSweepPaint0;
    
    /**
     * Used to draw the animated ring that sweeps out from the center
     */
    private Paint mSweepPaint1;

    /**
     * Used to draw the animated ring that sweeps out from the center
     */
    private Paint mSweepPaint2;

    /**
     * Time in millis when the most recent sweep began
     */
    private long mSweepTime;

    /**
     * True if the sweep has not yet intersected the blip
     */
    private boolean mSweepBefore;

    /**
     * Time in millis when the sweep last crossed the blip
     */
    private long mBlipTime;

    /**
     * True if the display should use metric units; false if the display should use standard
     * units
     */
    private boolean mUseMetric;

    /**
     * Time in millis for the last time GPS reported a location
     */
    private long mLastGpsFixTime = 0L;

    /**
     * The last location reported by the network provider. Use this if we can't get a location from
     * GPS
     */
    private Location mNetworkLocation;

    /**
     * True if GPS is reporting a location
     */
    private boolean mGpsAvailable;

    /**
     * True if the network provider is reporting a location
     */
    private boolean mNetworkAvailable;

    public RadarView(Context context) {
        this(context, null);
    }
    
    public RadarView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    
    public RadarView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        
        // Paint used for the rings and ring text
        mGridPaint = new Paint();
        mGridPaint.setColor(0xFF00FF00);
        mGridPaint.setAntiAlias(true);
        mGridPaint.setStyle(Style.STROKE);
        mGridPaint.setStrokeWidth(1.0f);
        mGridPaint.setTextSize(10.0f);
        mGridPaint.setTextAlign(Align.CENTER);
        
        // Paint used to erase the rectangle behing the ring text
        mErasePaint = new Paint();
        mErasePaint.setColor(0xFF191919);
        mErasePaint.setStyle(Style.FILL);
        
        // Outer ring of the sweep
        mSweepPaint0 = new Paint();
        mSweepPaint0.setColor(0xFF33FF33);
        mSweepPaint0.setAntiAlias(true);
        mSweepPaint0.setStyle(Style.STROKE);
        mSweepPaint0.setStrokeWidth(2f);
        
        // Middle ring of the sweep
        mSweepPaint1 = new Paint();
        mSweepPaint1.setColor(0x7733FF33);
        mSweepPaint1.setAntiAlias(true);
        mSweepPaint1.setStyle(Style.STROKE);
        mSweepPaint1.setStrokeWidth(2f);
        
        // Inner ring of the sweep
        mSweepPaint2 = new Paint();
        mSweepPaint2.setColor(0x3333FF33);
        mSweepPaint2.setAntiAlias(true);
        mSweepPaint2.setStyle(Style.STROKE);
        mSweepPaint2.setStrokeWidth(2f);

        mBlip = ((BitmapDrawable)getResources().getDrawable(R.drawable.blip)).getBitmap();
    }
    
    /**
     * Sets the target to track on the radar
     * @param latE6 Latitude of the target, multiplied by 1,000,000
     * @param lonE6 Longitude of the target, multiplied by 1,000,000
     */
    public void setTarget(int latE6, int lonE6) {
        mTargetLat = latE6 / (double) GeoUtils.MILLION;
        mTargetLon = lonE6 / (double) GeoUtils.MILLION;
    }
    
    /**
     * Sets the view that we will use to report distance
     * 
     * @param t The text view used to report distance
     */
    public void setDistanceView(TextView t) {
        mDistanceView = t;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int center = getWidth() / 2;
        int radius = center - 8;

        // Draw the rings
        final Paint gridPaint = mGridPaint;
        canvas.drawCircle(center, center, radius, gridPaint);
        canvas.drawCircle(center, center, radius * 3 / 4, gridPaint);
        canvas.drawCircle(center, center, radius >> 1, gridPaint);
        canvas.drawCircle(center, center, radius >> 2, gridPaint);
        
        int blipRadius = (int) (mDistanceRatio * radius);
        
        final long now = SystemClock.uptimeMillis();
        if (mSweepTime > 0 && mHaveLocation) {
            // Draw the sweep. Radius is determined by how long ago it started
            long sweepDifference = now - mSweepTime;
            if (sweepDifference < 512L) {
                int sweepRadius = (int) (((radius + 6) * sweepDifference) >> 9);
                canvas.drawCircle(center, center, sweepRadius, mSweepPaint0);
                canvas.drawCircle(center, center, sweepRadius - 2, mSweepPaint1);
                canvas.drawCircle(center, center, sweepRadius - 4, mSweepPaint2);
                
                // Note when the sweep has passed the blip
                boolean before = sweepRadius < blipRadius;
                if (!before && mSweepBefore) {
                    mSweepBefore = false;
                    mBlipTime = now;
                }
            } else {
                mSweepTime = now + 1000;
                mSweepBefore = true;
            }
            postInvalidate();
        }
        
        // Draw horizontal and vertical lines
        canvas.drawLine(center, center - (radius >> 2) + 6, center, center - radius - 6, gridPaint);
        canvas.drawLine(center, center + (radius >> 2) - 6 , center, center + radius + 6, gridPaint);
        canvas.drawLine(center - (radius >> 2) + 6, center, center - radius - 6, center, gridPaint); 
        canvas.drawLine(center + (radius >> 2) - 6, center, center + radius + 6, center, gridPaint); 

        // Draw X in the center of the screen
        canvas.drawLine(center - 4, center - 4, center + 4, center + 4, gridPaint);
        canvas.drawLine(center - 4, center + 4, center + 4, center - 4, gridPaint); 
        
        if (mHaveLocation) {            
            double bearingToTarget = mBearing - mOrientation;
            double drawingAngle = Math.toRadians(bearingToTarget) - (Math.PI / 2);
    
            float cos = (float) Math.cos(drawingAngle);
            float sin = (float) Math.sin(drawingAngle);
    
            // Draw the text for the rings
            final String[] distanceScale = mDistanceScale;
            
            addText(canvas, distanceScale[0], center, center + (radius >> 2));
            addText(canvas, distanceScale[1], center, center + (radius >> 1));
            addText(canvas, distanceScale[2], center, center + radius * 3 / 4);
            addText(canvas, distanceScale[3], center, center + radius);
    
            // Draw the blip. Alpha is based on how long ago the sweep crossed the blip
            long blipDifference = now - mBlipTime;
            gridPaint.setAlpha(255 - (int)((128 * blipDifference) >> 10));
            canvas.drawBitmap(mBlip, center + (cos * blipRadius) - 8 , 
                    center + (sin * blipRadius) - 8, gridPaint);
            gridPaint.setAlpha(255);
        }
    }
    
    private void addText(Canvas canvas, String str, int x, int y) {
        
        mGridPaint.getTextBounds(str, 0, str.length(), mTextBounds);
        mTextBounds.offset(x - (mTextBounds.width() >> 1), y);
        mTextBounds.inset(-2, -2);
        canvas.drawRect(mTextBounds, mErasePaint);
        canvas.drawText(str, x, y, mGridPaint);
    }

    public void onAccuracyChanged(int sensor, int accuracy) {
    }

    /**
     * Called when we get a new value from the compass
     * 
     * @see android.hardware.SensorListener#onSensorChanged(int, float[])
     */
    public void onSensorChanged(int sensor, float[] values) {
        mOrientation = values[0];
        postInvalidate();
    }

    /**
     * Called when a location provider has a new location to report
     * 
     * @see android.location.LocationListener#onLocationChanged(android.location.Location)
     */
    public void onLocationChanged(Location location) {
        if (!mHaveLocation) {
            mHaveLocation = true;
        }

        final long now = SystemClock.uptimeMillis();
        boolean useLocation = false;
        final String provider = location.getProvider();
        if (LocationManager.GPS_PROVIDER.equals(provider)) {
            // Use GPS if available
            mLastGpsFixTime = SystemClock.uptimeMillis();
            useLocation = true;
        } else if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
            // Use network provider if GPS is getting stale
            useLocation = now - mLastGpsFixTime > RETAIN_GPS_MILLIS;
            if (mNetworkLocation == null) {
                mNetworkLocation = new Location(location);
            } else {
                mNetworkLocation.set(location);
            }
            
            mLastGpsFixTime = 0L;
        }
        if (useLocation) {
            mMyLocationLat = location.getLatitude();
            mMyLocationLon = location.getLongitude();
    
            mDistance = GeoUtils.distanceKm(mMyLocationLat, mMyLocationLon, mTargetLat,
                    mTargetLon);
    
            mBearing = GeoUtils.bearing(mMyLocationLat, mMyLocationLon, mTargetLat,
                    mTargetLon);
    
            updateDistance(mDistance);
        }
    }

    public void onProviderDisabled(String provider) {        
    }

    public void onProviderEnabled(String provider) { 
    }

    /**
     * Called when a location provider has changed its availability.
     * 
     * @see android.location.LocationListener#onStatusChanged(java.lang.String, int, android.os.Bundle)
     */
    public void onStatusChanged(String provider, int status, Bundle extras) { 
        
        if (LocationManager.GPS_PROVIDER.equals(provider)) {
            switch (status) {
            case LocationProvider.AVAILABLE:
                mGpsAvailable = true;
                break;
            case LocationProvider.OUT_OF_SERVICE:
            case LocationProvider.TEMPORARILY_UNAVAILABLE:
                mGpsAvailable = false;
                
                if (mNetworkLocation != null && mNetworkAvailable) {
                    // Fallback to network location
                    mLastGpsFixTime = 0L;
                    onLocationChanged(mNetworkLocation);
                } else {
                    handleUnknownLocation();
                }
             
                break;
            }

        } else if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
            switch (status) {
            case LocationProvider.AVAILABLE:
                mNetworkAvailable = true;
                break;
            case LocationProvider.OUT_OF_SERVICE:
            case LocationProvider.TEMPORARILY_UNAVAILABLE:
                mNetworkAvailable = false;
                
                if (!mGpsAvailable) {
                    handleUnknownLocation();
                }
                break;
            }
        }
    }
    
    /**
     * Called when we no longer have a valid lcoation.
     */
    private void handleUnknownLocation() {
        mHaveLocation = false;
        mDistanceView.setText(R.string.scanning);   
    }
    
    /**
     * Update state to reflect whether we are using metric or standard units.
     * 
     * @param useMetric True if the display should use metric units
     */
    public void setUseMetric(boolean useMetric) {
        mUseMetric = useMetric;
        mLastScale = -1;
        if (mHaveLocation) {
            updateDistance(mDistance);
        }
        invalidate();
    }
    
    /**
     * Update our state to reflect a new distance to the target. This may require
     * choosing a new scale for the radar rings.
     * 
     * @param distanceKm The new distance to the target
     */
    private void updateDistance(double distanceKm) {
        final double[] scaleChoices;
        final float[] displayUnitsPerKm;
        final String[] displayFormats;
        final String[] scaleFormats;
        String distanceStr = null;
        if (mUseMetric) {
            scaleChoices = mMetricScaleChoices;
            displayUnitsPerKm = mMetricDisplayUnitsPerKm;
            displayFormats = mMetricDisplayFormats;
            scaleFormats = mMetricScaleFormats;
        } else {
            scaleChoices = mEnglishScaleChoices;
            displayUnitsPerKm = mEnglishDisplayUnitsPerKm;
            displayFormats = mEnglishDisplayFormats;
            scaleFormats = mEnglishScaleFormats;
        }
        
        int count = scaleChoices.length;
        for (int i = 0; i < count; i++) {
            if (distanceKm < scaleChoices[i] || i == (count - 1)) {
                String format = displayFormats[i];
                double distanceDisplay = distanceKm * displayUnitsPerKm[i];
                if (mLastScale != i) {
                    mLastScale = i;
                    String scaleFormat = scaleFormats[i];
                    float scaleDistance = (float) (scaleChoices[i] * displayUnitsPerKm[i]);
                    mDistanceScale[0] = String.format(scaleFormat, (scaleDistance / 4));
                    mDistanceScale[1] = String.format(scaleFormat, (scaleDistance / 2));
                    mDistanceScale[2] = String.format(scaleFormat, (scaleDistance * 3 / 4));
                    mDistanceScale[3] = String.format(scaleFormat, scaleDistance);
                }
                mDistanceRatio = (float) (mDistance / scaleChoices[mLastScale]);
                distanceStr = String.format(format, distanceDisplay);
                break;
            }
        }
        mDistanceView.setText(distanceStr);
    }

    /**
     * Turn on the sweep animation starting with the next draw
     */
    public void startSweep() {
        mSweepTime = SystemClock.uptimeMillis();
        mSweepBefore = true;
    }
    
    /**
     * Turn off the sweep animation
     */
    public void stopSweep() {
        mSweepTime = 0L;
    }
}

Other Android examples (source code examples)

Here is a short list of links related to this Android RadarView.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.