Áttekintés

Az Androidhoz készült .NET-build részeként az Android-erőforrások feldolgozásra kerülnek, és egy generált szerelvényen keresztül felfedik az _Microsoft.Android.Resource.Designer.dll. Például a tartalommal rendelkező fájlra Reources\layout\Main.axml vonatkozóan:

<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>

A buildelési idő alatt egy _Microsoft.Android.Resource.Designer.dll szerelvény a következőhöz hasonló tartalommal rendelkezik:

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;}
  }
}

Az erőforrások használata hagyományosan kódban történik, a típus és a ResourceFindViewById<T>() metódus állandóinak használatával:

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!";
    };
  }
}

A Xamarin.Android 8.4-től kezdve két további módon is használhatja az Android-erőforrásokat a C# használatakor:

  1. Kötések
  2. Code-Behind

Az új funkciók engedélyezéséhez állítsa be a $(AndroidGenerateLayoutBindings) MSBuild tulajdonság az True msbuild parancssorban:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

vagy a .csproj fájlban:

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

Kötések

A binding egy generált osztály, amely minden Android elrendezésfájlhoz tartozik, és tartalmazza az elrendezésfájlban lévő összes azonosítóhoz tartozó erősen típusos tulajdonságokat. A rendszer kötéstípusokat hoz létre a global::Bindings névtérben az elrendezésfájl fájlnevét tükröző típusnevekkel.

A kötéstípusok minden olyan elrendezésfájlhoz létrejönnek, amelyek bármilyen Android-azonosítót tartalmaznak.

Az Android Layout fájlt Resources\layout\Main.axml alapul véve:

<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>

ezután a következő típus jön létre:

// 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);
  }
}

A kötés alaptípusa Xamarin.Android.Design.LayoutBinding része az Androidhoz készült .NET osztálykódtárnak, hanem forrás formában az Androidhoz készült .NET-hez van szállítva, és a kötések használatakor automatikusan bekerül az alkalmazás buildjébe.

A generált kötéstípus a Activity példányok köré hozható létre, így az elrendezésfájlban erősen típusos hozzáférést biztosítva az azonosítókhoz.

// 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!";
    };
  }
}

A binding típusok a View példányok köré is felépíthetők, lehetővé téve az erősen gépelt hozzáférést az erőforrás-azonosítókhoz a nézeten vagy annak gyermekein belül.

var binding = new Binding.Main (some_view);

Hiányzó erőforrásazonosítók

A kötéstípusok még mindig FindViewById<T>()-t használnak a megvalósítás során. Ha a FindViewById<T>() visszaadja a null értéket, akkor az alapértelmezett viselkedés az, hogy a tulajdonság null helyett InvalidOperationException kivételt vált ki.

Ezt az alapértelmezett viselkedést felülírhatja egy hibakezelő delegált átadása a létrehozott kötésnek a példányosításkor:

// 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);
  }
}

A OnLayoutItemNotFound() metódust akkor hívják meg, ha nem található View vagy Fragment erőforrás-azonosító.

A kezelőnek vissza kell adnia vagy a null-t, amely esetben a InvalidOperationException lesz dobva, vagy, lehetőség szerint, a View vagy Fragment példányt, amely megfelel a kezelőnek átadott azonosítónak. A visszaadott objektumnak a megfelelő Kötés tulajdonság típusának megfelelő típusúnak kell lennie. A visszaadott érték erre a típusra lesz vetve, így ha az objektum nem megfelelően van begépelve, kivételt küld a rendszer.

Code-Behind

Code-Behind magában foglalja egy partial osztály buildelési idejű létrehozását, amely az elrendezésfájl összes azonosítójának erősen típusos tulajdonságait tartalmazza.

Code-Behind a kötési mechanizmusra épül, miközben megköveteli, hogy az elrendezésfájlok engedélyezzék a Code-Behind generálást az új xamarin:classes XML-attribútum használatával, amely a létrehozandó teljes osztálynevek egy ;-vel elválasztott listája.

Az Android Layout fájl Resources\layout\Main.axml esetén:

<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>

a buildeléskor a következő típus lesz létrehozva:

// 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;
  }
}

Ez lehetővé teszi az erőforrás-azonosítók "intuitívabb" használatát az elrendezésen belül:

// 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!";
    };
  }
}

A OnLayoutItemNotFound hibakezelő átadható az SetContentView tevékenység bármelyik túlterhelésének utolsó paramétereként.

// 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;
  }
}

Mivel Code-Behind részleges osztályokra támaszkodik, a részleges osztály összes deklarációját használniapartial class kell a deklarációban, ellenkező esetben cs0260 C#-fordítóhiba jön létre a létrehozáskor.

Testreszabás

A létrehozott kód mögötti típus mindig felülbírálja Activity.SetContentView()a paraméterek továbbítását, és alapértelmezés szerint mindig meghívja base.SetContentView(). Ha ez nem kívánatos, akkor az OnSetContentView()partial egyik metódust felül kell bírálni, a következő beállítással callBaseAfterReturnfalse:

// 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élda generált kódra

// 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;
  }
}

Elrendezés XML-attribútumai

Számos új elrendezési XML-attribútum szabályozza a kötés és Code-Behind viselkedését, amelyek az xamarin XML-névtérben (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools") találhatók. Ezek közé tartoznak:

xamarin:classes

Az xamarin:classes XML-attribútum a Code-Behind részeként használható annak meghatározására, hogy mely típusokat kell létrehozni.

Az xamarin:classes XML-attribútum a ;létrehozandó teljes osztálynevek elválasztott listáját tartalmazza.

xamarin:managedType

Az xamarin:managedType elrendezési attribútum a felügyelt típus explicit megadásával teszi elérhetővé a kötött azonosítót. Ha nincs megadva, a típust a deklarálási környezetből inferáljuk, például: <Button/> egy Android.Widget.Button eredményez, és <fragment/> egy Android.App.Fragment eredményez.

Az xamarin:managedType attribútum explicitebb típusdeklarációkat tesz lehetővé.

Felügyelt típusleképezés

Elég gyakori, hogy widgetneveket használnak az általuk kapott Java-csomag alapján, és ugyanilyen gyakran az ilyen típusú felügyelt .NET-névnek más (.NET-stílusú) neve lesz a felügyelt országban. A kódgenerátor számos nagyon egyszerű módosítást hajthat végre, hogy megpróbáljon megegyezni a kóddal, például:

  • A típusnévtér és a név összes összetevőjének nagybetűsítése. Például java.package.myButton válhatna Java.Package.MyButton

  • A típusnévtér kétbetűs összetevőinek nagybetűsítése. Például android.os.SomeType válhatna Android.OS.SomeType

  • Keressen több olyan, nehezen kódolt névteret, amely ismert leképezésekkel rendelkezik. A lista jelenleg a következő leképezéseket tartalmazza:

    • android.view –>Android.Views
    • com.actionbarsherlock –>ABSherlock
    • com.actionbarsherlock.widget –>ABSherlock.Widget
    • com.actionbarsherlock.view –>ABSherlock.View
    • com.actionbarsherlock.app –>ABSherlock.App
  • A belső táblákban számos, nehezen kódolt típust kereshet. A lista jelenleg a következő típusokat tartalmazza:

  • A rögzített névtér előtagjainak sávszáma. A lista jelenleg a következő előtagokat tartalmazza:

    • com.google.

Ha azonban a fenti kísérletek sikertelenek, módosítania kell az elrendezést, amely egy ilyen leképezetlen típusú widgetet használ ahhoz, hogy az XML-névtér-deklarációt az elrendezés gyökéreleméhez és a xamarin leképezést igénylő elemhez is xamarin:managedType hozzáadja. Ilyenek például a következők:

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

A natív típus CommonSampleLibrary.LogFragmenttípusát fogja használnicommonsamplelibrary.LogFragment.

Elkerülheti az XML-névtér deklarációjának és az attribútumnak a xamarin:managedType hozzáadását úgy, hogy egyszerűen elnevezi a típust a felügyelt nevével, például a fenti töredék az alábbiak szerint újra kiírható:

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

Töredékek: különleges eset

Az Android-ökoszisztéma jelenleg a widget két különböző implementációját Fragment támogatja:

Ezek az osztályok nem kompatibilisek egymással, ezért különös figyelmet kell fordítani az elrendezésfájlok elemeinek <fragment> kötéskódjának létrehozásakor. Az Androidhoz készült .NET-nek az Fragment alapértelmezett implementációt kell választania, ha az <fragment> elem nem rendelkezik megadott (felügyelt vagy egyéb) típussal. A kötéskód-generátor a következőt használja: $(AndroidFragmentType) MSBuild tulajdonság erre a célra. A tulajdonság felülbírálható a felhasználó által az alapértelmezett típustól eltérő típus megadásához. A tulajdonság alapértelmezés szerint Android.App.Fragment értékre van állítva, és az AndroidX NuGet-csomagok felülírják.

Ha a létrehozott kód nem épül fel, az elrendezésfájlt módosítani kell a szóban forgó töredék rugalmas típusának megadásával.

Kód mögötti elrendezés kiválasztása és feldolgozása

Szelekció

Alapértelmezés szerint a kód mögötti generáció le van tiltva. Az összes elrendezés feldolgozásának engedélyezéséhez bármely Resource\layout* könyvtárban, amely legalább egyetlen elemet tartalmaz az //*/@android:id attribútummal, állítsa az $(AndroidGenerateLayoutBindings) MSBuild tulajdonságot True az msbuild parancssorában:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

vagy a .csproj fájlban:

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

Alternatív megoldásként letilthatja a háttérkódot globálisan, és csak bizonyos fájlok esetében engedélyezheti. Ha egy adott .axml fájlhoz szeretné engedélyezni a "Code-Behind" funkciót, módosítsa a fájlt úgy, hogy a build művelet je legyen @(AndroidBoundLayout) a .csproj fájl szerkesztésének módosításával, és cserélje le a AndroidResource a AndroidBoundLayout-re:

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

Processing

Az elrendezések név szerint vannak csoportosítva, és a különbözőResource\layout* könyvtárak hasonló nevű sablonjai egyetlen csoportot alkotnak. Ezek a csoportok úgy vannak feldolgozva, mintha egyetlen elrendezés lennének. Lehetséges, hogy ebben az esetben típusütközés lesz az ugyanazon csoporthoz tartozó különböző elrendezésekben található két widget között. Ebben az esetben a létrehozott tulajdonság nem tudja pontosan a widget típusát, hanem egy "elpusztult" típust. A romlás az alábbi algoritmust követi:

  1. Ha az ütköző vezérlők View mindegyike származékos, a tulajdonság típusa Android.Views.View

  2. Ha az ütköző típusok Fragment mindegyike származékos, a tulajdonságtípus Android.App.Fragment

  3. Ha az ütköző widgetek mind a View, mind a Fragment tartalmazzák, a tulajdonság típusa global::System.Object lesz.

Generált kód

Ha érdekli, hogyan néz ki a létrehozott kód az elrendezéseihez, tekintse meg a obj\$(Configuration)\generated mappát a megoldáskönyvtárában.