Java tutorial
// copyedited from https://code.google.com/p/android-salat-times/source/browse/src/com/cepmuvakkit/times/view/QiblaCompassView.java // licensed under GPLv3 package ir.hatamiarash.calendar.view; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.DashPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.View; import com.cepmuvakkit.times.posAlgo.AstroLib; import com.cepmuvakkit.times.posAlgo.EarthHeading; import com.cepmuvakkit.times.posAlgo.Horizontal; import com.cepmuvakkit.times.posAlgo.SunMoonPosition; import java.util.GregorianCalendar; import ir.hatamiarash.calendar.R; public class QiblaCompassView extends View { private Paint dashedPaint; private int px, py; // Center of Compass (px,py) private int Radius; // Radius of Compass dial private int r; // Radius of Sun and Moon private String northString, eastString, southString, westString; private DashPathEffect dashPath; private float bearing; private Horizontal sunPosition, moonPosition; private EarthHeading qiblaInfo; private SunMoonPosition sunMoonPosition; private double longitude = 0.0; private double latitude = 0.0; private Paint textPaint; public QiblaCompassView(Context context) { super(context); initCompassView(); } public QiblaCompassView(Context context, AttributeSet attrs) { super(context, attrs); initCompassView(); } public QiblaCompassView(Context context, AttributeSet ats, int defaultStyle) { super(context, ats, defaultStyle); initCompassView(); } private void initAstronomicParameters() { GregorianCalendar c = new GregorianCalendar(); double jd = AstroLib.calculateJulianDay(c); double T = 0; double altitude = 0.0; sunMoonPosition = new SunMoonPosition(jd, latitude, longitude, altitude, T); sunPosition = sunMoonPosition.getSunPosition(); moonPosition = sunMoonPosition.getMoonPosition(); } public void initCompassView() { setFocusable(true); initAstronomicParameters(); northString = "N"; eastString = "E"; southString = "S"; westString = "W"; dashPath = new DashPathEffect(new float[] { 2, 5 }, 1); dashedPaint = new Paint(Paint.FAKE_BOLD_TEXT_FLAG); dashedPaint.setPathEffect(dashPath); dashedPaint.setStrokeWidth(2); dashedPaint.setPathEffect(dashPath); dashedPaint.setColor(ContextCompat.getColor(getContext(), R.color.qibla_color)); textPaint = new Paint(Paint.FAKE_BOLD_TEXT_FLAG); textPaint.setColor(ContextCompat.getColor(getContext(), (R.color.qibla_color))); textPaint.setTextSize(20); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // The compass is a circle that fills as much space as possible. // Set the measured dimensions by figuring out the shortest boundary, // height or width. int measuredWidth = measure(widthMeasureSpec); int measuredHeight = measure(heightMeasureSpec); // int d = Math.min(measuredWidth, measuredHeight); setMeasuredDimension(measuredWidth, measuredHeight); } private int measure(int measureSpec) { int result; // Decode the measurement specifications. int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.UNSPECIFIED) // Return a default size of 200 if no bounds are specified. result = 600; else { // As you want to fill the available space // always return the full available bounds. result = specSize; } return result; } @Override protected void onDraw(Canvas canvas) { this.Radius = Math.min(px, py); this.r = Radius / 10; // Sun Moon radius; // over here qiblaInfo = sunMoonPosition.getDestinationHeading(); textPaint.setTextAlign(Paint.Align.LEFT); textPaint.setColor(ContextCompat.getColor(getContext(), (R.color.qibla_color))); canvas.rotate(-bearing, px, py);// Attach and Detach capability lies canvas.save(); drawDial(canvas); if (isLongLatAvailable()) { canvas.save(); drawQibla(canvas); } canvas.save(); drawTrueNorthArrow(canvas, bearing); if (isLongLatAvailable()) { canvas.save(); drawMoon(canvas); canvas.save(); drawSun(canvas); } canvas.save(); } public boolean isLongLatAvailable() { return longitude != 0.0 && latitude != 0.0; } Path mPath = new Path(); Paint trueNorthArrowPaint = new Paint(Paint.ANTI_ALIAS_FLAG); public void drawTrueNorthArrow(Canvas canvas, float drawnAngle) { trueNorthArrowPaint.reset(); trueNorthArrowPaint.setColor(Color.RED); trueNorthArrowPaint.setStyle(Paint.Style.FILL); trueNorthArrowPaint.setAlpha(100); int r = Radius / 12; // Construct a wedge-shaped path mPath.reset(); mPath.moveTo(px, py - px); mPath.lineTo(px - r, py); mPath.lineTo(px, py + r); mPath.lineTo(px + r, py); mPath.addCircle(px, py, r, Path.Direction.CCW); mPath.close(); canvas.drawPath(mPath, trueNorthArrowPaint); dashedPaint.setColor(Color.RED); canvas.drawLine(px, py - px, px, py + Radius, dashedPaint); canvas.drawCircle(px, py, 5, dashedPaint); canvas.restore(); } Paint markerPaint = new Paint(Paint.FAKE_BOLD_TEXT_FLAG); Paint circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); public void drawDial(Canvas canvas) { // over here circlePaint.reset(); circlePaint.setColor(ContextCompat.getColor(getContext(), R.color.qibla_color)); circlePaint.setStrokeWidth(1); circlePaint.setStyle(Paint.Style.STROKE); // Sadece Cember ciziyor. int textHeight = (int) textPaint.measureText("yY"); markerPaint.reset(); markerPaint.setColor(ContextCompat.getColor(getContext(), R.color.qibla_color)); // Draw the background canvas.drawCircle(px, py, Radius, circlePaint); canvas.drawCircle(px, py, Radius - 20, circlePaint); // Rotate our perspective so that the "top" is // facing the current bearing. int textWidth = (int) textPaint.measureText("W"); int cardinalX = px - textWidth / 2; int cardinalY = py - Radius + textHeight; // Draw the marker every 15 degrees and text every 45. for (int i = 0; i < 24; i++) { // Draw a marker. canvas.drawLine(px, py - Radius, px, py - Radius + 10, markerPaint); canvas.save(); canvas.translate(0, textHeight); // Draw the cardinal points if (i % 6 == 0) { String dirString = ""; switch (i) { case (0): { dirString = northString; break; } case (6): dirString = eastString; break; case (12): dirString = southString; break; case (18): dirString = westString; break; } canvas.drawText(dirString, cardinalX, cardinalY, textPaint); } else if (i % 3 == 0) { // Draw the text every alternate 45deg String angle = String.valueOf(i * 15); float angleTextWidth = textPaint.measureText(angle); int angleTextX = (int) (px - angleTextWidth / 2); int angleTextY = py - Radius + textHeight; canvas.drawText(angle, angleTextX, angleTextY, textPaint); } canvas.restore(); canvas.rotate(15, px, py); } } Paint sunPaint = new Paint(Paint.ANTI_ALIAS_FLAG); public void drawSun(Canvas canvas) { sunPaint.reset(); sunPaint.setColor(Color.YELLOW); sunPaint.setStyle(Paint.Style.FILL_AND_STROKE); // Horizontal sunPosition = new Horizontal(225, 45); if (sunPosition.getElevation() > -10) { canvas.rotate((float) sunPosition.getAzimuth() - 360, px, py); sunPaint.setPathEffect(dashPath); int ry = (int) (((90 - sunPosition.getElevation()) / 90) * Radius); canvas.drawCircle(px, py - ry, r, sunPaint); dashedPaint.setColor(Color.YELLOW); canvas.drawLine(px, py - Radius, px, py + Radius, dashedPaint); sunPaint.setPathEffect(null); canvas.restore(); } } Paint moonPaint = new Paint(Paint.ANTI_ALIAS_FLAG); Paint moonPaintB = new Paint(Paint.ANTI_ALIAS_FLAG); Paint moonPaintO = new Paint(Paint.ANTI_ALIAS_FLAG); Paint moonPaintD = new Paint(Paint.ANTI_ALIAS_FLAG); RectF moonRect = new RectF(); RectF moonOval = new RectF(); public void drawMoon(Canvas canvas) { moonPaint.reset(); moonPaint.setColor(Color.WHITE); moonPaint.setStyle(Paint.Style.FILL_AND_STROKE); moonPaintB.reset();// moon Paint Black moonPaintB.setColor(Color.BLACK); moonPaintB.setStyle(Paint.Style.FILL_AND_STROKE); moonPaintO.reset();// moon Paint for Oval moonPaintO.setColor(Color.WHITE); moonPaintO.setStyle(Paint.Style.FILL_AND_STROKE); moonPaintD.reset();// moon Paint for Diameter // draw moonPaintD.setColor(Color.GRAY); moonPaintD.setStyle(Paint.Style.STROKE); double moonPhase = sunMoonPosition.getMoonPhase(); if (moonPosition.getElevation() > -5) { canvas.rotate((float) moonPosition.getAzimuth() - 360, px, py); int eOffset = (int) ((moonPosition.getElevation() / 90) * Radius); // elevation Offset 0 for 0 degree; r for 90 degree moonRect.set(px - r, py + eOffset - Radius - r, px + r, py + eOffset - Radius + r); canvas.drawArc(moonRect, 90, 180, false, moonPaint); canvas.drawArc(moonRect, 270, 180, false, moonPaintB); int arcWidth = (int) ((moonPhase - 0.5) * (4 * r)); moonPaintO.setColor(arcWidth < 0 ? Color.BLACK : Color.WHITE); moonOval.set(px - Math.abs(arcWidth) / 2, py + eOffset - Radius - r, px + Math.abs(arcWidth) / 2, py + eOffset - Radius + r); canvas.drawArc(moonOval, 0, 360, false, moonPaintO); canvas.drawArc(moonRect, 0, 360, false, moonPaintD); moonPaintD.setPathEffect(dashPath); canvas.drawLine(px, py - Radius, px, py + Radius, moonPaintD); moonPaintD.setPathEffect(null); canvas.restore(); } } Paint qiblaPaint = new Paint(Paint.ANTI_ALIAS_FLAG); Bitmap kaaba = BitmapFactory.decodeResource(getResources(), R.drawable.kaaba); public void drawQibla(Canvas canvas) { canvas.rotate((float) qiblaInfo.getHeading() - 360, px, py); qiblaPaint.reset(); qiblaPaint.setColor(Color.GREEN); qiblaPaint.setStyle(Paint.Style.FILL_AND_STROKE); qiblaPaint.setPathEffect(dashPath); qiblaPaint.setStrokeWidth(5.5f); canvas.drawLine(px, py - Radius, px, py + Radius, qiblaPaint); qiblaPaint.setPathEffect(null); canvas.drawBitmap(kaaba, px - kaaba.getWidth() / 2, py - Radius - kaaba.getHeight() / 2, qiblaPaint); canvas.restore(); } public void setBearing(float _bearing) { bearing = _bearing; } public void setLatitude(double latitude) { this.latitude = latitude; } public void setLongitude(double longitude) { this.longitude = longitude; } public void setScreenResolution(int widthPixels, int heightPixels) { this.px = widthPixels / 2; this.py = heightPixels / 2; } }