Created
October 11, 2017 19:12
-
-
Save THEb0nny/a4b34c4d6c06b2227730a1761c45fda7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ru.bonny.navring; | |
import android.animation.ArgbEvaluator; | |
import android.content.Context; | |
import android.graphics.Bitmap; | |
import android.graphics.BitmapFactory; | |
import android.graphics.Canvas; | |
import android.graphics.Color; | |
import android.graphics.Paint; | |
import android.graphics.Path; | |
import android.graphics.PixelFormat; | |
import android.graphics.PorterDuff; | |
import android.graphics.PorterDuffXfermode; | |
import android.graphics.Shader; | |
import android.os.Handler; | |
import android.os.Message; | |
import android.util.Log; | |
import android.view.SurfaceHolder; | |
import android.view.SurfaceView; | |
import android.view.View; | |
public class SurfaceNavRingHUD extends SurfaceView implements SurfaceHolder.Callback { | |
static int mainX, mainY; | |
final int alpha2 = 55, alpha3 = 20; | |
static final float pStrokeWidthBasicCircle = 0.4f * SharedPref.mmInPx; // Толщина главной окружности в ММ | |
static float basicCircleR, circleTagR, pointR, innerCirclesR, numPointsRings, constInnerCircleR; | |
final float multiplerFocusCircleTagR = 1.1f; | |
static float cX_one, cY_one, cX_two, cY_two, cX_three, cY_three, cX_four, cY_four, cX_five, cY_five; | |
static short mX, mY; | |
final int[] pointsColors = new int[] {0xCCffffff, 0xB2ffffff, 0x99ffffff, 0x66ffffff, 0x7Fffffff, 0x40ffffff, 0x00ffffff}; // Белая градиентная маска, где первые две цифры после 0x - степень прозрачности | |
final int[] pointsShadowColors = new int[] {0xCC000000, 0xB2000000, 0x99000000, 0x66000000, 0x7F000000, 0x40000000, 0x00000000}; | |
static Handler handlerChangeStateHUD; | |
Paint mDrawPaint, pBasicCircle, pTagCircleNoFocus, pTagCircleFocus, pShadowTagCircle, pTagIcon, maskShaderCirclePaint, maskShaderCircleShadowPaint; | |
Path mCirclePath, shadowPath; | |
Bitmap mBitmap, bTagIcon; | |
Canvas mBitmapCanvas, mIconBitmapCanvas, mPointsBitmapCanvas; | |
private DrawThread drawThread; | |
public SurfaceNavRingHUD(Context context) { | |
super(context); | |
this.setZOrderOnTop(true); | |
this.getHolder().setFormat(PixelFormat.TRANSLUCENT); | |
getHolder().addCallback(this); | |
} | |
@Override | |
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { | |
} | |
@Override | |
public void surfaceCreated(SurfaceHolder holder) { | |
// Крайние левые синие значения это мои диаметеры | |
circleTagR = ((SharedPref.valLabelSeekbarR + SharedPref.minValLabelRadiusSeekbar) * SharedPref.mmInPx) / 2; // Определяем радиус для окружностей ярлычков изначально | |
pointR = (0.8f * SharedPref.mmInPx) / 2; // Радиус точки | |
constInnerCircleR = (5 * SharedPref.mmInPx) / 2; // Радиус внутреннего круга, который в виде точек | |
getBasicCircleR(false); // Узнать радиус базовой окружности на старте | |
handlerChangeStateHUD = new Handler() { | |
@Override | |
public void handleMessage(Message msg) { | |
switch ((int) msg.obj) { | |
case 0: | |
// Портретная ориентация | |
mainX = SharedPref.wScreen / 2; // Середина экрана по x относительно NavRingView | |
mainY = SharedPref.sizeNavRingView - SharedPref.getHeightNavBar(); | |
cX_one = mainX; | |
cY_one = mainY - basicCircleR; | |
cX_two = mainX - basicCircleR * (float) Math.cos(Math.toRadians(alpha2)); | |
cY_two = mainY - basicCircleR * (float) Math.sin(Math.toRadians(alpha2)); | |
cX_three = mainX + basicCircleR * (float) Math.cos(Math.toRadians(alpha2)); | |
cY_three = mainY - basicCircleR * (float) Math.sin(Math.toRadians(alpha2)); | |
cX_four = mainX - basicCircleR * (float) Math.cos(Math.toRadians(alpha3)); | |
cY_four = mainY - basicCircleR * (float) Math.sin(Math.toRadians(alpha3)); | |
cX_five = mainX + basicCircleR * (float) Math.cos(Math.toRadians(alpha3)); | |
cY_five = mainY - basicCircleR * (float) Math.sin(Math.toRadians(alpha3)); | |
break; | |
case 90: | |
// Горизонтальная ориентация 90 | |
mainX = SharedPref.sizeNavRingView - SharedPref.getHeightNavBar(); // Середина экрана по x относительно NavRingView | |
mainY = SharedPref.sizeNavRingView / 2; | |
cX_one = mainX - basicCircleR; | |
cY_one = mainY; | |
cX_two = mainX - basicCircleR * (float) Math.cos(Math.toRadians(90 - alpha2)); | |
cY_two = mainY + basicCircleR * (float) Math.sin(Math.toRadians(90 - alpha2)); | |
cX_three = mainX - basicCircleR * (float) Math.cos(Math.toRadians(90 - alpha2)); | |
cY_three = mainY - basicCircleR * (float) Math.sin(Math.toRadians(90 - alpha2)); | |
cX_four = mainX - basicCircleR * (float) Math.cos(Math.toRadians(90 - alpha3)); | |
cY_four = mainY + basicCircleR * (float) Math.sin(Math.toRadians(90 - alpha3)); | |
cX_five = mainX - basicCircleR * (float) Math.cos(Math.toRadians(90 - alpha3)); | |
cY_five = mainY - basicCircleR * (float) Math.sin(Math.toRadians(90 - alpha3)); | |
break; | |
case 270: | |
// Горизонтальная ориентация 270 | |
mainX = SharedPref.getHeightNavBar(); // Середина экрана по x относительно NavRingView | |
mainY = SharedPref.sizeNavRingView / 2; | |
cX_one = mainX + basicCircleR; | |
cY_one = mainY; | |
cX_two = mainX + basicCircleR * (float) Math.cos(Math.toRadians(90 - alpha2)); | |
cY_two = mainY - basicCircleR * (float) Math.sin(Math.toRadians(90 - alpha2)); | |
cX_three = mainX + basicCircleR * (float) Math.cos(Math.toRadians(90 - alpha2)); | |
cY_three = mainY + basicCircleR * (float) Math.sin(Math.toRadians(90 - alpha2)); | |
cX_four = mainX + basicCircleR * (float) Math.cos(Math.toRadians(90 - alpha3)); | |
cY_four = mainY - basicCircleR * (float) Math.sin(Math.toRadians(90 - alpha3)); | |
cX_five = mainX + basicCircleR * (float) Math.cos(Math.toRadians(90 - alpha3)); | |
cY_five = mainY + basicCircleR * (float) Math.sin(Math.toRadians(90 - alpha3)); | |
break; | |
default: | |
break; | |
} | |
} | |
}; | |
SharedPref.hudChangeMainCoordinates(SharedPref.orient); // После запуска знать координаты | |
// Paint для главной окружности | |
pBasicCircle = new Paint(Paint.ANTI_ALIAS_FLAG); | |
pBasicCircle.setColor(Color.WHITE); | |
pBasicCircle.setStrokeWidth(pStrokeWidthBasicCircle); | |
pBasicCircle.setStyle(Paint.Style.STROKE); // стиль обводки - Paint.Style.FILL - полностью | |
pBasicCircle.setShadowLayer(20, 0, 0, Color.argb(48, 0, 0, 0)); // Параметры тени, цвет и прозрачность | |
pBasicCircle.setAlpha(156); // Прозрачность | |
// Ярлыки не в фокусе | |
pTagCircleNoFocus = new Paint(Paint.ANTI_ALIAS_FLAG); | |
pTagCircleNoFocus.setColor(Color.WHITE); | |
pTagCircleNoFocus.setStrokeWidth(6); | |
pTagCircleNoFocus.setStyle(Paint.Style.FILL); // FILL - полностью закрашивать | |
pTagCircleNoFocus.setAlpha(190); // где, 255 - full | |
pTagCircleNoFocus.setAntiAlias(true); | |
// Ярлыки в фокусе | |
pTagCircleFocus = new Paint(Paint.ANTI_ALIAS_FLAG); | |
pTagCircleFocus.setColor(Color.WHITE); | |
pTagCircleFocus.setStrokeWidth(6); | |
pTagCircleFocus.setStyle(Paint.Style.FILL); // FILL - полностью закрашивать | |
pTagCircleFocus.setAlpha(255); // где, 255 - full | |
pTagCircleFocus.setAntiAlias(true); | |
// Тень для ярлыка | |
pShadowTagCircle = new Paint(Paint.ANTI_ALIAS_FLAG); | |
mDrawPaint = new Paint(); // Главный Paint | |
maskShaderCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); // Инициализируем кисть маски точек | |
maskShaderCirclePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); // SRC | |
maskShaderCirclePaint.setAntiAlias(true); | |
maskShaderCircleShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // Инициализируем кисть маски тени точек | |
maskShaderCircleShadowPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)); // SRC_OUT или SRC | |
maskShaderCircleShadowPaint.setAntiAlias(true); | |
drawThread = new DrawThread(getHolder()); | |
drawThread.setRunning(true); | |
drawThread.start(); | |
} | |
private boolean mRunning; //запущен ли процесс | |
@Override | |
public void surfaceDestroyed(SurfaceHolder holder) { | |
boolean retry = true; | |
drawThread.setRunning(false); | |
mRunning = false; | |
while (retry) { | |
try { | |
drawThread.join(); | |
retry = false; | |
} catch (InterruptedException e) { | |
//e.printStackTrace(); | |
} | |
} | |
} | |
static void getBasicCircleR(boolean reloadHUDMainCoordinates) { | |
basicCircleR = SharedPref.valSeekbarR * SharedPref.hStepsInSeekbar + SharedPref.minValSeekbar; // Посчитать радиус главной окружности | |
if (reloadHUDMainCoordinates && handlerChangeStateHUD != null) SharedPref.hudChangeMainCoordinates(SharedPref.orient); | |
} | |
protected void Draw(Canvas canvas) { | |
touchInView(); // Обрабатываем касания относительно нажего View | |
mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); | |
mBitmapCanvas = new Canvas(mBitmap); // Общий холст | |
mIconBitmapCanvas = new Canvas(mBitmap); // Холст для иконок | |
//mBitmapCanvas.drawARGB(10, 102, 204, 255); // Фон для отладки | |
mBitmapCanvas.drawCircle(mainX, mainY, basicCircleR, pBasicCircle); // Главная окружность | |
// Внутренний эффект маленьких точек | |
if (SharedPref.stateCheckBoxPointEffect) { | |
mPointsBitmapCanvas = new Canvas(mBitmap); // Холст для эффекта точек | |
mCirclePath = new Path(); // Path для обрезки | |
// Обрезаем Canvas в виде ГЛАВНОЙ окружности на всякий случай, чтобы из-за какого-нибудь бага точки дальше не отображались | |
mCirclePath.addCircle(mainX, mainY, basicCircleR, Path.Direction.CCW); | |
mPointsBitmapCanvas.clipPath(mCirclePath); | |
maskShaderCirclePaint.setShader(createMaskShader(pointsColors)); | |
maskShaderCircleShadowPaint.setShader(createMaskShader(pointsShadowColors)); | |
maskShaderCircleShadowPaint.setShadowLayer(5, 0, 5, Color.argb(100, 0, 0, 0)); | |
innerCirclesR = constInnerCircleR; // Переписать значение, т.к. постоянно перерисовывается View | |
numPointsRings = (basicCircleR / innerCirclesR) - 1.35f; | |
for (int i = 0, numPoints = 8; i < numPointsRings; i++, innerCirclesR += constInnerCircleR, numPoints += 6) { | |
float deg = 90; | |
for (int point = 0; point < numPoints; point++) { | |
float x = (float) (mainX - innerCirclesR * Math.cos(Math.toRadians(deg))); | |
float y = (float) (mainY - innerCirclesR * Math.sin(Math.toRadians(deg))); | |
mPointsBitmapCanvas.drawCircle(x, y, pointR, maskShaderCircleShadowPaint); // Рисуем точки в маске // maskShaderCirclePaint // pSmallPoints | |
mPointsBitmapCanvas.drawCircle(x, y, pointR, maskShaderCirclePaint); // Рисуем точки в маске // maskShaderCirclePaint // pSmallPoints | |
deg += (float) 360 / numPoints; | |
} | |
} | |
} | |
if (SharedPref.iCheckedLabel5 != 0) { | |
tagElement(5, cX_five, cY_five); // Рисуем 5 (правый) ярлык | |
tagIcon(SharedPref.iCheckedLabel5, cX_five, cY_five); // Рисуем иконку ярлыка | |
} | |
if (SharedPref.iCheckedLabel4 != 0) { | |
tagElement(4, cX_four, cY_four); // Рисуем 4 (левый) ярлык | |
tagIcon(SharedPref.iCheckedLabel4, cX_four, cY_four); // Рисуем иконку ярлыка | |
} | |
if (SharedPref.iCheckedLabel3 != 0) { | |
tagElement(3, cX_three, cY_three); // Рисуем 3 (правый) ярлык | |
tagIcon(SharedPref.iCheckedLabel3, cX_three, cY_three); // Рисуем иконку ярлыка | |
} | |
if (SharedPref.iCheckedLabel2 != 0) { | |
tagElement(2, cX_two, cY_two); // Рисуем 2 (левый) ярлык | |
tagIcon(SharedPref.iCheckedLabel2, cX_two, cY_two); // Рисуем иконку ярлыка | |
} | |
if (SharedPref.iCheckedLabel1 != 0) { | |
tagElement(1, cX_one, cY_one); // Рисуем 1 ярлык (верхний) | |
tagIcon(SharedPref.iCheckedLabel1, cX_one, cY_one); // Рисуем иконку ярлыка | |
} | |
if (canvas != null) canvas.drawBitmap(mBitmap, 0, 0, mDrawPaint); // Рисуем холст полностью | |
mBitmap.recycle(); // ? Уничтожаем битмап, чтобы освободить память? | |
} | |
private void touchInView() { // Касания относительно нашего View | |
switch (SharedPref.sideNavBarIs) { | |
case "bottom": | |
// Включая то, что устройство планшет | |
if (SharedPref.orient == 0 || SharedPref.orient == 180) mX = (short) TouchEventThread.touchX; | |
else if (SharedPref.orient == 90) mX = (short) (TouchEventThread.touchX - ((SharedPref.xDisplay / 2) - (SharedPref.sizeNavRingView / 2))); | |
else if (SharedPref.orient == 270) mX = (short) (SharedPref.hScreen - TouchEventThread.touchX - ((SharedPref.xDisplay / 2) - (SharedPref.sizeNavRingView / 2))); // Сначала от touchX вычисляем начальную точку | |
mY = (short) (TouchEventThread.touchY - (SharedPref.yDisplay - mainY)); // Вычисляем точку относительно нашего View //(SharedPref.yDisplay - SharedPref.hNavRingView) | |
break; | |
case "right": | |
mX = (short) (TouchEventThread.touchX - (SharedPref.xDisplay + SharedPref.getHeightNavBar() - SharedPref.sizeNavRingView)); | |
mY = (short) TouchEventThread.touchY; | |
break; | |
case "left": | |
mX = (short) ((SharedPref.xDisplay + SharedPref.getHeightNavBar()) - TouchEventThread.touchX); | |
mY = (short) TouchEventThread.touchY; | |
break; | |
} | |
// Вычисляем максимальную точку x, y для ГЛАВНОЙ ОКРУЖНОСТИ | |
// Это этого нужно найти угол alpha от катетов a, b прямоугольного треугольника | |
double c = Math.sqrt(Math.pow(mX - mainX, 2) + Math.pow(mY - mainY, 2)); | |
int b = Math.abs(mX - mainX); | |
double a = Math.sqrt(Math.pow(c, 2) - Math.pow(b, 2)); | |
double alpha = Math.atan(a / b); | |
short maxX = 0; | |
// Вычисляем координаты точек, которые будут на границе ГЛАВНОГО КРУГА | |
if (mX > mainX) maxX = (short) (mainX + basicCircleR * (float) Math.cos(alpha)); | |
else if (mX < mainX) maxX = (short) (mainX - basicCircleR * (float) Math.cos(alpha)); | |
short maxY = (short) (mainY - basicCircleR * (float) Math.sin(alpha)); | |
if ((mX > mainX && mX > maxX) || (mX < mainX && mX < maxX)) mX = maxX; // Вышли за ГЛАВНЫЙ КРУГ и x = коордитате на окружности | |
if (mY < maxY) mY = maxY; // Вышли за ГЛАВНЫЙ КРУГ и y = коордитате на окружности | |
if (mY > SharedPref.sizeNavRingView) mY = (short) mainY; // Если точка за кругом ниже. Необязательно! | |
} | |
private Shader createMaskShader(int colors[]) { | |
// 70, 70, 60, 50 .. % прозрачности | |
final float[] anchors = new float[] {0, 0.25f, 0.4f, 0.5f, 0.65f, 0.8f, 1}; // Якори, где находятся цвета | |
return new android.graphics.RadialGradient(mX, mY, (short) (circleTagR * 1.7), colors, anchors, Shader.TileMode.CLAMP); | |
} | |
private void tagElement(int i, float cX, float cY) { | |
if (TouchEventThread.touchInCircle == i) { // Ярлык под фокусом | |
pShadowTagCircle.setShadowLayer(25, 0, 0, Color.argb(82, 0, 0, 0)); // или | |
mBitmapCanvas.drawCircle(cX, cY, circleTagR * multiplerFocusCircleTagR, pShadowTagCircle); // Тень для ярлыка | |
mBitmapCanvas.drawCircle(cX, cY, circleTagR * multiplerFocusCircleTagR, pTagCircleFocus); // Ярлык | |
} else { | |
pShadowTagCircle.setShadowLayer(25, 0, 15, Color.argb(82, 0, 0, 0)); // или | |
mBitmapCanvas.drawCircle(cX, cY, circleTagR, pTagCircleNoFocus); // Ярлык | |
shadowPath = new Path(); // Path для обрезки тени | |
shadowPath.addRect(0, 0, SharedPref.sizeNavRingView, SharedPref.sizeNavRingView, Path.Direction.CW); | |
shadowPath.addCircle(cX, cY, circleTagR, Path.Direction.CCW); | |
mBitmapCanvas.clipPath(shadowPath); | |
mBitmapCanvas.drawCircle(cX, cY, circleTagR, pShadowTagCircle); // Тень для ярлыка | |
} | |
} | |
private void tagIcon(int i, float cX, float cY) { | |
pTagIcon = new Paint(); | |
bTagIcon = BitmapFactory.decodeResource(getResources(), getActionIcon(i)); | |
pTagIcon.setColor(Color.BLACK); | |
mIconBitmapCanvas.drawBitmap(bTagIcon, cX - (bTagIcon.getWidth() / 2), cY - (bTagIcon.getHeight() / 2), pTagIcon); | |
} | |
private int getActionIcon(int i) { | |
int img = 0; | |
switch (i) { | |
case 1: | |
img = R.mipmap.ic_sceen_off; | |
break; | |
case 2: | |
img = R.mipmap.ic_sceen_off_and_block; | |
break; | |
case 3: | |
img = R.mipmap.ic_sceen_off_and_block; | |
break; | |
default: | |
break; | |
} | |
return img; | |
} | |
private class DrawThread extends Thread { | |
private boolean running = false; | |
private final SurfaceHolder surfaceHolder; | |
private final int ANIMATION_TIME = 1_000; //анимация - 1,5 сек | |
private long mStartTime; //время начала анимации | |
private Paint mPaint; | |
private ArgbEvaluator mArgbEvaluator; | |
DrawThread(SurfaceHolder surfaceHolder) { | |
this.surfaceHolder = surfaceHolder; | |
mRunning = false; | |
mPaint = new Paint(); | |
mPaint.setAntiAlias(true); | |
mPaint.setStyle(Paint.Style.FILL); | |
mArgbEvaluator = new ArgbEvaluator(); | |
} | |
void setRunning(boolean running) { | |
this.running = running; | |
mRunning = running; | |
} | |
public long getTime() { | |
return System.nanoTime() / 1_000_000; | |
} | |
@Override | |
public void run() { | |
Canvas canvas; | |
mStartTime = getTime(); | |
int i = 0; | |
while (mRunning) { | |
i++; | |
Log.d("while", String.valueOf(i)); | |
canvas = null; | |
try { | |
canvas = surfaceHolder.lockCanvas(); //получаем canvas | |
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); | |
draw(canvas); //функция рисования | |
} | |
catch (NullPointerException e) {/*если canvas не доступен*/} | |
finally { | |
if (canvas != null) surfaceHolder.unlockCanvasAndPost(canvas); //освобождаем canvas | |
} | |
} | |
Log.d("WHILE", "Close"); | |
} | |
private void draw(Canvas canvas) { | |
long curTime = getTime() - mStartTime; | |
int width = canvas.getWidth(); | |
int height = canvas.getHeight(); | |
canvas.drawColor(Color.BLACK); | |
int centerX = width / 2; | |
int centerY = height / 2; | |
float maxSize = Math.min(width, height) / 2; | |
float fraction = (float) (curTime % ANIMATION_TIME) / ANIMATION_TIME; | |
int color = (int) mArgbEvaluator.evaluate(fraction, Color.RED, Color.BLACK); | |
mPaint.setColor(color); | |
canvas.drawCircle(centerX, centerY, maxSize * fraction, mPaint); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment