Obsługa obrotu

W tym temacie opisano sposób obsługi zmian orientacji urządzenia na platformie Xamarin.Android. Omówiono w nim sposób pracy z systemem zasobów systemu Android w celu automatycznego ładowania zasobów dla określonej orientacji urządzenia oraz sposobu programowego obsługi zmian orientacji.

Omówienie

Ponieważ urządzenia przenośne są łatwo obracane, wbudowana rotacja jest standardową funkcją w systemach operacyjnych mobilnych. System Android udostępnia zaawansowaną strukturę do obsługi rotacji w aplikacjach, niezależnie od tego, czy interfejs użytkownika jest tworzony deklaratywnie w formacie XML, czy programowo w kodzie. W przypadku automatycznej obsługi zmian układu deklaratywnego na obróconym urządzeniu aplikacja może korzystać z ścisłej integracji z systemem zasobów systemu Android. W przypadku układu programowego zmiany muszą być obsługiwane ręcznie. Pozwala to na dokładniejszą kontrolę w czasie wykonywania, ale kosztem większej ilości pracy dla dewelopera. Aplikacja może również zrezygnować z ponownego uruchomienia działania i przejąć ręczną kontrolę nad zmianami orientacji.

W tym przewodniku omówiono następujące tematy orientacji:

  • Rotacja układu deklaratywnego — jak używać systemu zasobów systemu Android do tworzenia aplikacji obsługujących orientację, w tym sposobu ładowania układów i rysowalnych dla określonych orientacji.

  • Rotacja układu programowego — jak programowo dodawać kontrolki oraz jak ręcznie obsługiwać zmiany orientacji.

Obsługa rotacji deklaratywnie z układami

Dołączając pliki w folderach, które są zgodne z konwencjami nazewnictwa, system Android automatycznie ładuje odpowiednie pliki po zmianie orientacji. Obejmuje to obsługę następujących rozwiązań:

  • Zasoby układu — określanie, które pliki układu są zawyżone dla każdej orientacji.

  • Zasoby z możliwością rysowania — określanie, które elementy rysowalne są ładowane dla każdej orientacji.

Zasoby układu

Domyślnie pliki XML systemu Android (AXML) zawarte w folderze Resources/layout są używane do renderowania widoków działania. Zasoby tego folderu są używane zarówno dla orientacji pionowej, jak i poziomej, jeśli nie są udostępniane dodatkowe zasoby układu specjalnie dla poziomego. Rozważ strukturę projektu utworzoną przez domyślny szablon projektu:

Default project template structure

Ten projekt tworzy pojedynczy plik Main.axml w folderze Resources/layout . Po wywołaniu metody Działania OnCreate zwiększa ona widok zdefiniowany w pliku Main.axml, który deklaruje przycisk, jak pokazano w poniższym pliku XML:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
<Button  
  android:id="@+id/myButton"
  android:layout_width="fill_parent" 
  android:layout_height="wrap_content" 
  android:text="@string/hello"/>
</LinearLayout>

Jeśli urządzenie jest obracane w orientacji poziomej, metoda Działania OnCreate jest wywoływana ponownie, a ten sam plik Main.axml jest zawyżony, jak pokazano na poniższym zrzucie ekranu:

Same screen but in landscape orientation

Układy specyficzne dla orientacji

Oprócz folderu układu (który jest domyślnie pionowy i może być również jawnie nazwany port-układ przez dołączenie folderu o nazwie layout-land), aplikacja może zdefiniować widoki potrzebne w środowisku poziomym bez żadnych zmian w kodzie.

Załóżmy, że plik Main.axml zawiera następujący kod XML:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <TextView
    android:text="This is portrait"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent" />
</RelativeLayout>

Jeśli do projektu zostanie dodany folder o nazwie layout-land zawierający dodatkowy plik Main.axml , zawyżanie układu, gdy w krajobrazie spowoduje teraz załadowanie nowo dodanego pliku Main.axml w systemie Android. Rozważ wersję poziomą pliku Main.axml , który zawiera następujący kod (dla uproszczenia ten kod XML jest podobny do domyślnej wersji pionowej kodu, ale używa innego ciągu w pliku TextView):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <TextView
    android:text="This is landscape"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent" />
</RelativeLayout>

Uruchomienie tego kodu i rotacja urządzenia z pionowego na poziomą pokazuje nowe ładowanie KODU XML, jak pokazano poniżej:

Portrait and landscape screenshots printing the portrait mode

Zasoby z możliwością rysowania

Podczas rotacji system Android traktuje zasoby do rysowania podobnie jak w przypadku zasobów układu. W takim przypadku system pobiera elementy rysowalne z folderów Resources/drawable i Resources/drawable-land .

Załóżmy na przykład, że projekt zawiera obraz o nazwie Monkey.png w folderze Resources/drawable , do którego można odwoływać się z ImageView pliku w formacie XML w następujący sposób:

<ImageView
  android:layout_height="wrap_content"
  android:layout_width="wrap_content"
  android:src="@drawable/monkey"
  android:layout_centerVertical="true"
  android:layout_centerHorizontal="true" />

Załóżmy również, że w obszarze Zasoby/lądy znajduje się inna wersja Monkey.png. Podobnie jak w przypadku plików układu, gdy urządzenie jest obracane, rysowalne zmiany dla danej orientacji, jak pokazano poniżej:

Different version of Monkey.png shown in portrait and landscape modes

Programowe obsługa rotacji

Czasami definiujemy układy w kodzie. Może się to zdarzyć z różnych powodów, w tym ograniczeń technicznych, preferencji dewelopera itp. Po programowym dodaniu kontrolek aplikacja musi ręcznie uwzględnić orientację urządzenia, która jest obsługiwana automatycznie podczas korzystania z zasobów XML.

Dodawanie kontrolek w kodzie

Aby programowo dodać kontrolki, aplikacja musi wykonać następujące czynności:

  • Utwórz układ.
  • Ustaw parametry układu.
  • Tworzenie kontrolek.
  • Ustaw parametry układu kontrolki.
  • Dodaj kontrolki do układu.
  • Ustaw układ jako widok zawartości.

Rozważmy na przykład interfejs użytkownika składający się z pojedynczej TextView kontrolki dodanej do RelativeLayoutelementu , jak pokazano w poniższym kodzie.

protected override void OnCreate (Bundle bundle)
{
  base.OnCreate (bundle);
                        
  // create a layout
  var rl = new RelativeLayout (this);

  // set layout parameters
  var layoutParams = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.FillParent);
  rl.LayoutParameters = layoutParams;
        
  // create TextView control
  var tv = new TextView (this);

  // set TextView's LayoutParameters
  tv.LayoutParameters = layoutParams;
  tv.Text = "Programmatic layout";

  // add TextView to the layout
  rl.AddView (tv);
        
  // set the layout as the content view
  SetContentView (rl);
}

Ten kod tworzy wystąpienie RelativeLayout klasy i ustawia jego LayoutParameters właściwość. Klasa LayoutParams to sposób hermetyzacji sposobu umieszczania kontrolek w systemie Android w sposób wielokrotnego użytku. Po utworzeniu wystąpienia układu można utworzyć i dodać do niego kontrolki. Kontrolki mają również element LayoutParameters, taki jak TextView w tym przykładzie. Po utworzeniu TextView elementu dodaj go do RelativeLayout elementu i ustawienie RelativeLayout jako widok zawartości powoduje wyświetlenie aplikacji TextView w sposób pokazany:

Increment counter button shown in both portrait and landscape modes

Wykrywanie orientacji w kodzie

Jeśli aplikacja próbuje załadować inny interfejs użytkownika dla każdej orientacji po OnCreate wywołaniu (nastąpi to za każdym razem, gdy urządzenie zostanie obrócone), musi wykryć orientację, a następnie załadować żądany kod interfejsu użytkownika. System Android ma klasę o nazwie WindowManager, która może służyć do określenia bieżącej rotacji urządzenia za pośrednictwem WindowManager.DefaultDisplay.Rotation właściwości, jak pokazano poniżej:

protected override void OnCreate (Bundle bundle)
{
  base.OnCreate (bundle);
                        
  // create a layout
  var rl = new RelativeLayout (this);

  // set layout parameters
  var layoutParams = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.FillParent);
  rl.LayoutParameters = layoutParams;
                        
  // get the initial orientation
  var surfaceOrientation = WindowManager.DefaultDisplay.Rotation;
  // create layout based upon orientation
  RelativeLayout.LayoutParams tvLayoutParams;
                
  if (surfaceOrientation == SurfaceOrientation.Rotation0 || surfaceOrientation == SurfaceOrientation.Rotation180) {
    tvLayoutParams = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
  } else {
    tvLayoutParams = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
    tvLayoutParams.LeftMargin = 100;
    tvLayoutParams.TopMargin = 100;
  }
                        
  // create TextView control
  var tv = new TextView (this);
  tv.LayoutParameters = tvLayoutParams;
  tv.Text = "Programmatic layout";
        
  // add TextView to the layout
  rl.AddView (tv);
        
  // set the layout as the content view
  SetContentView (rl);
}

Ten kod ustawia TextView położenie 100 pikseli z lewej górnej części ekranu, automatycznie animując do nowego układu, po obróceniu do poziomego, jak pokazano poniżej:

View state is preserved across portrait and landscape modes

Zapobieganie ponownemu uruchamianiu działania

Oprócz obsługi wszystkich elementów w OnCreateprogramie aplikacja może również uniemożliwić ponowne uruchomienie działania, gdy orientacja ulegnie zmianie, ustawiając w ConfigurationChangesActivityAttribute następujący sposób:

[Activity (Label = "CodeLayoutActivity", ConfigurationChanges=Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)]

Teraz, gdy urządzenie zostanie obrócone, działanie nie zostanie ponownie uruchomione. Aby ręcznie obsłużyć zmianę orientacji w tym przypadku, działanie może zastąpić OnConfigurationChanged metodę i określić orientację z Configuration przekazanego obiektu, jak w nowej implementacji działania poniżej:

[Activity (Label = "CodeLayoutActivity", ConfigurationChanges=Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)]
public class CodeLayoutActivity : Activity
{
  TextView _tv;
  RelativeLayout.LayoutParams _layoutParamsPortrait;
  RelativeLayout.LayoutParams _layoutParamsLandscape;
                
  protected override void OnCreate (Bundle bundle)
  {
    // create a layout
    // set layout parameters
    // get the initial orientation

    // create portrait and landscape layout for the TextView
    _layoutParamsPortrait = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
                
    _layoutParamsLandscape = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
    _layoutParamsLandscape.LeftMargin = 100;
    _layoutParamsLandscape.TopMargin = 100;
                        
    _tv = new TextView (this);
                        
    if (surfaceOrientation == SurfaceOrientation.Rotation0 || surfaceOrientation == SurfaceOrientation.Rotation180) {
      _tv.LayoutParameters = _layoutParamsPortrait;
    } else {
      _tv.LayoutParameters = _layoutParamsLandscape;
    }
                        
    _tv.Text = "Programmatic layout";
    rl.AddView (_tv);
    SetContentView (rl);
  }
                
  public override void OnConfigurationChanged (Android.Content.Res.Configuration newConfig)
  {
    base.OnConfigurationChanged (newConfig);
                        
    if (newConfig.Orientation == Android.Content.Res.Orientation.Portrait) {
      _tv.LayoutParameters = _layoutParamsPortrait;
      _tv.Text = "Changed to portrait";
    } else if (newConfig.Orientation == Android.Content.Res.Orientation.Landscape) {
      _tv.LayoutParameters = _layoutParamsLandscape;
      _tv.Text = "Changed to landscape";
    }
  }
}

TextView's W tym miejscu parametry układu są inicjowane zarówno dla poziomego, jak i pionowego. Zmienne klasy przechowują parametry wraz z samymi parametrami TextView , ponieważ działanie nie zostanie ponownie utworzone podczas zmiany orientacji. Kod nadal używa surfaceOrientartion elementu in OnCreate , aby ustawić początkowy układ dla elementu TextView. OnConfigurationChanged Następnie obsługuje wszystkie kolejne zmiany układu.

Po uruchomieniu aplikacji system Android ładuje zmiany interfejsu użytkownika w miarę obrotu urządzenia i nie uruchamia ponownie działania.

Zapobieganie ponownemu uruchamianiu działania dla układów deklaratywnych

Ponowne uruchomienia działań spowodowane rotacją urządzeń mogą być również blokowane, jeśli zdefiniujemy układ w formacie XML. Na przykład możemy użyć tego podejścia, jeśli chcemy zapobiec ponownemu uruchomieniu działania (być może ze względów wydajności) i nie musimy ładować nowych zasobów dla różnych orientacji.

W tym celu stosujemy tę samą procedurę, której używamy z układem programowym. Wystarczy ustawić ConfigurationChanges element w elemecie ActivityAttribute, tak jak wcześniej CodeLayoutActivity . Każdy kod, który musi zostać uruchomiony dla zmiany orientacji, można ponownie zaimplementować w metodzie OnConfigurationChanged .

Utrzymywanie stanu podczas zmian orientacji

Niezależnie od tego, czy obsługa rotacji jest deklaratywnie, czy programowo, wszystkie aplikacje systemu Android powinny implementować te same techniki zarządzania stanem po zmianie orientacji urządzenia. Zarządzanie stanem jest ważne, ponieważ system uruchamia uruchomione działanie po obróceniu urządzenia z systemem Android. System Android ułatwia ładowanie alternatywnych zasobów, takich jak układy i elementy rysowalne przeznaczone specjalnie dla określonej orientacji. Po ponownym uruchomieniu działanie traci stan przejściowy, który mógł być przechowywany w zmiennych klasy lokalnej. W związku z tym, jeśli działanie jest stanem zależnym, musi utrwało stan na poziomie aplikacji. Aplikacja musi obsługiwać zapisywanie i przywracanie dowolnego stanu aplikacji, który chce zachować we wszystkich zmianach orientacji.

Aby uzyskać więcej informacji na temat utrwalania stanu w systemie Android, zapoznaj się z przewodnikiem cyklu życia działania.

Podsumowanie

W tym artykule opisano sposób korzystania z wbudowanych funkcji systemu Android do pracy z rotacją. Najpierw wyjaśniono, jak używać systemu zasobów systemu Android do tworzenia aplikacji obsługujących orientację. Następnie przedstawiono sposób dodawania kontrolek w kodzie oraz ręcznego obsługi zmian orientacji.