Java tutorial
/* * * Copyright (c) 2015 SameBits UG. All rights reserved. * * 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.samebits.beacon.locator.ui.view; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.SystemClock; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.View; import android.view.WindowManager; import android.widget.TextView; import com.samebits.beacon.locator.R; import com.samebits.beacon.locator.model.DetectedBeacon; import com.samebits.beacon.locator.util.AngleLowpassFilter; import org.altbeacon.beacon.Beacon; import java.text.DecimalFormat; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; public class RadarScanView extends View implements SensorEventListener { private final static int RADAR_RADIS_VISION_METERS = 15; private static String mMetricDisplayFormat = "%.0fm"; private static String mEnglishDisplayFormat = "%.0fft"; private static float METER_PER_FEET = 0.3048f; private static float FEET_PER_METER = 3.28084f; private float mDistanceRatio = 1.0f; private float[] mLastAccelerometer = new float[3]; private float[] mLastMagnetometer = new float[3]; private boolean mLastAccelerometerSet = false; private boolean mLastMagnetometerSet = false; private float[] mOrientation = new float[3]; private AngleLowpassFilter angleLowpassFilter = new AngleLowpassFilter(); private float mLast_bearing; private Context mContext; private WindowManager mWindowManager; private Map<String, DetectedBeacon> mBeacons = new LinkedHashMap(); private boolean mHaveDetected = false; private TextView mInfoView; private Rect mTextBounds = new Rect(); private Paint mGridPaint; private Paint mErasePaint; private Bitmap mBlip; private boolean mUseMetric; /** * 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; /** * Used to draw a beacon */ private Paint mBeaconPaint; /** * 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; public RadarScanView(Context context) { this(context, null); } public RadarScanView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RadarScanView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); // Paint used for the rings and ring text mGridPaint = new Paint(); mGridPaint.setColor(getResources().getColor(R.color.hn_orange_lighter)); mGridPaint.setAntiAlias(true); mGridPaint.setStyle(Style.STROKE); mGridPaint.setStrokeWidth(2.0f); mGridPaint.setTextSize(16.0f); mGridPaint.setTextAlign(Align.CENTER); // Paint used to erase the rectangle behind the ring text mErasePaint = new Paint(); mErasePaint.setColor(getResources().getColor(R.color.white)); //mErasePaint.setColor(getResources().getColor(R.color.hn_orange_lighter)); mErasePaint.setStyle(Style.FILL); mBeaconPaint = new Paint(); mBeaconPaint.setColor(getResources().getColor(R.color.white)); mBeaconPaint.setAntiAlias(true); mBeaconPaint.setStyle(Style.FILL_AND_STROKE); // Outer ring of the sweep mSweepPaint0 = new Paint(); mSweepPaint0.setColor(ContextCompat.getColor(context, R.color.hn_orange)); mSweepPaint0.setAntiAlias(true); mSweepPaint0.setStyle(Style.STROKE); mSweepPaint0.setStrokeWidth(3f); // Middle ring of the sweep mSweepPaint1 = new Paint(); mSweepPaint1.setColor(ContextCompat.getColor(context, R.color.hn_orange)); mSweepPaint1.setAntiAlias(true); mSweepPaint1.setStyle(Style.STROKE); mSweepPaint1.setStrokeWidth(3f); // Inner ring of the sweep mSweepPaint2 = new Paint(); mSweepPaint2.setColor(ContextCompat.getColor(context, R.color.hn_orange)); mSweepPaint2.setAntiAlias(true); mSweepPaint2.setStyle(Style.STROKE); mSweepPaint2.setStrokeWidth(3f); mBlip = ((BitmapDrawable) ContextCompat.getDrawable(context, R.drawable.ic_location_on_black_24dp)) .getBitmap(); } /** * Sets the view that we will use to report distance * * @param t The text view used to report distance */ public void setDistanceView(TextView t) { mInfoView = 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) { // 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 (mHaveDetected) { // 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)); double bearingToTarget = mLast_bearing; double drawingAngle = Math.toRadians(bearingToTarget) - (Math.PI / 2); float cos = (float) Math.cos(drawingAngle); float sin = (float) Math.sin(drawingAngle); addText(canvas, getRatioDistanceText(0.25f), center, center + (radius >> 2)); addText(canvas, getRatioDistanceText(0.5f), center, center + (radius >> 1)); addText(canvas, getRatioDistanceText(0.75f), center, center + radius * 3 / 4); addText(canvas, getRatioDistanceText(1.0f), center, center + radius); for (Map.Entry<String, DetectedBeacon> entry : mBeacons.entrySet()) { //String key = entry.getKey(); DetectedBeacon dBeacon = entry.getValue(); System.out.println("value: " + dBeacon); // drawing the beacon if (((System.currentTimeMillis() - dBeacon.getTimeLastSeen()) / 1000 < 5)) { canvas.drawBitmap(mBlip, center + (cos * distanceToPix(dBeacon.getDistance())) - 8, center + (sin * distanceToPix(dBeacon.getDistance())) - 8, gridPaint); } } gridPaint.setAlpha(255); } } private String getRatioDistanceText(float ringRation) { return new DecimalFormat("##0.00").format(RADAR_RADIS_VISION_METERS * mDistanceRatio * ringRation); } /** * max radar range is 15 meters * * @param distance * @return distance in px */ private float distanceToPix(double distance) { int center = getWidth() / 2; int radius = center - 8; return Math.round((radius * distance) / RADAR_RADIS_VISION_METERS * mDistanceRatio); } 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); } /** * 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; if (mHaveDetected) { // TODO } } @Override public void onSensorChanged(SensorEvent event) { calcBearing(event); } private synchronized void calcBearing(SensorEvent event) { if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { System.arraycopy(event.values, 0, mLastAccelerometer, 0, event.values.length); mLastAccelerometerSet = true; } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { System.arraycopy(event.values, 0, mLastMagnetometer, 0, event.values.length); mLastMagnetometerSet = true; } if (mLastAccelerometerSet && mLastMagnetometerSet) { /* Create rotation Matrix */ float[] rotationMatrix = new float[9]; if (SensorManager.getRotationMatrix(rotationMatrix, null, mLastAccelerometer, mLastMagnetometer)) { SensorManager.getOrientation(rotationMatrix, mOrientation); float azimuthInRadians = mOrientation[0]; angleLowpassFilter.add(azimuthInRadians); mLast_bearing = (float) (Math.toDegrees(angleLowpassFilter.average()) + 360) % 360; postInvalidate(); //Log.d(Constants.TAG, "orientation bearing: " + mLast_bearing); } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } private void insertBeacons(Collection<Beacon> beacons) { Iterator<Beacon> iterator = beacons.iterator(); while (iterator.hasNext()) { DetectedBeacon dBeacon = new DetectedBeacon(iterator.next()); dBeacon.setTimeLastSeen(System.currentTimeMillis()); this.mBeacons.put(dBeacon.getId(), dBeacon); } } public void onDetectedBeacons(final Collection<Beacon> beacons) { insertBeacons(beacons); updateDistances(); updateBeaconsInfo(beacons); invalidate(); } private void updateBeaconsInfo(final Collection<Beacon> beacons) { mInfoView.setText( String.format(getResources().getString(R.string.text_scanner_found_beacons_size), beacons.size())); } /** * update on radar */ private void updateDistances() { if (!mHaveDetected) { mHaveDetected = true; } } private void updateDistanceText(double distanceM) { String displayDistance; if (mUseMetric) { displayDistance = String.format(mMetricDisplayFormat, distanceM); } else { displayDistance = String.format(mEnglishDisplayFormat, distanceM * FEET_PER_METER); } mInfoView.setText(displayDistance); } /** * Turn on the sweep animation starting with the next draw */ public void startSweep() { mInfoView.setText(R.string.text_scanning); mSweepTime = SystemClock.uptimeMillis(); mSweepBefore = true; } /** * Turn off the sweep animation */ public void stopSweep() { mSweepTime = 0L; mInfoView.setText(""); } }