Přehled

V rámci sestavení .NET pro Android se zpracovávají prostředky Androidu a ID Androidu je zpřístupněno prostřednictvím generovaného _Microsoft.Android.Resource.Designer.dll sestavení. Například s ohledem na soubor Reources\layout\Main.axml s obsahem:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android">
  <Button android:id="@+id/myButton" />
  <fragment
      android:id="@+id/log_fragment"
      android:name="commonsamplelibrary.LogFragment"
  />
  <fragment
      android:id="@+id/secondary_log_fragment"
      android:name="CommonSampleLibrary.LogFragment"
  />
</LinearLayout>

Pak během kompilace vznikne _Microsoft.Android.Resource.Designer.dll sestavení s obsahem podobným:

namespace _Microsoft.Android.Resource.Designer;

partial class Resource {
  partial class Id {
    public static int myButton               {get;}
    public static int log_fragment           {get;}
    public static int secondary_log_fragment {get;}
  }
  partial class Layout {
    public static int Main                   {get;}
  }
}

Tradičně by se interakce s prostředky prováděla v kódu pomocí konstant z Resource typu a FindViewById<T>() metody:

partial class MainActivity : Activity {

  protected override void OnCreate (Bundle savedInstanceState)
  {
    base.OnCreate (savedInstanceState);
    SetContentView (Resource.Layout.Main);
    Button button = FindViewById<Button>(Resource.Id.myButton);
    button.Click += delegate {
        button.Text = $"{count++} clicks!";
    };
  }
}

Počínaje Xamarin.Android 8.4 existují dva další způsoby interakce s prostředky Androidu při použití jazyka C#:

  1. Spojení
  2. Code-Behind

Pokud chcete tyto nové funkce povolit, nastavte $(AndroidGenerateLayoutBindings) Vlastnost MSBuild na True příkazovém řádku msbuild:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

nebo v souboru .csproj:

<PropertyGroup>
    <AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>

Propojení

Vazba je vygenerovaná třída, jedna pro soubor rozvržení pro Android, která obsahuje striktně typové vlastnosti pro všechny ID v souboru rozvržení. Typy vazeb se generují do global::Bindings oboru názvů s názvy typů, které odpovídají jménu souboru rozložení.

Typy vazeb se vytvářejí pro všechny soubory rozložení, které obsahují ID systému Android.

Pokud máte soubor Android layoutu Resources\layout\Main.axml:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools">
  <Button android:id="@+id/myButton" />
  <fragment
      android:id="@+id/fragmentWithExplicitManagedType"
      android:name="commonsamplelibrary.LogFragment"
      xamarin:managedType="CommonSampleLibrary.LogFragment"
  />
  <fragment
      android:id="@+id/fragmentWithInferredType"
      android:name="CommonSampleLibrary.LogFragment"
  />
</LinearLayout>

pak se vygeneruje následující typ:

// Generated code
namespace Binding {
  sealed class Main : global::Xamarin.Android.Design.LayoutBinding {

    [global::Android.Runtime.PreserveAttribute (Conditional=true)]
    public Main (
      global::Android.App.Activity client,
      global::Xamarin.Android.Design.OnLayoutItemNotFoundHandler itemNotFoundHandler = null)
        : base (client, itemNotFoundHandler) {}

    [global::Android.Runtime.PreserveAttribute (Conditional=true)]
    public Main (
      global::Android.Views.View client,
      global::Xamarin.Android.Design.OnLayoutItemNotFoundHandler itemNotFoundHandler = null)
        : base (client, itemNotFoundHandler) {}

    Button __myButton;
    public Button myButton => FindView (global::Xamarin.Android.Tests.CodeBehindFew.Resource.Id.myButton, ref __myButton);

    CommonSampleLibrary.LogFragment __fragmentWithExplicitManagedType;
    public CommonSampleLibrary.LogFragment fragmentWithExplicitManagedType =>
      FindFragment (global::Xamarin.Android.Tests.CodeBehindFew.Resource.Id.fragmentWithExplicitManagedType, __fragmentWithExplicitManagedType, ref __fragmentWithExplicitManagedType);

    global::Android.App.Fragment __fragmentWithInferredType;
    public global::Android.App.Fragment fragmentWithInferredType =>
      FindFragment (global::Xamarin.Android.Tests.CodeBehindFew.Resource.Id.fragmentWithInferredType, __fragmentWithInferredType, ref __fragmentWithInferredType);
  }
}

Základní typ vazby Xamarin.Android.Design.LayoutBinding součástí knihovny tříd .NET pro Android, ale je dodáván s .NET pro Android ve zdrojové podobě a je součástí sestavení aplikace automaticky při každém použití vazeb.

Vygenerovaný typ vazby lze vytvořit kolem instancí Activity, což umožňuje silně typovaný přístup k ID v souboru rozložení.

// User-written code
partial class MainActivity : Activity {

  protected override void OnCreate (Bundle savedInstanceState)
  {
    base.OnCreate (savedInstanceState);

    SetContentView (Resource.Layout.Main);
    var binding     = new Binding.Main (this);
    Button button   = binding.myButton;
    button.Click   += delegate {
        button.Text = $"{count++} clicks!";
    };
  }
}

Typy vazeb mohou být také sestaveny kolem View instancí, což umožňuje silné typy přístupu k ID prostředků v zobrazení nebo podřízených objektech:

var binding = new Binding.Main (some_view);

Chybějící ID prostředků

Vlastnosti typů vazeb stále používají FindViewById<T>() ve své implementaci. Pokud FindViewById<T>() vrátí null, pak je výchozí chování vlastnosti vyvolat InvalidOperationException místo vrácení null.

Toto výchozí chování může být přepsáno předáním delegáta pro zpracování chyb vygenerované vazbě během jejího vytváření:

// User-written code
partial class MainActivity : Activity {

  Java.Lang.Object? OnLayoutItemNotFound (int resourceId, Type expectedViewType)
  {
     // Find and return the View or Fragment identified by `resourceId`
     // or `null` if unknown
     return null;
  }

  protected override void OnCreate (Bundle savedInstanceState)
  {
    base.OnCreate (savedInstanceState);

    SetContentView (Resource.Layout.Main);
    var binding     = new Binding.Main (this, OnLayoutItemNotFound);
  }
}

Metoda OnLayoutItemNotFound() se vyvolá, když nelze najít ID prostředku pro View nebo Fragment.

Obslužná rutina musí vrátit buď null, kdy InvalidOperationException bude vyvolán, nebo pokud možno vrátit instanci View nebo instanci Fragment, která odpovídá ID předané obslužné rutině. Vrácený objekt musí být správného typu, který odpovídá typu odpovídající Binding vlastnost. Vrácená hodnota se přetypuje na tento typ, takže pokud objekt není správně zadaný, vyvolá se výjimka.

Code-Behind

Code-Behind zahrnuje generování třídy v čase sestavení, která obsahuje silně typované vlastnosti pro všechna ID v souboru rozložení.

Code-Behind staví na mechanismu vazby, přičemž vyžaduje, aby se soubory rozložení rozhodly zapojit do generování Code-Behind pomocí nového atributu XML xamarin:classes, který obsahuje seznam úplných názvů tříd, oddělený znakem ;, které mají být vygenerovány.

Vzhledem k souboru Resources\layout\Main.axmlrozložení androidu:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools"
    xamarin:classes="Example.MainActivity">
  <Button android:id="@+id/myButton" />
  <fragment
      android:id="@+id/fragmentWithExplicitManagedType"
      android:name="commonsamplelibrary.LogFragment"
      xamarin:managedType="CommonSampleLibrary.LogFragment"
  />
  <fragment
      android:id="@+id/fragmentWithInferredType"
      android:name="CommonSampleLibrary.LogFragment"
  />
</LinearLayout>

při sestavování bude vytvořen následující typ:

// Generated code
namespace Example {
  partial class MainActivity {
    Binding.Main __layout_binding;

    public override void SetContentView (global::Android.Views.View view);
    void SetContentView (global::Android.Views.View view,
                         global::Xamarin.Android.Design.LayoutBinding.OnLayoutItemNotFoundHandler onLayoutItemNotFound);

    public override void SetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params);
    void SetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params,
                         global::Xamarin.Android.Design.LayoutBinding.OnLayoutItemNotFoundHandler onLayoutItemNotFound);

    public override void SetContentView (int layoutResID);
    void SetContentView (int layoutResID,
                         global::Xamarin.Android.Design.LayoutBinding.OnLayoutItemNotFoundHandler onLayoutItemNotFound);

    partial void OnSetContentView (global::Android.Views.View view, ref bool callBaseAfterReturn);
    partial void OnSetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params, ref bool callBaseAfterReturn);
    partial void OnSetContentView (int layoutResID, ref bool callBaseAfterReturn);

    public Button myButton => __layout_binding?.myButton;
    public CommonSampleLibrary.LogFragment fragmentWithExplicitManagedType => __layout_binding?.fragmentWithExplicitManagedType;
    public global::Android.App.Fragment fragmentWithInferredType => __layout_binding?.fragmentWithInferredType;
  }
}

To umožňuje intuitivnější použití ID prostředků v rámci rozložení:

// User-written code
partial class MainActivity : Activity {
  protected override void OnCreate (Bundle savedInstanceState)
  {
    base.OnCreate (savedInstanceState);

    SetContentView (Resource.Layout.Main);

    myButton.Click += delegate {
        button.Text = $"{count++} clicks!";
    };
  }
}

Obslužná rutina chyby OnLayoutItemNotFound může být předána jako poslední parametr kterémukoli přetížení SetContentView používanému aktivitou.

// User-written code
partial class MainActivity : Activity {
  protected override void OnCreate (Bundle savedInstanceState)
  {
    base.OnCreate (savedInstanceState);

    SetContentView (Resource.Layout.Main, OnLayoutItemNotFound);
  }

  Java.Lang.Object? OnLayoutItemNotFound (int resourceId, Type expectedViewType)
  {
    // Find and return the View or Fragment identified by `resourceId`
    // or `null` if unknown
    return null;
  }
}

Vzhledem k tomu, že Code-Behind spoléhá na částečné třídy, musí se ve své deklaraci použít partial class deklarace částečné třídy, jinak se v době sestavení vygeneruje chyba kompilátoru CS0260 C#.

Přizpůsobení

Vygenerovaný typ Code Behind vždy přepíše Activity.SetContentView(), a ve výchozím nastavení vždy volá base.SetContentView(), přičemž předává parametry dál. Pokud to není žádoucí, měla by být přepsána jedna z OnSetContentView()partial metod, která nastaví callBaseAfterReturn na false:

// Generated code
namespace Example
{
  partial class MainActivity {
    partial void OnSetContentView (global::Android.Views.View view, ref bool callBaseAfterReturn);
    partial void OnSetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params, ref bool callBaseAfterReturn);
    partial void OnSetContentView (int layoutResID, ref bool callBaseAfterReturn);
  }
}

Příklad vygenerovaného kódu

// Generated code
namespace Example
{
  partial class MainActivity {

    Binding.Main? __layout_binding;

    public override void SetContentView (global::Android.Views.View view)
    {
      __layout_binding = new global::Binding.Main (view);
      bool callBase = true;
      OnSetContentView (view, ref callBase);
      if (callBase) {
        base.SetContentView (view);
      }
    }

    void SetContentView (global::Android.Views.View view, global::Xamarin.Android.Design.LayoutBinding.OnLayoutItemNotFoundHandler onLayoutItemNotFound)
    {
      __layout_binding = new global::Binding.Main (view, onLayoutItemNotFound);
      bool callBase = true;
      OnSetContentView (view, ref callBase);
      if (callBase) {
        base.SetContentView (view);
      }
    }

    public override void SetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params)
    {
      __layout_binding = new global::Binding.Main (view);
      bool callBase = true;
      OnSetContentView (view, @params, ref callBase);
      if (callBase) {
        base.SetContentView (view, @params);
      }
    }

    void SetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params, global::Xamarin.Android.Design.LayoutBinding.OnLayoutItemNotFoundHandler onLayoutItemNotFound)
    {
      __layout_binding = new global::Binding.Main (view, onLayoutItemNotFound);
      bool callBase = true;
      OnSetContentView (view, @params, ref callBase);
      if (callBase) {
        base.SetContentView (view, @params);
      }
    }

    public override void SetContentView (int layoutResID)
    {
      __layout_binding = new global::Binding.Main (this);
      bool callBase = true;
      OnSetContentView (layoutResID, ref callBase);
      if (callBase) {
        base.SetContentView (layoutResID);
      }
    }

    void SetContentView (int layoutResID, global::Xamarin.Android.Design.LayoutBinding.OnLayoutItemNotFoundHandler onLayoutItemNotFound)
    {
      __layout_binding = new global::Binding.Main (this, onLayoutItemNotFound);
      bool callBase = true;
      OnSetContentView (layoutResID, ref callBase);
      if (callBase) {
        base.SetContentView (layoutResID);
      }
    }

    partial void OnSetContentView (global::Android.Views.View view, ref bool callBaseAfterReturn);
    partial void OnSetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params, ref bool callBaseAfterReturn);
    partial void OnSetContentView (int layoutResID, ref bool callBaseAfterReturn);

    public  Button                          myButton                         => __layout_binding?.myButton;
    public  CommonSampleLibrary.LogFragment fragmentWithExplicitManagedType  => __layout_binding?.fragmentWithExplicitManagedType;
    public  global::Android.App.Fragment    fragmentWithInferredType         => __layout_binding?.fragmentWithInferredType;
  }
}

Atributy rozložení XML

Mnoho nových atributů XML rozložení řídí vazby a fungování Code-Behind, které jsou ve jmenném prostoru XML (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools"). Tady jsou některé z nich:

xamarin:classes

Atribut xamarin:classes XML se používá jako součást Code-Behind k určení, které typy mají být generovány.

Atribut xamarin:classes XML obsahuje ;-oddělený seznam úplných názvů tříd , které by se měly generovat.

xamarin:managedType

Atribut xamarin:managedType rozložení slouží k explicitní zadání spravovaného typu pro zveřejnění vázaného ID jako. Pokud není zadaný, typ bude odvozen z deklarujícího kontextu, například <Button/> výsledkem bude hodnota Android.Widget.Button, a <fragment/> výsledkem bude .Android.App.Fragment

Atribut xamarin:managedType umožňuje explicitnější deklarace typů.

Mapování spravovaného typu

Je docela běžné používat názvy widgetů založené na balíčku Java, ze kterých pocházejí, a stejně často bude mít spravovaný název .NET takového typu v spravované zemi jiný název (styl .NET). Generátor kódu může provést řadu velmi jednoduchých úprav, které se pokusí spárovat s kódem, například:

  • Velkými písmeny všechny součásti oboru názvů a názvu typu. Například java.package.myButton by se stal Java.Package.MyButton

  • Velká písmena u dvoupísmenných součástí názvového prostoru typu Například android.os.SomeType by se stal Android.OS.SomeType

  • Vyhledejte několik hardcodeovaných jmenných prostorů, které mají známé mapování. Seznam v současné době obsahuje následující mapování:

    • android.view –>Android.Views
    • com.actionbarsherlock –>ABSherlock
    • com.actionbarsherlock.widget –>ABSherlock.Widget
    • com.actionbarsherlock.view –>ABSherlock.View
    • com.actionbarsherlock.app –>ABSherlock.App
  • Vyhledejte v interních tabulkách řadu pevně zakódovaných typů. Seznam v současné době obsahuje následující typy:

  • Prokládání počtu pevně zakódovaných předpon oboru názvů Seznam v současné době obsahuje následující předpony:

    • com.google.

Pokud však výše uvedené pokusy selžou, budete muset upravit rozložení, které používá widget s nepřiřazeným typem tak, aby se do kořenového prvku rozložení přidala deklarace oboru názvů XML `xamarin` a k prvku, který vyžaduje mapování, deklarace `xamarin:managedType`. Například:

<fragment
    android:id="@+id/log_fragment"
    android:name="commonsamplelibrary.LogFragment"
    xamarin:managedType="CommonSampleLibrary.LogFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

Bude používat CommonSampleLibrary.LogFragment typ pro nativní typ commonsamplelibrary.LogFragment.

Můžete se vyhnout přidání deklarace oboru názvů XML a xamarin:managedType atributu jednoduše pojmenováním typu pomocí jeho spravovaného názvu, například výše uvedený fragment může být znovu zadán takto:

<fragment
    android:name="CommonSampleLibrary.LogFragment"
    android:id="@+id/secondary_log_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

Fragmenty: zvláštní případ

Ekosystém Androidu v současné době podporuje dvě odlišné implementace widgetu Fragment :

Tyto třídy nejsou vzájemně kompatibilní a proto je nutné při generování kódu vazby pro <fragment> prvky v souborech rozložení věnovat zvláštní pozornost. .NET pro Android musí zvolit jednu Fragment implementaci jako výchozí, která se má použít, pokud <fragment> prvek nemá zadaný žádný konkrétní typ (spravovaný nebo jinak). Generátor vazeb kódu používá $(AndroidFragmentType) Vlastnost MSBuild pro tento účel. Vlastnost může uživatel přepsat tak, aby určil jiný typ než výchozí. Vlastnost je ve výchozím nastavení nastavena Android.App.Fragment a je přepsána balíčky NuGet AndroidX.

Pokud se vygenerovaný kód nesestaví, soubor rozložení musí být upraven zadáním spravovaného typu daného fragmentu.

Výběr rozložení a zpracování kódu na pozadí

Výběr

Ve výchozím nastavení je generování kódu na pozadí zakázané. Pokud chcete povolit zpracování pro všechna rozložení v libovolných Resource\layout* adresářích, které obsahují alespoň jeden prvek s atributem //*/@android:id, nastavte vlastnost $(AndroidGenerateLayoutBindings) MSBuild přes příkazový řádek msbuild:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

nebo v souboru .csproj:

<PropertyGroup>
  <AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>

Alternativně můžete nechat funkci code-behind globálně zakázanou a povolit ji pouze pro konkrétní soubory. Pokud chcete povolit Code-Behind pro konkrétní .axml soubor, změňte soubor tak, aby měl akci Sestavení @(AndroidBoundLayout)úpravou .csproj souboru a nahrazením AndroidResource za AndroidBoundLayout:

<!-- This -->
<AndroidResource Include="Resources\layout\Main.axml" />
<!-- should become this -->
<AndroidBoundLayout Include="Resources\layout\Main.axml" />

zpracování

Rozložení se seskupují podle názvu a mají podobné šablony z různýchResource\layout* adresářů, které tvoří jednu skupinu. Takové skupiny se zpracovávají, jako by šlo o jedno rozložení. Je možné, že v takovém případě dojde ke kolizí typu mezi dvěma widgety nalezené v různých rozloženích patřících do stejné skupiny. V takovém případě vygenerovaná vlastnost nebude moci mít přesný typ widgetu, ale spíše "rozpadlý" typ. Rozpad se řídí následujícím algoritmem:

  1. Pokud jsou View všechny konfliktní widgety deriváty, typ vlastnosti bude Android.Views.View

  2. Pokud jsou Fragment všechny konfliktní typy deriváty, typ vlastnosti bude Android.App.Fragment

  3. Pokud konfliktní widgety obsahují jak View, tak i Fragment, typ vlastnosti bude global::System.Object.

Vygenerovaný kód

Pokud vás zajímá, jak vypadá vygenerovaný kód pro vaše rozložení, podívejte se do složky obj\$(Configuration)\generated v adresáři řešení.