Sensory urządzenia mobilnego dają aplikacjom bezpośredni dostęp do fizycznego świata — pochylenia, obrotu, kierunku geograficznego czy ciśnienia atmosferycznego. .NET MAUI Essentials, wbudowany w platformę od pierwszej wersji, dostarcza jednolite, typowane API dla akcelerometru, żyroskopu, kompasu, barometru, magnetometru i sensora orientacji zarówno na iOS, jak i na Androidzie — bez pisania ani jednej linii kodu natywnego.
Każdy sensor w MAUI Essentials realizuje ten sam wzorzec: weryfikujesz dostępność przez IsSupported, uruchamiasz pomiary przez Start(SensorSpeed), odbierasz dane w zdarzeniu ReadingChanged, a pomiary zatrzymujesz przez Stop(). Interfejsy takie jak IAccelerometer czy ICompass pozwalają na rejestrację w kontenerze DI i zastępowanie implementacją testową bez zmiany kodu biznesowego.
Sensory w .NET MAUI Essentials
.NET MAUI Essentials to zestaw interfejsów API wbudowany bezpośrednio w platformę .NET MAUI — nie jest to osobna biblioteka, którą trzeba instalować przez NuGet. Obsługa czujników jest jego integralną częścią. Dostępne sensory to:
- Accelerometer — mierzy przyspieszenie liniowe wzdłuż trzech osi; wartości zwracane w jednostkach g.
- Gyroscope — mierzy prędkość kątową wokół trzech osi; wartości w radianach na sekundę.
- Compass — zwraca azymut magnetycznej północy w stopniach (0–360).
- Magnetometer — podaje surowe natężenie pola magnetycznego wzdłuż osi X, Y, Z w mikroteslach.
- Barometer — mierzy ciśnienie atmosferyczne w hektopaskalach (hPa).
- OrientationSensor — dostarcza orientację urządzenia jako kwaternion.
Częstotliwość próbkowania ustawiasz przez enum SensorSpeed: Default, UI, Game i Fastest. Wartość UI jest odpowiednia do animacji interfejsu, gdzie aktualizacje kilkadziesiąt razy na sekundę w zupełności wystarczają. Game nadaje się do interakcji wymagających niskich opóźnień, a Fastest dostarcza dane tak szybko, jak pozwala sprzęt — kosztem znacznie wyższego zużycia energii.
Akcelerometr i wykrywanie potrząśnięcia
Akcelerometr jest najczęściej używanym sensorem w aplikacjach mobilnych. Dane odczytujesz przez zdarzenie ReadingChanged, którego argument AccelerometerChangedEventArgs dostarcza obiekt AccelerometerData z właściwością Acceleration typu System.Numerics.Vector3:
if (Accelerometer.Default.IsSupported)
{
Accelerometer.Default.ReadingChanged += OnAccelerometerChanged;
Accelerometer.Default.Start(SensorSpeed.UI);
}
private void OnAccelerometerChanged(object sender, AccelerometerChangedEventArgs e)
{
float x = e.Reading.Acceleration.X;
float y = e.Reading.Acceleration.Y;
float z = e.Reading.Acceleration.Z;
}Pamiętaj o wyrejestrowaniu handlera i zatrzymaniu czujnika, gdy strona lub widok znikają:
Accelerometer.Default.ReadingChanged -= OnAccelerometerChanged;
Accelerometer.Default.Stop();Do wykrywania potrząśnięcia urządzenia nie musisz ręcznie analizować wektora przyspieszenia — Accelerometer wystawia dedykowane zdarzenie ShakeDetected:
Accelerometer.Default.ShakeDetected += OnShakeDetected;
Accelerometer.Default.Start(SensorSpeed.Game);
private void OnShakeDetected(object sender, EventArgs e)
{
// odpowiedź na potrząśnięcie urządzeniem
}Zdarzenie jest wzbudzane po przekroczeniu wewnętrznego progu przyspieszenia, co zwalnia z konieczności implementowania własnego algorytmu detekcji. Akcelerometr wystarczy uruchomić raz — zarówno ReadingChanged, jak i ShakeDetected działają na tej samej instancji sensora.
Żyroskop i sensor orientacji
Żyroskop mierzy prędkość kątową wokół osi X, Y i Z, zwracając Vector3 w radianach na sekundę. Sensor przydaje się do śledzenia obrotu urządzenia z dużą precyzją — na przykład w aplikacjach AR lub grach:
if (Gyroscope.Default.IsSupported)
{
Gyroscope.Default.ReadingChanged += OnGyroscopeChanged;
Gyroscope.Default.Start(SensorSpeed.Game);
}
private void OnGyroscopeChanged(object sender, GyroscopeChangedEventArgs e)
{
System.Numerics.Vector3 angularVelocity = e.Reading.AngularVelocity;
}Gdy potrzebujesz pełnej orientacji urządzenia bez samodzielnego całkowania danych z żyroskopu i akcelerometru, sięgnij po OrientationSensor. Zwraca on kwaternion reprezentujący aktualną orientację urządzenia w przestrzeni 3D:
if (OrientationSensor.Default.IsSupported)
{
OrientationSensor.Default.ReadingChanged += OnOrientationChanged;
OrientationSensor.Default.Start(SensorSpeed.UI);
}
private void OnOrientationChanged(object sender, OrientationSensorChangedEventArgs e)
{
System.Numerics.Quaternion orientation = e.Reading.Orientation;
}OrientationSensor jest wewnętrznie zrealizowany jako sensor fuzji łączący dane z akcelerometru, żyroskopu i magnetometru. Daje bardziej stabilne i dokładne wyniki niż każdy z tych sensorów z osobna i jest preferowanym wyborem, gdy celem jest reakcja na ogólną orientację ekranu, a nie na konkretny, izolowany ruch.
Kompas, magnetometr i barometr
Kompas
Klasa Compass zwraca azymut magnetycznej północy jako wartość double w stopniach od 0 do 360. Metoda Start przyjmuje dodatkowy parametr logiczny applyLowPassFilter, który wygładza gwałtowne wahania odczytów — przydatny w widokach nawigacyjnych, gdzie drobne drżenia kompasu byłyby rozpraszające:
if (Compass.Default.IsSupported)
{
Compass.Default.ReadingChanged += OnCompassChanged;
Compass.Default.Start(SensorSpeed.UI, applyLowPassFilter: true);
}
private void OnCompassChanged(object sender, CompassChangedEventArgs e)
{
double heading = e.Reading.HeadingMagneticNorth;
}Magnetometr
Magnetometr dostarcza surowe wartości pola magnetycznego jako Vector3 w mikroteslach. W odróżnieniu od Compass, dane nie są kalibrowane ani filtrowane. Sensor przydaje się do wykrywania obecności metalowych obiektów lub pola magnetycznego zewnętrznego — na przykład do weryfikacji zbliżenia urządzenia do głośnika lub magnesu:
if (Magnetometer.Default.IsSupported)
{
Magnetometer.Default.ReadingChanged += OnMagnetometerChanged;
Magnetometer.Default.Start(SensorSpeed.UI);
}
private void OnMagnetometerChanged(object sender, MagnetometerChangedEventArgs e)
{
System.Numerics.Vector3 field = e.Reading.MagneticField;
}Barometr
Barometr mierzy ciśnienie atmosferyczne. Właściwość Pressure obiektu BarometerData podaje wartość w hektopaskalach. Sensor jest dostępny tylko na urządzeniach wyposażonych w odpowiedni chip — zawsze sprawdzaj IsSupported przed uruchomieniem:
if (Barometer.Default.IsSupported)
{
Barometer.Default.ReadingChanged += OnBarometerChanged;
Barometer.Default.Start(SensorSpeed.Default);
}
private void OnBarometerChanged(object sender, BarometerChangedEventArgs e)
{
double pressureHPa = e.Reading.Pressure;
}Wzorzec MVVM i wstrzykiwanie zależności
Statyczny dostęp przez Sensor.Default jest wygodny w prostych scenariuszach, ale utrudnia testowanie jednostkowe i narusza zasadę inwersji zależności. .NET MAUI Essentials dostarcza interfejsy dla każdego sensora — rejestruj je w kontenerze DI w MauiProgram.cs:
builder.Services.AddSingleton<IAccelerometer>(Accelerometer.Default);
builder.Services.AddSingleton<IGyroscope>(Gyroscope.Default);
builder.Services.AddSingleton<ICompass>(Compass.Default);
builder.Services.AddSingleton<IBarometer>(Barometer.Default);W ViewModelu przyjmujesz interfejs przez konstruktor. Poniższy przykład korzysta z CommunityToolkit.Mvvm i atrybutu [ObservableProperty] do automatycznego generowania właściwości obserwowalnych:
public partial class MotionViewModel : ObservableObject
{
private readonly IAccelerometer _accelerometer;
[ObservableProperty]
private float accelerationX;
public MotionViewModel(IAccelerometer accelerometer)
{
_accelerometer = accelerometer;
}
public void StartMonitoring()
{
if (!_accelerometer.IsSupported || _accelerometer.IsMonitoring)
return;
_accelerometer.ReadingChanged += OnReadingChanged;
_accelerometer.Start(SensorSpeed.UI);
}
public void StopMonitoring()
{
if (!_accelerometer.IsMonitoring)
return;
_accelerometer.ReadingChanged -= OnReadingChanged;
_accelerometer.Stop();
}
private void OnReadingChanged(object sender, AccelerometerChangedEventArgs e)
{
AccelerationX = e.Reading.Acceleration.X;
}
}W testach jednostkowych wstrzykujesz własną implementację IAccelerometer lub atrapę wygenerowaną biblioteką do mockowania. Możesz programowo wzbudzić zdarzenie ReadingChanged z dowolnymi danymi i zweryfikować zachowanie ViewModelu bez prawdziwego sprzętu. Szczegóły architektury MVVM w .NET MAUI, w tym konfigurację kontenera DI i nawigację Shell, znajdziesz w przewodniku po architekturze MVVM w .NET MAUI.
Cykl życia i zarządzanie baterią
Sensory pobierają energię przez cały czas, gdy są aktywne. Nieprawidłowe zarządzanie ich cyklem życia to częsta przyczyna szybkiego rozładowania baterii w aplikacjach produkcyjnych. Sprawdzony wzorzec to uruchamianie czujnika w OnAppearing i zatrzymywanie w OnDisappearing:
public partial class SensorPage : ContentPage
{
private readonly MotionViewModel _viewModel;
public SensorPage(MotionViewModel viewModel)
{
InitializeComponent();
_viewModel = viewModel;
BindingContext = viewModel;
}
protected override void OnAppearing()
{
base.OnAppearing();
_viewModel.StartMonitoring();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_viewModel.StopMonitoring();
}
}Nadpisanie tych dwóch metod gwarantuje, że sensor pracuje tylko wtedy, gdy strona jest widoczna dla użytkownika. Gdy przejdzie on do innej strony lub przełączy aplikację w tło, pomiary są automatycznie wstrzymywane.
Kilka dodatkowych reguł, które warto stosować konsekwentnie:
- Sprawdzaj
IsMonitoringprzed wywołaniemStart(), żeby uniknąć podwójnej rejestracji zdarzeń i podwójnego uruchomienia sensora. - Gdy jedynym celem jest wykrywanie potrząśnięcia, użyj zdarzenia
ShakeDetectedz prędkościąGamezamiast samodzielnie analizować każdy odczyt zFastest. - Zdarzenia przy prędkości
GameiFastestmogą być wywoływane z wątku innego niż główny — jeśli aktualizujesz właściwości obserwowalne związane z UI, korzystaj zMainThread.BeginInvokeOnMainThread. - Nie uruchamiaj wielu sensorów z prędkością
Fastestjednocześnie — każdy sensor na tym poziomie zużywa znaczną część budżetu procesora sygnałowego (DSP).
Często zadawane pytania
Jak sprawdzić, czy dany czujnik jest dostępny na urządzeniu?
Każda klasa sensora wystawia właściwość IsSupported (statyczną lub instancyjną przez interfejs DI). Sprawdź ją przed wywołaniem Start() — próba uruchomienia nieobsługiwanego sensora skutkuje wyjątkiem FeatureNotSupportedException.
Jaki SensorSpeed wybrać dla mojej aplikacji?
UI jest dobrym wyborem do animacji i wskaźników wizualnych. Game nadaje się do interakcji wymagających niskich opóźnień. Użyj Default, gdy częstotliwość próbkowania nie jest krytyczna — na przykład przy barometrze monitorującym ciśnienie atmosferyczne przez dłuższy czas.
Jak testować ViewModele korzystające z czujników bez prawdziwego urządzenia?
Rejestruj interfejsy sensorów (np. IAccelerometer) w kontenerze DI zamiast korzystać bezpośrednio ze statycznych klas. W testach jednostkowych tworzysz lub generujesz atrapę implementującą ten interfejs i wstrzykujesz ją do ViewModelu, dzięki czemu możesz programowo wzbudzać zdarzenie ReadingChanged z dowolnymi danymi.
Czy mogę korzystać z wielu czujników jednocześnie?
Tak, .NET MAUI Essentials pozwala na jednoczesne uruchomienie wielu sensorów. Dobieraj prędkość próbkowania do rzeczywistych potrzeb każdego z nich — na przykład UI dla kompasu i Game wyłącznie dla żyroskopu, gdy oba są aktywne w tym samym czasie.
Dlaczego odczyty czujnika są nieruchome lub niedokładne na emulatorze?
Emulatory Androida i iOS mogą symulować dane sensorów, ale jakość tej symulacji zależy od wersji narzędzi. Android Emulator umożliwia ustawianie wirtualnych wartości przez panel sterowania emulatora. W przypadku barometru wiele emulatorów zgłasza sensor jako nieobsługiwany. Do rzetelnych testów sensorów niezbędne jest fizyczne urządzenie.



