Översikt

Som en del av .NET för Android-versionen bearbetas Android-resurser och exponerar Android-ID:er via en genererad _Microsoft.Android.Resource.Designer.dll sammansättning. Till exempel, med tanke på filen Reources\layout\Main.axml med innehåll:

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

Vid byggtillfället skapas sedan en _Microsoft.Android.Resource.Designer.dll assembly med innehåll som liknar:

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

Traditionellt skulle interaktion med resurser göras i kod, med hjälp av konstanterna från Resource typen och FindViewById<T>() metoden:

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

Från och med Xamarin.Android 8.4 finns det ytterligare två sätt att interagera med Android-resurser när du använder C#:

  1. Kopplingar
  2. Code-Behind

Om du vill aktivera dessa nya funktioner ställer du in $(AndroidGenerateLayoutBindings) MSBuild-egenskapen till True antingen på kommandoraden msbuild:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

eller i .csproj-filen:

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

Bindningar

En bindning är en genererad klass, en per Android-layoutfil, som innehåller starkt skrivna egenskaper för alla ID:er i layoutfilen. Bindningstyper genereras till global::Bindings namnområdet med typnamn som speglar layoutfilens filnamn.

Bindningstyper skapas för alla layoutfiler som innehåller alla Android-ID:er.

Med tanke på Android Layout-filen 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>

Då genereras följande 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);
  }
}

Bindningens bastyp Xamarin.Android.Design.LayoutBinding är inte en del av .NET för Android-klassbiblioteket, utan levereras i stället med .NET för Android i källformulär och ingår i programmets version automatiskt när bindningar används.

Den genererade bindningstypen kan skapas runt Activity instanser, vilket ger starkt typad åtkomst till ID:n i layoutfilen.

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

Bindningstyper kan också konstrueras kring View instanser, vilket ger starkt typad åtkomst till resurs-ID:er i vyn eller dess underordnade:

var binding = new Binding.Main (some_view);

Resurs-ID:t saknas

Egenskaper för bindningstyper används FindViewById<T>() fortfarande i implementeringen. Om FindViewById<T>() returnerar nullär standardbeteendet att egenskapen genererar en InvalidOperationException i stället för att nullreturnera .

Det här standardbeteendet kan åsidosättas genom att ett felhanteringsdelegat skickas till den genererade bindningen vid instansieringen:

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

Metoden OnLayoutItemNotFound() anropas när det inte gick att hitta ett resurs-ID för en View eller ett Fragment .

Hanteraren måste returnera antingen null, i vilket fall InvalidOperationException kommer att kastas, eller, helst, returnera den View- eller Fragment-instans som motsvarar det ID som skickas till hanteraren. Det returnerade objektet måste vara av rätt typ som matchar typen av motsvarande bindningsegenskap. Det returnerade värdet skickas till den typen, så om objektet inte är korrekt skrivet genereras ett undantag.

Code-Behind

Code-Behind omfattar byggtidsgenerering av en partial klass som innehåller starkt skrivna egenskaper för alla ID:er i layoutfilen.

Code-Behind bygger ovanpå bindningsmekanismen, samtidigt som det kräver att layoutfilerna väljer Code-Behind generation med hjälp av det nya xamarin:classes XML-attributet, som är en ;-avgränsad lista med fullständiga klassnamn som ska genereras.

Med tanke på Android Layout-filen Resources\layout\Main.axml:

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

vid byggtiden skapas följande 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;
  }
}

Detta möjliggör mer "intuitiv" användning av resurs-ID:t i layouten:

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

Felhanteraren OnLayoutItemNotFound kan skickas som den sista parametern i vilken överlagring av SetContentView aktivitet som används:

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

Eftersom Code-Behind förlitar sig på partiella klasser måste alla deklarationer av en partiell klass använda partial class i deklarationen, annars genereras ett CS0260 C#-kompilatorfel vid bygget.

Kundanpassning

Den genererade typen Bakom kod alltid åsidosätter , och som standard anropar den alltid och vidarebefordrar parametrarna. Om detta inte är önskat ska en av OnSetContentView()partial metoderna åsidosättas och ställas in 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);
  }
}

Exempel på genererad kod

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

Xml-attribut för layout

Många nya XML-layoutattribut styr bindning och Code-Behind beteende, som finns inom xamarin XML-namnområdet (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools"). Dessa inkluderar:

xamarin:classes

xamarin:classes XML-attributet används som en del av Code-Behind för att ange vilka typer som ska genereras.

xamarin:classes XML-attributet innehåller en ;-avgränsad lista med fullständiga klassnamn som ska genereras.

xamarin:managedType

Layoutattributet xamarin:managedType används för att uttryckligen ange den hanterade typen för att exponera det bundna ID:t som. Om den inte anges härleds typen från deklareringskontexten, t.ex. <Button/> resulterar i en Android.Widget.Button, och <fragment/> resulterar i en Android.App.Fragment.

Attributet xamarin:managedType tillåter mer explicita typdeklarationer.

Hanterad typmappning

Det är ganska vanligt att använda widgetnamn baserat på Java-paketet som de kommer från, och lika ofta har det hanterade .NET-namnet av den typen ett annat namn (.NET-format) i det hanterade landet. Kodgeneratorn kan utföra ett antal mycket enkla justeringar för att försöka matcha koden, till exempel:

  • Använd versaler för alla komponenter i typens namnområde och namn. Till exempel java.package.myButton skulle bli Java.Package.MyButton

  • Versalera komponenter med två bokstäver i typnamnområdet. Till exempel android.os.SomeType skulle bli Android.OS.SomeType

  • Leta upp ett antal hårdkodade namnområden som har kända mappningar. För närvarande innehåller listan följande mappningar:

    • android.view ->Android.Views
    • com.actionbarsherlock ->ABSherlock
    • com.actionbarsherlock.widget ->ABSherlock.Widget
    • com.actionbarsherlock.view ->ABSherlock.View
    • com.actionbarsherlock.app ->ABSherlock.App
  • Leta upp ett antal hårdkodade typer i interna tabeller. Listan innehåller för närvarande följande typer:

  • Ta bort antalet hårdkodade namnområdesprefix. För närvarande innehåller listan följande prefix:

    • com.google.

Om ovanstående försök misslyckas måste du dock ändra layouten som använder en widget med en sådan ommappad typ för att lägga till både xamarin XML-namnområdesdeklarationen till rotelementet i layouten och xamarin:managedType till elementet som kräver mappningen. Till exempel:

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

Använder typen CommonSampleLibrary.LogFragment för den inbyggda typen commonsamplelibrary.LogFragment.

Du kan undvika att lägga till XML-namnområdesdeklarationen och attributet xamarin:managedType genom att helt enkelt namnge typen med dess förvaltade namn, till exempel kan ovanstående fragment omdeklareras på följande sätt:

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

Fragment: ett specialfall

Android-ekosystemet stöder för närvarande två distinkta implementeringar av widgeten Fragment :

Dessa klasser är inte kompatibla med varandra och därför måste särskild försiktighet iakttas vid generering av bindningskod för <fragment> element i layoutfilerna. .NET för Android måste välja en Fragment implementering som standard som ska användas om elementet <fragment> inte har någon specifik typ (hanterad eller på annat sätt) angiven. Bindningskodgeneratorn använder $(AndroidFragmentType) MSBuild-egenskapen för det ändamålet. Egenskapen kan åsidosättas av användaren för att ange en annan typ än standardvärdet. Egenskapen är inställd på Android.App.Fragment som standard och åsidosätts av AndroidX NuGet-paketen.

Om den genererade koden inte kompilerar måste layoutfilen ändras genom att ange hanterade typen av fragmentet i fråga.

Layoutval och bearbetning i kodbakgrunden

Urval

Som standard är code-behind-generering inaktiverad. Om du vill aktivera bearbetning för alla layouter i någon av de Resource\layout* kataloger som innehåller minst ett enda element med //*/@android:id attributet anger du $(AndroidGenerateLayoutBindings) egenskapen MSBuild till True antingen på kommandoraden msbuild:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

eller i .csproj-filen:

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

Alternativt kan du lämna code-behind inaktiverat globalt och aktivera det endast för specifika filer. Om du vill aktivera Code-Behind för en viss .axml fil ändrar du filen så att den har en Build-åtgärd@(AndroidBoundLayout) genom att redigera .csproj filen och ersätta AndroidResource med AndroidBoundLayout:

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

Processing

Layouter grupperas efter namn, med mallar med liknande namn från olikaResource\layout* kataloger som består av en enda grupp. Sådana grupper bearbetas som om de vore en enda layout. Det är möjligt att det i sådana fall blir en typkonflikt mellan två widgetar som finns i olika layouter som tillhör samma grupp. I sådana fall kommer den genererade egenskapen inte att kunna ha den exakta widgettypen, utan snarare en "förfallen" egenskap. Förfall följer algoritmen nedan:

  1. Om alla motstridiga widgetar är View derivat blir egenskapstypen Android.Views.View

  2. Om alla typer av motstridiga typer är Fragment derivat kommer egenskapstypen att vara Android.App.Fragment

  3. Om de motstridiga widgetarna innehåller både en View och en Fragment, kommer egenskapstypen att vara global::System.Object

Genererad kod

Om du är intresserad av hur den genererade koden ser ut för dina layouter kan du ta en titt i obj\$(Configuration)\generated-mappen i din lösningskatalog.