package com.duy.compass.sensor.drawer; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.Typeface; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import com.duy.compass.utils.DLog; import com.duy.compass.R; import com.duy.compass.sensor.model.SensorValue; import com.duy.compass.location.model.Sunshine; import com.duy.compass.utils.TypefaceManager; import java.util.Locale; import static com.duy.compass.utils.Utility.getDirectionText; public class CompassDrawer { private static final String TAG = "CanvasHelper"; private final Paint mNumberTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mDirectionTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mMagneticPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mSecondaryTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mPrimaryTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Path mPath = new Path(); private final SensorValue mSensorValue = new SensorValue(); private final float mMaxRadius = 430; /*Color*/ @ColorInt private int mForegroundColor; @ColorInt private int mBackgroundColor; @ColorInt private int mPrimaryTextColor; @ColorInt private int mSecondaryTextColor; @ColorInt private int mAccentColor; @NonNull private Context mContext; private Typeface mTypeface; @Nullable private Sunshine mSunshine = new Sunshine(30, 123); private float mPixelScale; private Point mCenter; private float mUnitPadding; @Nullable private Path mClockPathSecondary = null; @Nullable private Path mClockPathPrimary = null; private boolean mIsPaintCreated = false; public CompassDrawer(@NonNull Context context) { this.mContext = context; this.mTypeface = TypefaceManager.get(context, "Roboto-Light.ttf"); } public SensorValue getSensorValue() { return mSensorValue; } public void draw(Canvas canvas) { mPixelScale = ((float) Math.min(canvas.getWidth(), canvas.getHeight())) / 1000.0f; mCenter = new Point(canvas.getWidth() / 2, canvas.getHeight() / 2); mUnitPadding = realPx(5); initPaint(); drawBackground(canvas); drawMagnetic(canvas); drawClock(canvas); drawAzimuthValue(canvas); //drawSunTime(canvas); } private void initPaint() { mNumberTextPaint.setTextSize(realPx(30)); mNumberTextPaint.setColor(mPrimaryTextColor); mNumberTextPaint.setTypeface(mTypeface); //no need setup if (mIsPaintCreated) { return; } mDirectionTextPaint.setTextSize(realPx(60)); mDirectionTextPaint.setTypeface(mTypeface); LinearGradient gradient = new LinearGradient(0, 0, 0, realPx(500), new int[]{Color.GREEN, Color.GREEN, Color.RED, Color.RED}, null, Shader.TileMode.MIRROR); mMagneticPaint.setShader(gradient); mMagneticPaint.setStrokeWidth(realPx(25)); mMagneticPaint.setStyle(Style.STROKE); mMagneticPaint.setStrokeCap(Paint.Cap.ROUND); mForegroundColor = ContextCompat.getColor(mContext, R.color.compass_foreground_color); mBackgroundColor = ContextCompat.getColor(mContext, R.color.compass_background_color); mPrimaryTextColor = ContextCompat.getColor(mContext, R.color.compass_text_primary_color); mSecondaryTextColor = ContextCompat.getColor(mContext, R.color.compass_text_secondary_color); mAccentColor = ContextCompat.getColor(mContext, R.color.compass_accent_color); mBackgroundPaint.setColor(mBackgroundColor); mBackgroundPaint.setStyle(Style.FILL); mPathPaint.setStrokeCap(Paint.Cap.ROUND); mSecondaryTextPaint.setColor(mSecondaryTextColor); mSecondaryTextPaint.setTypeface(mTypeface); mPrimaryTextPaint.setColor(mPrimaryTextColor); mPrimaryTextPaint.setTypeface(mTypeface); mIsPaintCreated = true; } private void drawBackground(Canvas canvas) { float radius = realPx(mMaxRadius); canvas.drawCircle(mCenter.x, mCenter.y, radius, mBackgroundPaint); mPathPaint.setColor(mSecondaryTextColor); mPathPaint.setStyle(Style.STROKE); mPathPaint.setStrokeWidth(realPx(3)); canvas.drawCircle(mCenter.x, mCenter.y, radius, mPathPaint); mPathPaint.setColor(mForegroundColor); Paint.FontMetrics fm = mNumberTextPaint.getFontMetrics(); float fontHeight = fm.bottom - fm.top + fm.leading; float strokeWidth = realPx(20) + fontHeight; mPathPaint.setStrokeWidth(strokeWidth); radius = realPx(350) - strokeWidth / 2.0f - realPx(mUnitPadding); canvas.drawCircle(mCenter.x, mCenter.y, radius, mPathPaint); } private void drawMagnetic(Canvas canvas) { float step = realPx(450); mPathPaint.setStrokeWidth(realPx(25)); mPathPaint.setColor(mBackgroundColor); RectF bound = new RectF(mCenter.x - step, mCenter.y - step, mCenter.x + step, mCenter.y + step); int sweepAngle = 100; mPath.reset(); mPath.addArc(bound, 310, 100); canvas.drawPath(mPath, mPathPaint); float magneticField = mSensorValue.getMagneticField(); int max = 160; float percent = Math.min(1, magneticField / max); percent = percent * sweepAngle; mPath.reset(); mPath.addArc(bound, 310 + sweepAngle - percent, percent); canvas.drawPath(mPath, mMagneticPaint); mPrimaryTextPaint.setTextSize(realPx(30)); drawText(canvas, 303, String.format(Locale.US, "%dμT", (int) mSensorValue.getMagneticField()), 445, mPrimaryTextPaint); mSecondaryTextPaint.setTextSize(realPx(30)); drawText(canvas, 60, "mag.field", 445, mSecondaryTextPaint); } private void drawSunTime(Canvas canvas) { if (mSunshine == null) return; float sunRise = mSunshine.getSunrise(); float sunShine = mSunshine.getSunset(); mPathPaint.setColor(Color.YELLOW); mPathPaint.setStyle(Style.STROKE); mPathPaint.setStrokeWidth(realPx(10)); float step = realPx(405); mPath.reset(); RectF bound = new RectF(mCenter.x - step, mCenter.y - step, mCenter.x + step, mCenter.y + step); mPath.addArc(bound, sunRise, Math.abs(sunShine - sunRise)); canvas.drawPath(mPath, mPathPaint); } private void drawAzimuthValue(Canvas canvas) { //draw triangle float x = mCenter.x; float length = realPx(30); float y = (mCenter.y - realPx(mMaxRadius) + length / 2.0f); mPath.reset(); mPath.lineTo(x - length / 2.0f, y - length); mPath.lineTo(x + length / 2.0f, y - length); mPath.lineTo(x, y); mPath.lineTo(x - length / 2.0f, y - length); mPathPaint.setStyle(Style.FILL); mPathPaint.setColor(mAccentColor); mPathPaint.setShadowLayer(realPx(4), 0, 0, Color.RED); canvas.drawPath(mPath, mPathPaint); mPathPaint.reset(); mPathPaint.setAntiAlias(true); mPathPaint.setStrokeCap(Paint.Cap.ROUND); // length = realPx(16); // y = (mCenter.y - realPx(mMaxRadius) + length / 2.0f); // mPath.reset(); // mPath.lineTo(x - length / 2.0f, y - length); // mPath.lineTo(x + length / 2.0f, y - length); // mPath.lineTo(x, y); // mPath.lineTo(x - length / 2.0f, y - length); // // mPathPaint.setStyle(Style.FILL); // mPathPaint.setColor(mAccentColor); // canvas.drawPath(mPath, mPathPaint); mPrimaryTextPaint.setTextSize(realPx(80)); String str = ((int) mSensorValue.getAzimuth()) + "° " + getDirectionText(mSensorValue.getAzimuth()); Rect rectF = new Rect(); mPrimaryTextPaint.getTextBounds(str, 0, str.length(), rectF); y = mCenter.y + rectF.height() / 2.0f; x = mCenter.x - mPrimaryTextPaint.measureText(str) / 2.0f; canvas.drawText(str, x, y, mPrimaryTextPaint); } private void drawClock(Canvas canvas) { canvas.save(); canvas.rotate(-mSensorValue.getAzimuth(), mCenter.x, mCenter.y); drawClock(canvas, mCenter); drawClockBig(canvas, mCenter); drawNumber(canvas); drawDirectionText(canvas); canvas.restore(); } private void drawClock(Canvas canvas, Point center) { mPathPaint.setColor(mForegroundColor); mPathPaint.setStyle(Style.STROKE); mPathPaint.setStrokeWidth(realPx(3)); if (mClockPathSecondary == null) { mClockPathSecondary = new Path(); float degreeStep = 2.5f; for (float step = 0.0f; step < 2 * Math.PI; step += Math.toRadians(degreeStep)) { float cos = (float) Math.cos(step); float sin = (float) Math.sin(step); float x = realPx(350) * cos; float y = realPx(350) * sin; mClockPathSecondary.moveTo(x + ((float) center.x), y + ((float) center.y)); x = realPx(380) * cos; y = realPx(380) * sin; mClockPathSecondary.lineTo(x + ((float) center.x), y + ((float) center.y)); } } canvas.drawPath(mClockPathSecondary, mPathPaint); } private float realPx(float width) { return width * mPixelScale; } private void drawClockBig(Canvas canvas, Point center) { mPathPaint.setStrokeWidth(realPx(7)); if (mClockPathPrimary == null) { mClockPathPrimary = new Path(); float degreeStep = 30.0f; for (float step = 0.0f; step < 2 * Math.PI; step += Math.toRadians(degreeStep)) { float cos = (float) Math.cos(step); float sin = (float) Math.sin(step); float x = realPx(330) * cos; float y = realPx(330) * sin; mClockPathPrimary.moveTo(x + ((float) center.x), y + ((float) center.y)); cos *= realPx(380); sin *= realPx(380); mClockPathPrimary.lineTo(cos + ((float) center.x), sin + ((float) center.y)); } } mPathPaint.setColor(Color.WHITE); canvas.drawPath(mClockPathPrimary, mPathPaint); mPath.reset(); float radian = (float) Math.toRadians(270.0d); float cos = (float) Math.cos((double) radian); float sin = (float) Math.sin((double) radian); float x = realPx(320) * cos; float y = realPx(320) * sin; mPath.moveTo(((float) mCenter.x) + x, ((float) mCenter.y) + y); x = realPx(400) * cos; y = realPx(400) * sin; mPath.lineTo(x + ((float) mCenter.x), y + ((float) mCenter.y)); mPathPaint.setColor(mAccentColor); mPathPaint.setStrokeWidth(realPx(9)); canvas.drawPath(mPath, mPathPaint); } private void drawNumber(Canvas canvas) { float radius = 330; drawNumber(canvas, 300.0f, "30", radius); drawNumber(canvas, 330.0f, "60", radius); drawNumber(canvas, 360.0f, "90", radius); drawNumber(canvas, 30.0f, "120", radius); drawNumber(canvas, 60.0f, "150", radius); drawNumber(canvas, 90.0f, "180", radius); drawNumber(canvas, 120.0f, "210", radius); drawNumber(canvas, 150.0f, "240", radius); drawNumber(canvas, 180.0f, "270", radius); drawNumber(canvas, 210.0f, "300", radius); drawNumber(canvas, 240.0f, "330", radius); } private void drawNumber(Canvas canvas, float degree, String text, float radius) { Paint.FontMetrics fm = mNumberTextPaint.getFontMetrics(); float height = fm.bottom - fm.top + fm.leading; float cos = (float) Math.cos(Math.toRadians(degree)); float sin = (float) Math.sin(Math.toRadians(degree)); float x = (cos * realPx(radius)) + mCenter.x; float y = (sin * realPx(radius)) + mCenter.y; canvas.save(); canvas.translate(x, y); canvas.rotate(90.0f + degree); canvas.drawText(text, -mNumberTextPaint.measureText(text) / 2.0f, height, mNumberTextPaint); canvas.restore(); } private void drawText(Canvas canvas, float degree, String text, float radius, Paint paint) { Paint.FontMetrics fm = paint.getFontMetrics(); float height = fm.bottom - fm.top + fm.leading; float cos = (float) Math.cos(Math.toRadians(degree)); float sin = (float) Math.sin(Math.toRadians(degree)); float x = (cos * realPx(radius)) + mCenter.x; float y = (sin * realPx(radius)) + mCenter.y; // canvas.drawPoint(x, y, mNumberPaint); // canvas.drawPoint(mCenter.x, mCenter.y, mNumberPaint); canvas.save(); canvas.translate(x, y); if (degree > 0 && degree < 180) { canvas.rotate(270 + degree); canvas.drawText(text, -paint.measureText(text) / 2.0f, height / 2, paint); } else { canvas.rotate(90 + degree); canvas.drawText(text, -paint.measureText(text) / 2.0f, 0, paint); } canvas.restore(); } private void drawDirectionText(Canvas canvas) { //draw direction N S E W //N = 0, E = 90, S = 180, W = 270 Paint.FontMetrics fm = mNumberTextPaint.getFontMetrics(); float fontHeight = fm.bottom - fm.top + fm.leading; float radiusPx = realPx(330) - fontHeight - realPx(mUnitPadding); mDirectionTextPaint.setColor(mAccentColor); mDirectionTextPaint.setTextSize(realPx(60)); drawDirectionText(canvas, 270, "N", radiusPx, mDirectionTextPaint); mDirectionTextPaint.setColor(mPrimaryTextColor); drawDirectionText(canvas, 0, "E", radiusPx, mDirectionTextPaint); drawDirectionText(canvas, 90, "S", radiusPx, mDirectionTextPaint); drawDirectionText(canvas, 180, "W", radiusPx, mDirectionTextPaint); mDirectionTextPaint.setTextSize(realPx(40)); mDirectionTextPaint.setColor(mSecondaryTextColor); drawDirectionText(canvas, 315, "NE", radiusPx, mDirectionTextPaint); drawDirectionText(canvas, 45, "SE", radiusPx, mDirectionTextPaint); drawDirectionText(canvas, 135, "SW", radiusPx, mDirectionTextPaint); drawDirectionText(canvas, 225, "NW", radiusPx, mDirectionTextPaint); } private void drawDirectionText(Canvas canvas, float degree, String text, float radiusPx, Paint paint) { Paint.FontMetrics fm = paint.getFontMetrics(); float height = fm.bottom - fm.top + fm.leading; float cos = (float) Math.cos(Math.toRadians(degree)); float sin = (float) Math.sin(Math.toRadians(degree)); float x = (cos * (radiusPx)) + mCenter.x; float y = (sin * (radiusPx)) + mCenter.y; canvas.save(); canvas.translate(x, y); canvas.rotate(90 + degree); canvas.drawText(text, -paint.measureText(text) / 2.0f, height, paint); canvas.restore(); } public void onSizeChanged(int w, int h, int oldw, int oldh) { DLog.d(TAG, "onSizeChanged() called with: w = [" + w + "], h = [" + h + "], oldw = [" + oldw + "], oldh = [" + oldh + "]"); mIsPaintCreated = false; } }