다음을 통해 공유


개요

Android용 .NET 빌드의 일부로 Android 리소스가 처리되어 생성된 _Microsoft.Android.Resource.Designer.dll 어셈블리를 통해 Android ID가 노출됩니다. 예를 들어 내용이 포함된 파일이 Reources\layout\Main.axml 지정된 경우:

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

그런 다음 빌드 시간 _Microsoft.Android.Resource.Designer.dll 동안 다음과 유사한 내용이 있는 어셈블리를 만듭니다.

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

일반적으로 리소스와 상호 작용하는 작업은 형식 및 FindViewById<T>() 메서드의 상수로 코드에서 Resource 수행됩니다.

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

Xamarin.Android 8.4부터 C#을 사용할 때 Android 리소스와 상호 작용하는 두 가지 추가 방법이 있습니다.

  1. 바인딩
  2. 코드 숨김

이러한 새 기능을 사용하도록 설정하려면 $(AndroidGenerateLayoutBindings) msbuild 명령줄에 있는 MSBuild 속성 True :

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

또는 .csproj 파일에서 다음을 수행합니다.

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

바인딩

바인딩은 Android 레이아웃 파일당 하나씩 생성된 클래스로, 레이아웃 파일 내의 모든 ID에 대해 강력한 형식의 속성을 포함합니다. 바인딩 형식은 레이아웃 파일의 global::Bindings 파일 이름을 미러링하는 형식 이름을 사용하여 네임스페이스에 생성됩니다.

바인딩 형식은 Android ID를 포함하는 모든 레이아웃 파일에 대해 만들어집니다.

Android 레이아웃 파일이 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>

그런 다음 다음 형식이 생성됩니다.

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

바인딩의 기본 형식 Xamarin.Android.Design.LayoutBindingAndroid용 .NET 클래스 라이브러리의 일부가 아니라 원본 형식으로 Android용 .NET과 함께 제공되며 바인딩을 사용할 때마다 애플리케이션 빌드에 자동으로 포함됩니다.

생성된 바인딩 형식은 인스턴스를 중심으로 Activity 만들 수 있으므로 레이아웃 파일 내의 ID에 대한 강력한 형식의 액세스를 허용합니다.

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

또한 인스턴스를 중심으로 View 바인딩 형식을 생성하여 뷰 또는 해당 자식 내의 리소스 ID에 대한 강력한 형식의 액세스를 허용할 수 있습니다.

var binding = new Binding.Main (some_view);

누락된 리소스 ID

바인딩 형식의 속성은 여전히 구현에 사용됩니다 FindViewById<T>() . 반환되는 경우 FindViewById<T>() 기본 동작은 속성을 반환하는 대신 throw InvalidOperationException 하는 null것입니다.null

이 기본 동작은 인스턴스화에서 생성된 바인딩에 오류 처리기 대리자를 전달하여 재정의될 수 있습니다.

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

OnLayoutItemNotFound() 메서드는 a 또는 Fragment a의 리소스 ID를 View 찾을 수 없을 때 호출됩니다.

처리기는 throw되거나, 바람직하게는 처리기에 전달된 ID에 View 해당하는 인스턴스를 Fragment 반환해야 합니다 null InvalidOperationException. 반환된 개체 는 해당 Binding 속성의 형식과 일치하는 올바른 형식이어야 합니다 . 반환된 값은 해당 형식으로 캐스팅되므로 개체가 올바르게 형식화되지 않은 경우 예외가 throw됩니다.

코드 숨김

코드 숨김에는 레이아웃 파일 내의 모든 ID에 대해 강력한 형식의 partial 속성이 포함된 클래스의 빌드 시간 생성이 포함됩니다 .

코드 숨김은 바인딩 메커니즘 위에 빌드되며, 생성할 전체 클래스 이름의 구분된 목록인 새 xamarin:classes XML 특성을 ;사용하여 레이아웃 파일을 코드 숨김 생성에 "옵트인"하도록 요구합니다.

Android 레이아웃 파일이 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>

빌드 시 다음 형식이 생성됩니다.

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

이렇게 하면 레이아웃 내에서 리소스 ID를 보다 "직관적으로" 사용할 수 있습니다.

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

OnLayoutItemNotFound 오류 처리기는 사용 중인 작업의 오버로드 SetContentView 에 대한 마지막 매개 변수로 전달될 수 있습니다.

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

Code-Behind에서 partial 클래스를 사용하므로 partial 클래스 의 모든 선언은 해당 선언에 사용해야 partial class 하며, 그렇지 않으면 빌드 시 CS0260 C# 컴파일러 오류가 생성됩니다.

사용자 지정

생성된 Code Behind 형식 은 항상 재정의 Activity.SetContentView()되며, 기본적으로 항상 호출 base.SetContentView()하여 매개 변수를 전달합니다. 원하는 것이 아닌 경우 메서드 중 하나를 재정의partialOnSetContentView()하고 다음으로 false설정 callBaseAfterReturn 해야 합니다.

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

생성된 코드 예제

// 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 특성

많은 새 레이아웃 XML 특성은 XML 네임스페이스(xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools")에 있는 바인딩 및 코드 숨김 동작을 xamarin 제어합니다. 여기에는 다음이 포함됩니다.

xamarin:classes

xamarin:classes XML 특성은 생성해야 하는 형식을 지정하기 위해 Code-Behind일부로 사용됩니다.

XML 특성에는 xamarin:classes 생성해야 하는 전체 클래스 이름의 구분된 목록이 포함되어 ;있습니다.

xamarin:managedType

xamarin:managedType 레이아웃 특성은 바인딩된 ID를 노출할 관리되는 형식을 명시적으로 지정하는 데 사용됩니다. 지정하지 않으면 형식이 선언 컨텍스트에서 유추됩니다. 예를 들어 <Button/> ,가 생성Android.Widget.Button되고 <fragment/> .Android.App.Fragment

이 특성은 xamarin:managedType 보다 명시적인 형식 선언을 허용합니다.

관리되는 형식 매핑

이러한 형식의 관리되는 .NET 이름은 관리되는 토지에서 다른(.NET 스타일) 이름을 가지는 경우가 많으며, Java 패키지에 따라 위젯 이름을 사용하는 것이 일반적입니다. 코드 생성기는 다음과 같이 코드를 일치시키려고 매우 간단한 여러 조정을 수행할 수 있습니다.

  • 형식 네임스페이스와 이름의 모든 구성 요소를 대문자로 표시합니다. 예를 들어 java.package.myButtonJava.Package.MyButton

  • 형식 네임스페이스의 두 글자 구성 요소를 대문자로 표시합니다. 예를 들어 android.os.SomeTypeAndroid.OS.SomeType

  • 알려진 매핑이 있는 여러 하드 코딩된 네임스페이스를 조회합니다. 현재 목록에는 다음 매핑이 포함됩니다.

    • android.view ->Android.Views
    • com.actionbarsherlock ->ABSherlock
    • com.actionbarsherlock.widget ->ABSherlock.Widget
    • com.actionbarsherlock.view ->ABSherlock.View
    • com.actionbarsherlock.app ->ABSherlock.App
  • 내부 테이블에서 여러 하드 코딩된 형식을 조회합니다. 현재 목록에는 다음 형식이 포함됩니다.

  • 하드 코딩된 네임스페이스 접두사 수를 제거합니다. 현재 목록에는 다음 접두사가 포함됩니다.

    • com.google.

그러나 위의 시도가 실패하는 경우 매핑되지 않은 형식의 위젯을 사용하는 레이아웃을 수정하여 XML 네임스페이스 선언을 레이아웃의 루트 요소와 xamarin:managedType 매핑이 필요한 요소에 모두 xamarin 추가해야 합니다. 예를 들면 다음과 같습니다.

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

네이티브 형식에 CommonSampleLibrary.LogFragment 대한 형식 commonsamplelibrary.LogFragment을 사용합니다.

XML 네임스페이스 선언 및 xamarin:managedType 특성은 관리되는 이름을 사용하여 간단히 이름을 지정하여 추가할 수 있습니다. 예를 들어 위의 조각을 다음과 같이 다시 선언할 수 있습니다.

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

조각: 특수 사례

Android 에코시스템은 현재 위젯의 두 가지 고유한 구현을 Fragment 지원합니다.

이러한 클래스는 서로 호환되지 않으므로 레이아웃 파일의 요소에 대한 <fragment> 바인딩 코드를 생성할 때 특별히 주의해야 합니다. Android용 .NET은 요소에 특정 형식(관리형 또는 기타)이 지정되지 않은 경우 <fragment> 사용할 기본 구현으로 하나의 Fragment 구현을 선택해야 합니다. 바인딩 코드 생성기는 다음을 사용합니다. $(AndroidFragmentType) 해당 용도에 대한 MSBuild 속성입니다. 사용자가 속성을 재정의하여 기본 형식과 다른 형식을 지정할 수 있습니다. 속성은 기본적으로 설정 Android.App.Fragment 되며 AndroidX NuGet 패키지에 의해 재정의됩니다.

생성된 코드가 빌드되지 않으면 해당 조각의 관리 형식을 지정하여 레이아웃 파일을 수정해야 합니다.

코드 숨김 레이아웃 선택 및 처리

선택 사항

기본적으로 코드 숨김 생성은 사용하지 않도록 설정됩니다. 특성이 있는 하나 이상의 요소가 //*/@android:id 포함된 디렉터리에 있는 Resource\layout* 모든 레이아웃에 대한 처리를 사용하도록 설정하려면 MSBuild 속성을 True msbuild 명령줄에서 다음 중 하나를 사용하도록 설정합니다$(AndroidGenerateLayoutBindings).

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

또는 .csproj 파일에서 다음을 수행합니다.

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

또는 코드 숨김을 전역적으로 사용하지 않도록 설정하고 특정 파일에 대해서만 사용하도록 설정할 수 있습니다. 특정 .axml 파일에 대해 Code-Behind를 사용하도록 설정하려면 빌드 작업을 수행하도록 파일을 변경합니다.@(AndroidBoundLayout)파일을 편집하고 다음으로 AndroidBoundLayout바꿔 AndroidResource.csproj

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

처리

레이아웃은 단일 그룹으로 구성된 여러Resource\layout* 디렉터리에서 명명된 템플릿을 사용하여 이름으로 그룹화됩니다. 이러한 그룹은 단일 레이아웃인 것처럼 처리됩니다. 이러한 경우 동일한 그룹에 속하는 서로 다른 레이아웃에 있는 두 위젯 간에 형식 충돌이 있을 수 있습니다. 이러한 경우 생성된 속성은 정확한 위젯 형식이 아니라 "감쇠" 형식을 가질 수 없습니다. 감쇠는 아래 알고리즘을 따릅니다.

  1. 충돌하는 모든 위젯이 View 파생 항목인 경우 속성 형식은 다음과 같습니다. Android.Views.View

  2. 충돌하는 모든 형식이 Fragment 파생 항목인 경우 속성 형식은 Android.App.Fragment

  3. 충돌하는 위젯에 a와 a View 가 모두 포함된 경우 속성 형식은 Fragment/>입니다. global::System.Object

생성된 코드

생성된 코드가 레이아웃을 찾는 방식에 관심이 있는 경우 솔루션 디렉터리의 폴더를 obj\$(Configuration)\generated 살펴보세요.