Skip to content

Instantly share code, notes, and snippets.

@THEb0nny
Created October 11, 2017 19:12
Show Gist options
  • Save THEb0nny/a4b34c4d6c06b2227730a1761c45fda7 to your computer and use it in GitHub Desktop.
Save THEb0nny/a4b34c4d6c06b2227730a1761c45fda7 to your computer and use it in GitHub Desktop.
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