Created ship vectors (not added yet)

Created menu
Created udp support
Created DockWidgets for compass and SOG/COG
This commit is contained in:
2025-09-03 15:40:02 +03:00
parent 2734560160
commit 25b1dabf73
70 changed files with 3145 additions and 293 deletions
@@ -53,6 +53,12 @@ public abstract class BaseDockWidget extends FrameLayout {
}
protected OnDockResizeListener dockResizeListener;
// Интерфейс для уведомления об изменении состояния docked
public interface OnDockStateChangeListener {
void onDockStateChanged(boolean isDocked, boolean isTop);
}
protected OnDockStateChangeListener dockStateChangeListener;
public BaseDockWidget(Context context) {
super(context);
init();
@@ -246,12 +252,23 @@ public abstract class BaseDockWidget extends FrameLayout {
dockHeightPx = newHeight;
setLayoutParams(lp);
// Если закреплен снизу, нужно также изменить позицию Y
if (!dockTop) {
// Корректируем позицию Y в зависимости от позиции закрепления
if (dockTop) {
// Если закреплен сверху, позиция Y всегда должна быть 0
setY(0);
} else {
// Если закреплен снизу, позиция Y должна быть (parentHeight - newHeight)
float newY = ((ViewGroup) getParent()).getHeight() - newHeight;
setY(newY);
}
// Перепозиционируем все docked виджеты после изменения размера
ViewGroup parent = (ViewGroup) getParent();
if (parent != null) {
repositionAllDockedWidgets(parent);
}
// Уведомляем об изменении размера
if (dockResizeListener != null) {
dockResizeListener.onDockResize(newHeight);
}
@@ -362,9 +379,13 @@ public abstract class BaseDockWidget extends FrameLayout {
float endX = targetX;
float endY = targetY;
// Если доким в нижнюю часть, корректируем позицию Y
if (docked && !top) {
endY = parentHeight - dockHeight;
// Если доким, вычисляем правильную позицию с учетом других docked виджетов
if (docked) {
endX = 0;
endY = calculateDockPosition(top);
} else {
// При переходе в movable режим сбрасываем размер до дефолтного
dockHeightPx = 0;
}
// Сохраняем финальные значения для использования в lambda и inner class
@@ -414,6 +435,11 @@ public abstract class BaseDockWidget extends FrameLayout {
morphAnimator.start();
this.isDocked = docked;
isMorphing = true;
// Уведомляем об изменении состояния docked
if (dockStateChangeListener != null) {
dockStateChangeListener.onDockStateChanged(docked, top);
}
}
public boolean isDocked() {
@@ -432,10 +458,101 @@ public abstract class BaseDockWidget extends FrameLayout {
this.dockResizeListener = listener;
}
public void setOnDockStateChangeListener(OnDockStateChangeListener listener) {
this.dockStateChangeListener = listener;
}
protected float dp(float dp) {
return dp * getResources().getDisplayMetrics().density;
}
/**
* Вычисляет правильную позицию для докинга с учетом других docked виджетов
*/
protected float calculateDockPosition(boolean dockTop) {
ViewGroup parent = (ViewGroup) getParent();
if (parent == null) return 0;
int dockHeight = (int) dp(DEFAULT_DOCK_HEIGHT_DP);
float y = 0;
if (dockTop) {
// Доким сверху - начинаем с позиции 0
y = 0;
// Проверяем другие виджеты сверху
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
if (child != this && child instanceof BaseDockWidget) {
BaseDockWidget otherWidget = (BaseDockWidget) child;
if (otherWidget.isDocked() && otherWidget.isDockTop()) {
// Если другой виджет уже docked сверху, ставим наш под ним
y = otherWidget.getY() + otherWidget.getHeight();
break;
}
}
}
} else {
// Доким снизу - начинаем с нижней позиции
y = parent.getHeight() - dockHeight;
// Проверяем другие виджеты снизу
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
if (child != this && child instanceof BaseDockWidget) {
BaseDockWidget otherWidget = (BaseDockWidget) child;
if (otherWidget.isDocked() && !otherWidget.isDockTop()) {
// Если другой виджет уже docked снизу, ставим наш над ним
y = otherWidget.getY() - dockHeight;
break;
}
}
}
}
return y;
}
/**
* Перепозиционирует все docked виджеты, чтобы они прижались к краям
*/
public static void repositionAllDockedWidgets(ViewGroup parent) {
if (parent == null) return;
// Собираем все docked виджеты сверху
java.util.List<BaseDockWidget> topWidgets = new java.util.ArrayList<>();
java.util.List<BaseDockWidget> bottomWidgets = new java.util.ArrayList<>();
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
if (child instanceof BaseDockWidget) {
BaseDockWidget widget = (BaseDockWidget) child;
if (widget.isDocked()) {
if (widget.isDockTop()) {
topWidgets.add(widget);
} else {
bottomWidgets.add(widget);
}
}
}
}
// Перепозиционируем виджеты сверху
float currentY = 0;
for (BaseDockWidget widget : topWidgets) {
widget.setY(currentY);
currentY += widget.getHeight();
}
// Перепозиционируем виджеты снизу
currentY = parent.getHeight();
for (int i = bottomWidgets.size() - 1; i >= 0; i--) {
BaseDockWidget widget = bottomWidgets.get(i);
currentY -= widget.getHeight();
widget.setY(currentY);
}
}
// Абстрактные методы для переопределения в наследниках
protected abstract void onDrawDock(Canvas canvas);
protected abstract void onDrawCircle(Canvas canvas);
@@ -80,7 +80,7 @@ public class CompassView extends BaseDockWidget {
// Прямая шкала (dock-режим)
@Override
protected void onDrawDock(Canvas canvas) {
Log.d(TAG, "onDrawDock called, width=" + getWidth() + ", height=" + getHeight());
// Log.d(TAG, "onDrawDock called, width=" + getWidth() + ", height=" + getHeight());
float w = getWidth();
float h = getHeight();
@@ -192,13 +192,13 @@ public class CompassView extends BaseDockWidget {
// Круглый компас (draggable-режим)
@Override
protected void onDrawCircle(Canvas canvas) {
Log.d(TAG, "onDrawCircle called, width=" + getWidth() + ", height=" + getHeight());
//Log.d(TAG, "onDrawCircle called, width=" + getWidth() + ", height=" + getHeight());
float w = getWidth();
float h = getHeight();
if (w <= 0 || h <= 0) {
Log.w(TAG, "Invalid dimensions: width=" + w + ", height=" + h);
// Log.w(TAG, "Invalid dimensions: width=" + w + ", height=" + h);
return;
}
@@ -0,0 +1,275 @@
package com.grigowashere.aismap.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import com.grigowashere.aismap.models.Vessel;
public class CoordinatesDockWidget extends BaseDockWidget {
private static final String TAG = "CoordinatesDockWidget";
// Цвета
private static final int BACKGROUND_COLOR = 0xE6000000; // Полупрозрачный черный
private static final int TEXT_COLOR = 0xFFFFFFFF; // Белый
private static final int ACCENT_COLOR = 0xFF4CAF50; // Зеленый
private static final int WARNING_COLOR = 0xFFFF9800; // Оранжевый
private static final int ERROR_COLOR = 0xFFF44336; // Красный
// Кисти
private Paint backgroundPaint;
private Paint textPaint;
private Paint accentPaint;
private Paint warningPaint;
private Paint errorPaint;
// Данные для отображения
private Vessel vessel;
private String coordinatesText = "Координаты: --";
private String sogText = "SOG: --";
private String cogText = "COG: --";
public CoordinatesDockWidget(Context context) {
super(context);
init();
}
public CoordinatesDockWidget(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// Инициализируем кисти
backgroundPaint = new Paint();
backgroundPaint.setColor(BACKGROUND_COLOR);
backgroundPaint.setStyle(Paint.Style.FILL);
backgroundPaint.setAntiAlias(true);
textPaint = new Paint();
textPaint.setColor(TEXT_COLOR);
textPaint.setTextSize(dp(14));
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
textPaint.setAntiAlias(true);
accentPaint = new Paint();
accentPaint.setColor(ACCENT_COLOR);
accentPaint.setTextSize(dp(14));
accentPaint.setTypeface(Typeface.DEFAULT_BOLD);
accentPaint.setAntiAlias(true);
warningPaint = new Paint();
warningPaint.setColor(WARNING_COLOR);
warningPaint.setTextSize(dp(14));
warningPaint.setTypeface(Typeface.DEFAULT_BOLD);
warningPaint.setAntiAlias(true);
errorPaint = new Paint();
errorPaint.setColor(ERROR_COLOR);
errorPaint.setTextSize(dp(14));
errorPaint.setTypeface(Typeface.DEFAULT_BOLD);
errorPaint.setAntiAlias(true);
// Устанавливаем фон для видимости (как в CompassView)
setBackgroundColor(android.graphics.Color.argb(200, 0, 0, 0));
}
/**
* Обновляет данные судна
*/
public void updateVessel(Vessel vessel) {
Log.d(TAG, "updateVessel called with vessel: " + (vessel != null ? "not null" : "null"));
if (vessel != null) {
Log.d(TAG, "Vessel data: lat=" + vessel.getLatitude() +
", lon=" + vessel.getLongitude() +
", speed=" + vessel.getSpeed() +
", course=" + vessel.getCourse());
}
this.vessel = vessel;
updateDisplayText();
invalidate();
}
/**
* Обновляет текст для отображения
*/
private void updateDisplayText() {
if (vessel == null) {
coordinatesText = "Координаты: --";
sogText = "SOG: --";
cogText = "COG: --";
return;
}
// Координаты
if (vessel.getLatitude() != 0 && vessel.getLongitude() != 0) {
coordinatesText = String.format("📍 %.6f, %.6f",
vessel.getLatitude(), vessel.getLongitude());
} else {
coordinatesText = "📍 Координаты: --";
}
// SOG (Speed Over Ground)
if (vessel.getSpeed() > 0) {
sogText = String.format("⚡ SOG: %.1f уз", vessel.getSpeed());
} else {
sogText = "⚡ SOG: --";
}
// COG (Course Over Ground)
if (vessel.getCourse() > 0) {
cogText = String.format("🧭 COG: %.1f°", vessel.getCourse());
} else {
cogText = "🧭 COG: --";
}
}
@Override
protected void onDrawDock(Canvas canvas) {
int width = getWidth();
int height = getHeight();
Log.d(TAG, "onDrawDock called, width=" + width + ", height=" + height);
if (width <= 0 || height <= 0) {
Log.w(TAG, "Invalid dimensions: width=" + width + ", height=" + height);
return;
}
// Рисуем фон
canvas.drawRect(0, 0, width, height, backgroundPaint);
// Вычисляем позиции для текста
float textSize = dp(14);
float lineHeight = textSize * 1.2f;
float startY = (height - (lineHeight * 3)) / 2 + textSize;
// Определяем цвета в зависимости от качества данных
Paint coordinatesPaint = getCoordinatesPaint();
Paint sogPaint = getSOGPaint();
Paint cogPaint = getCOGPaint();
// Рисуем тестовый заголовок для проверки видимости
Paint testPaint = new Paint();
testPaint.setColor(android.graphics.Color.WHITE);
testPaint.setTextSize(dp(16));
testPaint.setTypeface(android.graphics.Typeface.DEFAULT_BOLD);
testPaint.setAntiAlias(true);
canvas.drawText("КООРДИНАТЫ", dp(16), dp(20), testPaint);
// Рисуем текст
canvas.drawText(coordinatesText, dp(16), startY, coordinatesPaint);
canvas.drawText(sogText, dp(16), startY + lineHeight, sogPaint);
canvas.drawText(cogText, dp(16), startY + lineHeight * 2, cogPaint);
// Рисуем разделительную линию сверху, если закреплен снизу
if (!isDockTop()) {
Paint linePaint = new Paint();
linePaint.setColor(ACCENT_COLOR);
linePaint.setStrokeWidth(dp(2));
canvas.drawLine(0, 0, width, 0, linePaint);
}
}
@Override
protected void onDrawCircle(Canvas canvas) {
int width = getWidth();
int height = getHeight();
int centerX = width / 2;
int centerY = height / 2;
int radius = Math.min(width, height) / 2 - (int)dp(8);
// Рисуем фон
canvas.drawCircle(centerX, centerY, radius, backgroundPaint);
// Рисуем рамку
Paint borderPaint = new Paint();
borderPaint.setColor(ACCENT_COLOR);
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(dp(3));
borderPaint.setAntiAlias(true);
canvas.drawCircle(centerX, centerY, radius, borderPaint);
// Вычисляем позиции для текста в круге
float textSize = dp(12);
float lineHeight = textSize * 1.3f;
float startY = centerY - lineHeight;
// Определяем цвета
Paint coordinatesPaint = getCoordinatesPaint();
Paint sogPaint = getSOGPaint();
Paint cogPaint = getCOGPaint();
// Центрируем текст
Rect textBounds = new Rect();
// Координаты
coordinatesPaint.getTextBounds(coordinatesText, 0, coordinatesText.length(), textBounds);
canvas.drawText(coordinatesText, centerX - textBounds.width() / 2f, startY, coordinatesPaint);
// SOG
sogPaint.getTextBounds(sogText, 0, sogText.length(), textBounds);
canvas.drawText(sogText, centerX - textBounds.width() / 2f, startY + lineHeight, sogPaint);
// COG
cogPaint.getTextBounds(cogText, 0, cogText.length(), textBounds);
canvas.drawText(cogText, centerX - textBounds.width() / 2f, startY + lineHeight * 2, cogPaint);
}
/**
* Определяет цвет для отображения координат
*/
private Paint getCoordinatesPaint() {
if (vessel == null || vessel.getLatitude() == 0 || vessel.getLongitude() == 0) {
return errorPaint;
}
// Проверяем точность GPS
if (vessel.getAccuracy() > 0) {
if (vessel.getAccuracy() <= 5) {
return accentPaint; // Высокая точность - зеленый
} else if (vessel.getAccuracy() <= 20) {
return warningPaint; // Средняя точность - оранжевый
} else {
return errorPaint; // Низкая точность - красный
}
}
return textPaint; // По умолчанию - белый
}
/**
* Определяет цвет для отображения SOG
*/
private Paint getSOGPaint() {
if (vessel == null || vessel.getSpeed() <= 0) {
return errorPaint;
}
return accentPaint; // Если есть данные о скорости - зеленый
}
/**
* Определяет цвет для отображения COG
*/
private Paint getCOGPaint() {
if (vessel == null || vessel.getCourse() <= 0) {
return errorPaint;
}
return accentPaint; // Если есть данные о курсе - зеленый
}
/**
* Получает высоту виджета в dock-режиме
*/
public int getDockHeight() {
if (isDocked()) {
return getHeight();
}
return (int) dp(DEFAULT_DOCK_HEIGHT_DP);
}
}