Bagikan melalui


Gambaran Umum

Sebagai bagian dari build .NET untuk Android, sumber daya Android diproses, mengekspos ID Android melalui rakitan yang dihasilkan_Microsoft.Android.Resource.Designer.dll. Misalnya, mengingat file Reources\layout\Main.axml dengan konten:

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

Kemudian selama build-time assembly _Microsoft.Android.Resource.Designer.dll dengan konten yang mirip dengan:

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

Secara tradisional, berinteraksi dengan Sumber Daya akan dilakukan dalam kode, menggunakan konstanta dari Resource jenis dan FindViewById<T>() metode :

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

Dimulai dengan Xamarin.Android 8.4, ada dua cara tambahan untuk berinteraksi dengan sumber daya Android saat menggunakan C#:

  1. Pengikatan-Pengikatan
  2. Kode-Belakang

Untuk mengaktifkan fitur baru ini, atur $(AndroidGenerateLayoutBindings) Properti MSBuild ke True baik pada baris perintah msbuild:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

atau dalam file .csproj Anda:

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

Pengikatan

Pengikatan adalah kelas yang dihasilkan, satu per file tata letak Android, yang berisi properti yang sangat diketik untuk semua id dalam file tata letak. Jenis pengikatan dihasilkan ke dalam global::Bindings namespace layanan, dengan nama jenis yang mencerminkan nama file file tata letak.

Jenis pengikatan dibuat untuk semua file tata letak yang berisi ID Android apa pun.

Mengingat file Resources\layout\Main.axmlTata Letak Android :

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

maka jenis berikut akan dihasilkan:

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

Jenis dasar pengikatan, Xamarin.Android.Design.LayoutBinding bukan bagian dari pustaka kelas .NET untuk Android, melainkan dikirim dengan .NET untuk Android dalam bentuk sumber dan disertakan dalam build aplikasi secara otomatis setiap kali pengikatan digunakan.

Jenis pengikatan yang dihasilkan dapat dibuat di sekitar Activity instans, memungkinkan akses yang sangat ditik ke ID dalam file tata letak:

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

Jenis pengikatan juga dapat dibangun di sekitar View instans, memungkinkan akses yang sangat ditik ke ID Sumber Daya dalam Tampilan atau turunannya:

var binding = new Binding.Main (some_view);

ID Sumber Daya Hilang

Properti pada jenis pengikatan masih digunakan FindViewById<T>() dalam implementasinya. Jika FindViewById<T>() mengembalikan null, maka perilaku default adalah properti untuk melemparkan InvalidOperationException alih-alih mengembalikan null.

Perilaku default ini dapat ditimpa dengan meneruskan delegasi handler kesalahan ke pengikatan yang dihasilkan pada instansiasinya:

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

Metode OnLayoutItemNotFound() ini dipanggil ketika ID sumber daya untuk atau View Fragment tidak dapat ditemukan.

Handler harus mengembalikan , nulldalam hal ini InvalidOperationException akan dilemparkan atau, sebaiknya, mengembalikan View instans atau Fragment yang sesuai dengan ID yang diteruskan ke handler. Objek yang dikembalikan harus dari jenis yang benar yang cocok dengan jenis properti Pengikatan yang sesuai. Nilai yang dikembalikan ditransmisikan ke jenis tersebut, jadi jika objek tidak diketik dengan benar, pengecualian akan dilemparkan.

Kode-Belakang

Code-Behind melibatkan pembuatan build-time kelas partial yang berisi properti yang sangat diketik untuk semua id dalam file tata letak.

Code-Behind dibangun di atas mekanisme Pengikatan, sambil mengharuskan file tata letak "ikut serta" ke pembuatan Code-Behind dengan menggunakan atribut XML baru xamarin:classes , yang merupakan ;daftar nama kelas lengkap yang dipisahkan untuk dihasilkan.

Mengingat file Resources\layout\Main.axmlTata Letak Android :

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

pada waktu build, jenis berikut akan diproduksi:

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

Ini memungkinkan penggunaan ID Sumber Daya yang lebih "intuitif" dalam tata letak:

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

Handler OnLayoutItemNotFound kesalahan dapat diteruskan sebagai parameter terakhir dari kelebihan SetContentView aktivitas apa pun yang menggunakan:

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

Karena Code-Behind bergantung pada kelas parsial, semua deklarasi kelas parsial harus digunakan partial class dalam deklarasi mereka, jika tidak , kesalahan kompilator CS0260 C# akan dihasilkan pada waktu build.

Penyesuaian

Jenis Code Behind yang dihasilkan selalu mengambil alih Activity.SetContentView(), dan secara default selalu memanggil base.SetContentView(), meneruskan parameter. Jika ini tidak diinginkan, maka salah OnSetContentView()partial satu metode harus ditimpa, mengatur callBaseAfterReturn ke :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);
  }
}

Contoh Kode yang Dihasilkan

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

Atribut XML Tata Letak

Banyak atribut XML Tata Letak baru mengontrol perilaku Pengikatan dan Code-Behind, yang berada dalam xamarin namespace XML (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools"). Ini termasuk:

xamarin:classes

Atribut xamarin:classes XML digunakan sebagai bagian dari Code-Behind untuk menentukan jenis mana yang harus dihasilkan.

Atribut xamarin:classes XML berisi ;daftar nama kelas lengkap yang dipisahkan yang harus dihasilkan.

xamarin:managedType

Atribut xamarin:managedType tata letak digunakan untuk secara eksplisit menentukan jenis terkelola untuk mengekspos ID terikat sebagai. Jika tidak ditentukan, jenis akan disimpulkan dari konteks deklarasikan, misalnya <Button/> akan menghasilkan Android.Widget.Button, dan <fragment/> akan menghasilkan Android.App.Fragment.

Atribut xamarin:managedType ini memungkinkan deklarasi jenis yang lebih eksplisit.

Pemetaan jenis terkelola

Sangat umum untuk menggunakan nama widget berdasarkan paket Java yang berasal dari dan, sama seringnya, nama .NET terkelola dari jenis tersebut akan memiliki nama (.NET style) yang berbeda di tanah terkelola. Generator kode dapat melakukan sejumlah penyesuaian yang sangat sederhana untuk mencoba mencocokkan kode, seperti:

  • Kapitalkan semua komponen jenis namespace layanan dan nama. Misalnya java.package.myButton akan menjadi Java.Package.MyButton

  • Kapitalkan komponen dua huruf dari namespace tipe. Misalnya android.os.SomeType akan menjadi Android.OS.SomeType

  • Cari sejumlah namespace layanan yang dikodekan secara permanen yang memiliki pemetaan yang diketahui. Saat ini daftar menyertakan pemetaan berikut:

    • android.view ->Android.Views
    • com.actionbarsherlock ->ABSherlock
    • com.actionbarsherlock.widget ->ABSherlock.Widget
    • com.actionbarsherlock.view ->ABSherlock.View
    • com.actionbarsherlock.app ->ABSherlock.App
  • Cari sejumlah jenis yang dikodekan secara permanen dalam tabel internal. Saat ini daftar menyertakan jenis berikut:

  • Nomor strip awalan namespace yang dikodekan secara permanen. Saat ini daftar menyertakan awalan berikut:

    • com.google.

Namun, jika upaya di atas gagal, Anda harus memodifikasi tata letak yang menggunakan widget dengan jenis yang tidak dipetakan seperti itu xamarin untuk menambahkan deklarasi namespace XML ke elemen akar tata letak dan xamarin:managedType ke elemen yang memerlukan pemetaan. Contohnya:

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

Akan menggunakan CommonSampleLibrary.LogFragment jenis untuk jenis commonsamplelibrary.LogFragmentasli .

Anda dapat menghindari penambahan deklarasi namespace XML dan xamarin:managedType atribut hanya dengan menamai jenis menggunakan nama terkelolanya, misalnya fragmen di atas dapat direeklarasikan ulang sebagai berikut:

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

Fragmen: kasus khusus

Ekosistem Android saat ini mendukung dua implementasi widget yang Fragment berbeda:

Kelas-kelas ini tidak kompatibel satu sama lain dan jadi perawatan khusus harus diambil saat menghasilkan kode pengikatan untuk <fragment> elemen dalam file tata letak. .NET untuk Android harus memilih satu Fragment implementasi sebagai implementasi default yang akan digunakan jika <fragment> elemen tidak memiliki jenis tertentu (terkelola atau sebaliknya) yang ditentukan. Generator kode pengikatan menggunakan $(AndroidFragmentType) Properti MSBuild untuk tujuan tersebut. Properti dapat ditimpa oleh pengguna untuk menentukan jenis yang berbeda dari yang default. Properti diatur ke Android.App.Fragment secara default, dan ditimpa oleh paket AndroidX NuGet.

Jika kode yang dihasilkan tidak dibuat, file tata letak harus diubah dengan menentukan jenis fragmen yang dirusak yang dimaksud.

Pemilihan dan pemrosesan tata letak code-behind

Pilihan

Secara default pembuatan code-behind dinonaktifkan. Untuk mengaktifkan pemrosesan untuk semua tata letak di salah Resource\layout* satu direktori yang berisi setidaknya satu elemen dengan //*/@android:id atribut , atur $(AndroidGenerateLayoutBindings) properti MSBuild ke True pada baris perintah msbuild:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

atau dalam file .csproj Anda:

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

Atau, Anda dapat membiarkan code-behind dinonaktifkan secara global dan mengaktifkannya hanya untuk file tertentu. Untuk mengaktifkan Code-Behind untuk file tertentu .axml , ubah file agar memiliki tindakan Build @(AndroidBoundLayout) dengan mengedit file Anda .csproj dan mengganti AndroidResource dengan AndroidBoundLayout:

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

Sedang diproses

Tata letak dikelompokkan menurut nama, dengan templat bernama seperti dari direktori yang berbedaResource\layout* yang terdiri dari satu grup. Grup tersebut diproses seolah-olah mereka adalah tata letak tunggal. Ada kemungkinan bahwa dalam kasus seperti itu akan ada bentrokan jenis antara dua widget yang ditemukan di tata letak yang berbeda milik grup yang sama. Dalam kasus seperti itu, properti yang dihasilkan tidak akan dapat memiliki jenis widget yang tepat, melainkan properti "membusuk". Pembusukan mengikuti algoritma di bawah ini:

  1. Jika semua widget yang bertentangan adalah View turunan, jenis properti akan menjadi Android.Views.View

  2. Jika semua jenis yang bertentangan adalah Fragment turunan, jenis properti akan menjadi Android.App.Fragment

  3. Jika widget yang bertentangan berisi dan View Fragment, jenis properti akan global::System.Object

Kode yang dihasilkan

Jika Anda tertarik dengan bagaimana kode yang dihasilkan mencari tata letak Anda, silakan lihat di obj\$(Configuration)\generated folder di direktori solusi Anda.