W tym artykule pokażemy, jak skonfigurować przypadki użycia w aplikacji CameraX, aby uzyskać obrazy z prawidłowymi informacjami o obrócie niezależnie od tego, czy pochodzą z zastosowania ImageAnalysis
czy ImageCapture
. A więc:
- Element
Analyzer
przypadku użyciaImageAnalysis
powinien otrzymywać ramki z prawidłowym obrotem. - Funkcja
ImageCapture
powinna robić zdjęcia z prawidłowym obrotem.
Terminologia
W tym temacie wykorzystano podaną niżej terminologię, dlatego ważne jest, aby zrozumieć, co oznaczają poszczególne terminy:
- Orientacja wyświetlacza
- Określa ono, po której stronie urządzenia jest góra i może mieć jedną z 4 wartości: pionowa, pozioma, odwrotna pionowa lub odwrócona poziomo.
- Obrót wyświetlacza
- Jest to wartość zwracana przez
Display.getRotation()
, która reprezentuje stopnie, o jakie urządzenie jest obrócone w lewo względem naturalnej orientacji. - Rotacja docelowa
- Jest to liczba stopni, o którą należy obrócić urządzenie w prawo, aby uzyskać orientację.
Jak określić rotację docelową
W przykładach poniżej pokazujemy, jak określić docelowy obrót urządzenia na podstawie jego naturalnej orientacji.
Przykład 1. Naturalna orientacja pionowa
Przykład urządzenia: Pixel 3 XL | |
---|---|
Orientacja naturalna = Orientacja pionowa Rotacja reklam = 0 |
|
Orientacja naturalna = Orientacja pionowa Rotacja reklam = 90 |
Przykład 2: naturalna orientacja pozioma
Przykład urządzenia: Pixel C | |
---|---|
Orientacja naturalna = Orientacja pozioma Rotacja reklam = 0 |
|
Orientacja naturalna = Orientacja pozioma Rotacja reklam displayowych = 270 |
Obrót obrazu
Który koniec? Orientacja czujnika w Androidzie jest definiowana jako wartość stała, która reprezentuje stopnie (0, 90, 180, 270), o których czujnik jest obrócony od górnej krawędzi urządzenia, gdy znajduje się ono w naturalnym położeniu. We wszystkich przypadkach na diagramach rotacja obrazu określa, jak należy obracać dane w prawo, aby były wyświetlane pionowo.
Poniższe przykłady pokazują, jak powinien przebiegać obrót zdjęcia w zależności od orientacji czujnika aparatu. Zakładają też, że rotacja docelowa jest ustawiona na rotację wyświetlania.
Przykład 1. Czujnik został obrócony o 90 stopni
Przykład urządzenia: Pixel 3 XL | |
---|---|
Obrót wyświetlacza = 0 |
|
Obrót wyświetlacza = 90 |
Przykład 2. Czujnik został obrócony o 270 stopni
Przykład urządzenia: Nexus 5X | |
---|---|
Obrót wyświetlacza = 0 |
|
Obrót wyświetlacza = 90 |
Przykład 3. Czujnik został obrócony o 0 stopni
Przykład urządzenia: Pixel C (tablet) | |
---|---|
Obrót wyświetlacza = 0 |
|
Obrót wyświetlacza = 270 |
Obliczanie obrotu obrazu
Analiza obrazu
Urządzenie Analyzer
urządzenia ImageAnalysis
otrzymuje z aparatu obrazy w postaci ImageProxy
s. Każdy obraz zawiera informacje o obrotie dostępne
przez:
val rotation = imageProxy.imageInfo.rotationDegrees
Ta wartość określa stopnie, o jakie trzeba obracać obraz w prawo, by zapewnić zgodność z docelowym obrotem (ImageAnalysis
). W kontekście aplikacji na Androida docelowa rotacja w ImageAnalysis
zwykle odpowiada orientacji ekranu.
Robienie zdjęć
Wywołanie zwrotne jest dołączone do instancji ImageCapture
, aby zasygnalizować, że wynik przechwytywania jest gotowy. Przyczyną może być zrobienie zdjęcia lub błąd.
Podczas robienia zdjęcia możesz użyć jednego z tych wywołań zwrotnych:
OnImageCapturedCallback
: odbiera obraz z dostępem w pamięci w postaciImageProxy
.OnImageSavedCallback
: wywoływany po zapisaniu przechwyconego obrazu w lokalizacji podanej w poluImageCapture.OutputFileOptions
. Opcje mogą zawierać poleFile
,OutputStream
lub lokalizację w poluMediaStore
.
Obrót przechwyconego obrazu, niezależnie od jego formatu (ImageProxy
, File
, OutputStream
, MediaStore Uri
) reprezentuje stopień obrotu, o który trzeba obrócić zdjęcie w prawo, aby dopasować je do docelowego obrotu w trybie ImageCapture
. Trzeba to powtarzać w kontekście aplikacji na Androida i dopasować ją do orientacji ekranu.
Informacje o obrócie obrazu można pobrać w jeden z tych sposobów:
ImageProxy
val rotation = imageProxy.imageInfo.rotationDegrees
File
val exif = Exif.createFromFile(file) val rotation = exif.rotation
OutputStream
val byteArray = outputStream.toByteArray() val exif = Exif.createFromInputStream(ByteArrayInputStream(byteArray)) val rotation = exif.rotation
MediaStore uri
val inputStream = contentResolver.openInputStream(outputFileResults.savedUri) val exif = Exif.createFromInputStream(inputStream) val rotation = exif.rotation
Sprawdzanie rotacji obrazu
Przypadki użycia ImageAnalysis
i ImageCapture
otrzymują zdarzenia ImageProxy
z kamery po pomyślnym żądaniu zapisu. Element ImageProxy
zawija obraz i informacje o nim, w tym jego obrót. Ta informacja o obrócie reprezentuje stopnie, o jakie należy obrócić obraz, aby dopasować go do docelowej rotacji w danym przypadku użycia.
Wskazówki dotyczące rotacji celów przechwytywania obrazu/analizy obrazów
Ponieważ wiele urządzeń domyślnie nie obraca się do pozycji pionowej lub poziomej, niektóre aplikacje na Androida ich nie obsługują. Określa, czy aplikacja ją obsługuje czy nie zmienia sposób aktualizowania rotacji docelowej w przypadku użycia.
Poniżej znajdziesz 2 tabele określające, jak zachować synchronizację rotacji docelowej w przypadku poszczególnych przypadków użycia z rotacją reklam displayowych. Pierwszy z nich pokazuje, jak to zrobić przy obsłudze wszystkich 4 orientacji, a drugi obsługuje tylko orientacje, do których urządzenie jest domyślnie obracane.
Aby wybrać wytyczne, których chcesz przestrzegać w swojej aplikacji:
Sprawdź, czy kamera
Activity
w aplikacji ma zablokowaną orientację, niezablokowaną orientację lub czy zastępuje zmiany konfiguracji orientacji.Zdecyduj, czy kamera
Activity
w Twojej aplikacji ma obsługiwać wszystkie 4 orientacje urządzenia (pionowa, pionowa, pionowa, pozioma i odwrotna), czy też domyślnie powinna obsługiwać tylko orientacje obsługiwane przez urządzenie, na którym działa.
Obsługuj wszystkie 4 orientacje
W tej tabeli znajdziesz pewne wskazówki, których należy przestrzegać, gdy urządzenie nie może obrócić się do pozycji pionowej. To samo dotyczy urządzeń, które nie obracają ekranu w orientacji poziomej.
Scenariusz | Wskazówki | Tryb jednego okna | Tryb podzielonego ekranu w trybie wielu okien |
---|---|---|---|
Odblokowana orientacja |
Skonfiguruj przypadki użycia za każdym razem, gdy tworzony jest Activity , na przykład w wywołaniu zwrotnym onCreate() dla Activity .
|
||
Użyj usługi onOrientationChanged() usługi OrientationEventListener .
W wywołaniu zwrotnym zaktualizuj rotację docelową przypadków użycia. Dotyczy to sytuacji, w których system nie odtwarza Activity nawet po zmianie orientacji, np. gdy urządzenie jest obrócone o 180 stopni.
|
Obsługuje też tryb, gdy wyświetlacz jest w odwrotnej orientacji pionowej, a urządzenie domyślnie nie obraca się do tej orientacji. |
Uwzględnia też sytuacje, gdy Activity nie jest odtwarzany, gdy urządzenie obróci się (np. o 90 stopni). Dotyczy to małych urządzeń, gdy aplikacja zajmuje połowę ekranu, i większych, gdy zajmuje dwie trzecie ekranu.
|
|
Opcjonalnie: ustaw właściwość screenOrientation elementu Activity na fullSensor w pliku AndroidManifest .
|
Dzięki temu interfejs użytkownika jest ustawiony pionowo, gdy urządzenie jest ustawione w odwrotnej orientacji pionowej, a system może odtworzyć element Activity za każdym razem, gdy urządzenie zostanie obrócone o 90 stopni.
|
Nie ma wpływu na urządzenia, które domyślnie nie obracają ekranu do orientacji pionowej. Tryb wielu okien nie jest obsługiwany, gdy wyświetlacz jest w odwrotnej orientacji pionowej. | |
Zablokowana orientacja |
Przypadki użycia skonfiguruj tylko raz, podczas pierwszego tworzenia zasobu Activity , na przykład w wywołaniu zwrotnym onCreate() interfejsu Activity .
|
||
Użyj usługi onOrientationChanged() usługi OrientationEventListener .
W wywołaniu zwrotnym zaktualizuj rotację docelową przypadków użycia oprócz podglądu.
|
Uwzględnia też sytuacje, gdy Activity nie jest odtwarzany, gdy urządzenie obróci się (np. o 90 stopni). Dotyczy to małych urządzeń, gdy aplikacja zajmuje połowę ekranu, i większych, gdy zajmuje dwie trzecie ekranu.
|
||
Zastąpienie konfiguracji configChanges |
Przypadki użycia skonfiguruj tylko raz, podczas pierwszego tworzenia zasobu Activity , na przykład w wywołaniu zwrotnym onCreate() interfejsu Activity .
|
||
Użyj usługi onOrientationChanged() usługi OrientationEventListener .
W wywołaniu zwrotnym zaktualizuj rotację docelową przypadków użycia.
|
Uwzględnia też sytuacje, gdy Activity nie jest odtwarzany, gdy urządzenie obróci się (np. o 90 stopni). Dotyczy to małych urządzeń, gdy aplikacja zajmuje połowę ekranu, i większych, gdy zajmuje dwie trzecie ekranu.
|
||
Opcjonalnie: w pliku AndroidManifest ustaw właściwość screenOrientation aktywności na fullSensor. | Pozwala ustawić interfejs pionowo, gdy urządzenie jest ustawione w odwrotnej orientacji. | Nie ma wpływu na urządzenia, które domyślnie nie obracają ekranu do orientacji pionowej. Tryb wielu okien nie jest obsługiwany, gdy wyświetlacz jest w odwrotnej orientacji pionowej. |
Obsługuj tylko orientacje obsługiwane przez urządzenia.
Obsługuje tylko orientacje, które urządzenie domyślnie obsługuje (może nie obejmować odwrotnej orientacji poziomej/pionowej).
Scenariusz | Wskazówki | Tryb podzielonego ekranu w trybie wielu okien |
---|---|---|
Odblokowana orientacja |
Skonfiguruj przypadki użycia za każdym razem, gdy tworzony jest Activity , na przykład w wywołaniu zwrotnym onCreate() dla Activity .
|
|
Użyj usługi onDisplayChanged() usługi DisplayListener . W wywołaniu zwrotnym zaktualizuj docelową rotację przypadków użycia, np. przy obróceniu urządzenia o 180 stopni.
|
Uwzględnia też sytuacje, gdy Activity nie jest odtwarzany, gdy urządzenie obróci się (np. o 90 stopni). Dotyczy to małych urządzeń, gdy aplikacja zajmuje połowę ekranu, i większych, gdy zajmuje dwie trzecie ekranu.
|
|
Zablokowana orientacja |
Przypadki użycia skonfiguruj tylko raz, podczas pierwszego tworzenia zasobu Activity , na przykład w wywołaniu zwrotnym onCreate() interfejsu Activity .
|
|
Użyj usługi onOrientationChanged() usługi OrientationEventListener .
W wywołaniu zwrotnym zaktualizuj rotację docelową przypadków użycia.
|
Uwzględnia też sytuacje, gdy Activity nie jest odtwarzany, gdy urządzenie obróci się (np. o 90 stopni). Dotyczy to małych urządzeń, gdy aplikacja zajmuje połowę ekranu, i większych, gdy zajmuje dwie trzecie ekranu.
|
|
Zastąpienie konfiguracji configChanges |
Przypadki użycia skonfiguruj tylko raz, podczas pierwszego tworzenia zasobu Activity , na przykład w wywołaniu zwrotnym onCreate() interfejsu Activity .
|
|
Użyj usługi onDisplayChanged() usługi DisplayListener . W wywołaniu zwrotnym zaktualizuj docelową rotację przypadków użycia, np. przy obróceniu urządzenia o 180 stopni.
|
Uwzględnia też sytuacje, gdy Activity nie jest odtwarzany, gdy urządzenie obróci się (np. o 90 stopni). Dotyczy to małych urządzeń, gdy aplikacja zajmuje połowę ekranu, i większych, gdy zajmuje dwie trzecie ekranu.
|
Orientacja odblokowana
Urządzenie Activity
ma odblokowaną orientację, gdy orientacja wyświetlacza (np. pionowa lub pozioma) odpowiada fizycznej orientacji urządzenia. Wyjątkiem są odwrotne orientacja pozioma/pionowa, której niektóre urządzenia nie obsługują domyślnie. Aby wymusić obrócenie urządzenia do wszystkich 4 orientacji, ustaw właściwość screenOrientation
elementu Activity
na fullSensor
.
W trybie wielu okien urządzenie, które domyślnie nie obsługuje orientacji poziomej/pionowej, nie zostanie obrócone do orientacji poziomej/pionowej, nawet jeśli właściwość screenOrientation
ma wartość fullSensor
.
<!-- The Activity has an unlocked orientation, but might not rotate to reverse portrait/landscape in single-window mode if the device doesn't support it by default. --> <activity android:name=".UnlockedOrientationActivity" /> <!-- The Activity has an unlocked orientation, and will rotate to all four orientations in single-window mode. --> <activity android:name=".UnlockedOrientationActivity" android:screenOrientation="fullSensor" />
Zablokowanie orientacji
Wyświetlacz ma zablokowaną orientację, gdy pozostaje w tej samej orientacji (np. pionowa lub pozioma) niezależnie od fizycznej orientacji urządzenia. Można to zrobić, określając właściwość screenOrientation
elementu Activity
w deklaracji w pliku AndroidManifest.xml
.
Jeśli wyświetlacz ma zablokowaną orientację, system nie zniszczy ani nie utworzy elementu Activity
przy obracaniu urządzenia.
<!-- The Activity keeps a portrait orientation even as the device rotates. --> <activity android:name=".LockedOrientationActivity" android:screenOrientation="portrait" />
Zastąpienie zmian w konfiguracji orientacji
Gdy Activity
zastępuje zmianę konfiguracji orientacji, system nie zniszczy go ani nie odtworzy po zmianie fizycznej orientacji urządzenia.
System aktualizuje jednak interfejs, aby dopasować go do fizycznej orientacji urządzenia.
<!-- The Activity's UI might not rotate in reverse portrait/landscape if the device doesn't support it by default. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" /> <!-- The Activity's UI will rotate to all 4 orientations in single-window mode. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" android:screenOrientation="fullSensor" />
Konfiguracja przypadków użycia kamery
W powyższych scenariuszach przypadki użycia kamery można skonfigurować podczas tworzenia obiektu Activity
.
W przypadku obiektu Activity
z odblokowaną orientacją konfiguracja jest wykonywana przy każdym obróceniu urządzenia, ponieważ system niszczy i odtwarza Activity
po zmianie orientacji. Powoduje to, że za każdym razem rotacja docelowa jest domyślnie zgodna z orientacją ekranu.
W przypadku obiektu Activity
z zablokowaną orientacją lub takim, który zastępuje zmiany konfiguracji orientacji, konfiguracja jest wykonywana raz przy pierwszym utworzeniu obiektu Activity
.
class CameraActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val cameraProcessFuture = ProcessCameraProvider.getInstance(this) cameraProcessFuture.addListener(Runnable { val cameraProvider = cameraProcessFuture.get() // By default, the use cases set their target rotation to match the // display’s rotation. val preview = buildPreview() val imageAnalysis = buildImageAnalysis() val imageCapture = buildImageCapture() cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageAnalysis, imageCapture) }, mainExecutor) } }
Konfiguracja OrientationEventListener
Za pomocą OrientationEventListener
można stale aktualizować docelowy obrót przypadków użycia kamery wraz ze zmianą orientacji urządzenia.
class CameraActivity : AppCompatActivity() { private val orientationEventListener by lazy { object : OrientationEventListener(this) { override fun onOrientationChanged(orientation: Int) { if (orientation == ORIENTATION_UNKNOWN) { return } val rotation = when (orientation) { in 45 until 135 -> Surface.ROTATION_270 in 135 until 225 -> Surface.ROTATION_180 in 225 until 315 -> Surface.ROTATION_90 else -> Surface.ROTATION_0 } imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } } override fun onStart() { super.onStart() orientationEventListener.enable() } override fun onStop() { super.onStop() orientationEventListener.disable() } }
Konfiguracja DisplayListener
Użycie obiektu DisplayListener
pozwala zaktualizować docelowy obrót kamery w określonych sytuacjach, np. gdy system nie niszczy, a potem odtwarza Activity
po obróceniu urządzenia o 180 stopni.
class CameraActivity : AppCompatActivity() { private val displayListener = object : DisplayManager.DisplayListener { override fun onDisplayChanged(displayId: Int) { if (rootView.display.displayId == displayId) { val rotation = rootView.display.rotation imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } override fun onDisplayAdded(displayId: Int) { } override fun onDisplayRemoved(displayId: Int) { } } override fun onStart() { super.onStart() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.registerDisplayListener(displayListener, null) } override fun onStop() { super.onStop() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.unregisterDisplayListener(displayListener) } }