AndroidのLayoutのxmlファイルからViewへのアクセスクラスを生成してみる

最低限で

```csharp
	public class Control
	{
		public string Name { get; set; }
		public string Id { get; set; }
	}

	internal class Program
	{
		private static void Main(string[] args)
		{
			var filepath = args[0];
			var nameSpaceName = args[1];

			var xml = File.ReadAllText(filepath);
			var xdoc = XDocument.Parse(xml);

			var reader = xdoc.CreateReader();
			var controls = new List<Control>();
			while (reader.Read())
			{
				var control = new Control();
				control.Name = reader.Name;
				if (reader.MoveToAttribute("android:id"))
				{
					control.Id = reader.Value.Split('/')[1];
					controls.Add(control);
				}
			}

			var sb = new StringBuilder();
			var classname = Path.GetFileNameWithoutExtension(filepath) + "_holder";

			sb.AppendLine(@"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;");

			sb.AppendLine($"public class {classname} : IDisposable");
			sb.AppendLine("{");

			foreach (var control in controls)
			{
				sb.Append("\t");
				sb.Append($"public {control.Name} {control.Id} ");
				sb.Append("{ get; }");
				sb.AppendLine();
			}
			sb.AppendLine();

			sb.Append("\t");
			sb.Append($"public {classname}(View view)");
			sb.AppendLine();

			sb.AppendLine("\t{");

			foreach (var control in controls)
			{
				sb.Append("\t");
				sb.Append("\t");
				sb.Append($"{control.Id} = view.FindViewById<{control.Name}>({nameSpaceName}.Resource.Id.{control.Id});");
				sb.AppendLine();
			}

			sb.AppendLine("\t}");

			sb.AppendLine($"\tpublic void Dispose()");
			sb.AppendLine("\t{");

			foreach (var control in controls)
			{
				sb.Append("\t");
				sb.Append("\t");
				sb.Append($"{control.Id}.Dispose();");
				sb.AppendLine();
			}
			sb.AppendLine("\t}");

			sb.AppendLine("}");

			Console.Write(sb);

			var file = File.Create($"{classname}.cs");

			TextWriter text = new StreamWriter(file);
			text.Write(sb.ToString());

			text.Flush();
			text.Dispose();

			Console.Read();
		}
	}
```

こんなxmlがあったら

```xml

<?xml version="1.0" encoding="utf-8"?>

<ScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <LinearLayout
    android:id="@+id/root_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <RelativeLayout
      android:id="@+id/language_settings_container"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:background="@drawable/clickable"
      android:clickable="true"
      android:padding="@dimen/space_16dp">

      <TextView
        android:id="@+id/txt_title"
        style="@style/SettingTitleText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@+id/txt_language"
        android:layout_toStartOf="@+id/txt_language"
        android:ellipsize="end"
        android:lines="1"
        android:text="@string/settings_language" />

      <TextView
        android:id="@+id/setting_description"
        style="@style/SettingDescriptionText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/txt_title"
        android:layout_marginTop="@dimen/space_8dp"
        android:layout_toLeftOf="@+id/txt_language"
        android:layout_toStartOf="@+id/txt_language"
        android:text="@string/settings_language_description" />

      <TextView
        android:id="@+id/txt_language"
        style="@style/SessionTag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginLeft="@dimen/space_16dp"
        android:layout_marginStart="@dimen/space_16dp"
        android:background="@drawable/tag_language"
        android:padding="@dimen/space_8dp"
        tools:text="@string/lang_en" />

    </RelativeLayout>

    <View
      android:id="@+id/heads_up_border_top"
      style="@style/Border" />

    <DroidKaigi2017.Droid.Views.CustomViews.SettingSwitchRowView
      android:id="@+id/local_time_switch_row"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      app:settingDescription="@string/settings_local_time_description"
      app:settingTitle="@string/settings_local_time" />

    <View
      android:id="@+id/heads_up_border_bottom"
      style="@style/Border" />

    <RelativeLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:padding="@dimen/space_16dp">

      <TextView
        style="@style/TextSubheading"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/settings_notification_group"
        android:textColor="?attr/colorPrimary" />

    </RelativeLayout>

    <DroidKaigi2017.Droid.Views.CustomViews.SettingSwitchRowView
      android:id="@+id/notification_switch_row"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      app:settingDescription="@string/settings_notification_description"
      app:settingTitle="@string/settings_notification" />

    <View
      style="@style/Border" />

    <DroidKaigi2017.Droid.Views.CustomViews.SettingSwitchRowView
      android:id="@+id/heads_up_switch_row"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      app:settingDescription="@string/settings_heads_up_notification_description"
      app:settingTitle="@string/settings_heads_up_notification" />

    <View
      android:id="@+id/heads_up_border"
      style="@style/Border" />

    <RelativeLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:padding="@dimen/space_16dp">

      <TextView
        android:id="@+id/developer_menu_title"
        style="@style/TextSubheading"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/settings_developer_group"
        android:textColor="?attr/colorPrimary" />

      <TextView
        android:id="@+id/developer_menu_tips"
        style="@style/SettingDescriptionText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/developer_menu_title"
        android:layout_marginTop="@dimen/space_8dp"
        android:text="@string/settings_developer_tips" />
    </RelativeLayout>

    <DroidKaigi2017.Droid.Views.CustomViews.SettingSwitchRowView
      android:id="@+id/debug_overlay_view_switch_row"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      app:settingDescription="@string/settings_debug_overlay_view_description"
      app:settingTitle="@string/settings_debug_overlay_view" />


    <View style="@style/Border" />

    <Switch
      android:id="@+id/setting_switch"
      android:checked="true"
      android:layout_width="match_parent"
      android:layout_height="wrap_content" />

  </LinearLayout>

</ScrollView>
```

こんな感じ

```
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
public class fragment_settings_holder : IDisposable
{
	public LinearLayout root_view { get; }
	public RelativeLayout language_settings_container { get; }
	public TextView txt_title { get; }
	public TextView setting_description { get; }
	public TextView txt_language { get; }
	public View heads_up_border_top { get; }
	public DroidKaigi2017.Droid.Views.CustomViews.SettingSwitchRowView local_time_switch_row { get; }
	public View heads_up_border_bottom { get; }
	public DroidKaigi2017.Droid.Views.CustomViews.SettingSwitchRowView notification_switch_row { get; }
	public DroidKaigi2017.Droid.Views.CustomViews.SettingSwitchRowView heads_up_switch_row { get; }
	public View heads_up_border { get; }
	public TextView developer_menu_title { get; }
	public TextView developer_menu_tips { get; }
	public DroidKaigi2017.Droid.Views.CustomViews.SettingSwitchRowView debug_overlay_view_switch_row { get; }
	public Switch setting_switch { get; }

	public fragment_settings_holder(View view)
	{
		root_view = view.FindViewById<LinearLayout>(DroidKaigi2017.Droid.Resource.Id.root_view);
		language_settings_container = view.FindViewById<RelativeLayout>(DroidKaigi2017.Droid.Resource.Id.language_settings_container);
		txt_title = view.FindViewById<TextView>(DroidKaigi2017.Droid.Resource.Id.txt_title);
		setting_description = view.FindViewById<TextView>(DroidKaigi2017.Droid.Resource.Id.setting_description);
		txt_language = view.FindViewById<TextView>(DroidKaigi2017.Droid.Resource.Id.txt_language);
		heads_up_border_top = view.FindViewById<View>(DroidKaigi2017.Droid.Resource.Id.heads_up_border_top);
		local_time_switch_row = view.FindViewById<DroidKaigi2017.Droid.Views.CustomViews.SettingSwitchRowView>(DroidKaigi2017.Droid.Resource.Id.local_time_switch_row);
		heads_up_border_bottom = view.FindViewById<View>(DroidKaigi2017.Droid.Resource.Id.heads_up_border_bottom);
		notification_switch_row = view.FindViewById<DroidKaigi2017.Droid.Views.CustomViews.SettingSwitchRowView>(DroidKaigi2017.Droid.Resource.Id.notification_switch_row);
		heads_up_switch_row = view.FindViewById<DroidKaigi2017.Droid.Views.CustomViews.SettingSwitchRowView>(DroidKaigi2017.Droid.Resource.Id.heads_up_switch_row);
		heads_up_border = view.FindViewById<View>(DroidKaigi2017.Droid.Resource.Id.heads_up_border);
		developer_menu_title = view.FindViewById<TextView>(DroidKaigi2017.Droid.Resource.Id.developer_menu_title);
		developer_menu_tips = view.FindViewById<TextView>(DroidKaigi2017.Droid.Resource.Id.developer_menu_tips);
		debug_overlay_view_switch_row = view.FindViewById<DroidKaigi2017.Droid.Views.CustomViews.SettingSwitchRowView>(DroidKaigi2017.Droid.Resource.Id.debug_overlay_view_switch_row);
		setting_switch = view.FindViewById<Switch>(DroidKaigi2017.Droid.Resource.Id.setting_switch);
	}
	public void Dispose()
	{
		root_view.Dispose();
		language_settings_container.Dispose();
		txt_title.Dispose();
		setting_description.Dispose();
		txt_language.Dispose();
		heads_up_border_top.Dispose();
		local_time_switch_row.Dispose();
		heads_up_border_bottom.Dispose();
		notification_switch_row.Dispose();
		heads_up_switch_row.Dispose();
		heads_up_border.Dispose();
		developer_menu_title.Dispose();
		developer_menu_tips.Dispose();
		debug_overlay_view_switch_row.Dispose();
		setting_switch.Dispose();
	}
}
```

ソリューションに組み込めば少し幸せになれるかもしれない。


実際に組み込んだらこんな感じ ↓

```csharp

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Nyanto.ViewSupportTool
{
	class Program
	{
		static Dictionary<string,string> replaceName = new Dictionary<string, string>()
		{
			{"android.support.constraint.ConstraintLayout", "android.support.constraints.ConstraintLayout" }
		};
		static void Main(string[] args)
		{
			
			var targetProjectPath = args[0];
			var rootNamespace = args[1];
			var resourcePath = Path.Combine(targetProjectPath, "Resources");
			var layoutPath = Path.Combine(resourcePath, "layout");
			var outputFilePath = Path.Combine(resourcePath, "Layout.Designer.cs");
			var files = Directory.GetFiles(layoutPath);
			var sb = new StringBuilder();
			sb.AppendLine("using System;");
			sb.AppendLine("using System.Collections.Generic;");
			sb.AppendLine("using System.Linq;");
			sb.AppendLine("using System.Text;");
			sb.AppendLine("using Android.App;");
			sb.AppendLine("using Android.Content;");
			sb.AppendLine("using Android.OS;");
			sb.AppendLine("using Android.Runtime;");
			sb.AppendLine("using Android.Views;");
			sb.AppendLine("using Android.Widget;");
			sb.AppendLine();
			sb.AppendLine($"namespace {rootNamespace}");
			sb.AppendLine("{");
			foreach (var file in files)
			{
				CreateClass(sb, file, rootNamespace);
			}
			sb.AppendLine("}");

			Console.WriteLine(sb);
			var writer = File.CreateText(outputFilePath);
			writer.Write(sb.ToString());
			
			writer.Flush();
			writer.Dispose();

			Console.Read();
		}

		static void CreateClass(StringBuilder sb, string filePath, string nameSpaceName)
		{
			var classname = Path.GetFileNameWithoutExtension(filePath) + "_holder";
			var xml = File.ReadAllText(filePath);
			var xdoc = XDocument.Parse(xml);
			var reader = xdoc.CreateReader();
			var controls = new List<Control>();
			while (reader.Read())
			{
				var control = new Control();
				var basename = reader.Name;
				if (replaceName.ContainsKey(basename))
				{
					basename = replaceName[basename];
				}
				var fileName = string.Join(".", basename.Split('.').Select(x => FirstLetterToUpper(x)));

				control.Name = fileName;

				if (reader.MoveToAttribute("android:id"))
				{
					control.Id = reader.Value.Split('/')[1];
					controls.Add(control);
				}
			}

			sb.Append("\t");
			sb.AppendLine($"public class {classname} : IDisposable");
			sb.Append("\t");
			sb.AppendLine("{");

			foreach (var control in controls)
			{
				sb.Append("\t");
				sb.Append("\t");
				sb.Append($"public {control.Name} {control.Id} ");
				sb.Append("{ get; }");
				sb.AppendLine();
			}
			sb.AppendLine();

			sb.Append("\t");
			sb.Append("\t");
			sb.Append($"public {classname}(View view)");
			sb.AppendLine();

			sb.Append("\t");
			sb.AppendLine("\t{");

			foreach (var control in controls)
			{
				sb.Append("\t");
				sb.Append("\t");
				sb.Append("\t");
				sb.Append($"{control.Id} = view.FindViewById<{control.Name}>({nameSpaceName}.Resource.Id.{control.Id});");
				sb.AppendLine();
			}

			sb.Append("\t");
			sb.AppendLine("\t}");

			sb.Append("\t");
			sb.AppendLine($"\tpublic void Dispose()");
			sb.Append("\t");
			sb.AppendLine("\t{");

			foreach (var control in controls)
			{
				sb.Append("\t");
				sb.Append("\t");
				sb.Append("\t");
				sb.Append($"{control.Id}.Dispose();");
				sb.AppendLine();
			}
			sb.Append("\t");
			sb.AppendLine("\t}");

			sb.Append("\t");
			sb.AppendLine("}");
			sb.AppendLine();


		}

		public static string FirstLetterToUpper(string str)
		{
			if (str == null)
				return null;

			if (str.Length > 1)
				return char.ToUpper(str[0]) + str.Substring(1);

			return str.ToUpper();
		}

		private class Control
		{
			public string Name { get; set; }
			public string Id { get; set; }
		}
	}
}
```

こんな感じにしてtargetsスクリプトでごにょごにょすれば自動的に作成されるようになる。

```csharp

				var viewAccesor = new view_session_cell_holder(holder.ItemView);

				
				viewAccesor.root.Clickable = vm.IsSelectable;

				viewAccesor.root.SetBackgroundResource(vm.BackgroundResourceId);

				viewAccesor.categoryBorder.Visibility = vm.IsNormalSession.ToViewStates();
				viewAccesor.categoryBorder.SetBackgroundResource(vm.TopicColorResourceId);
				viewAccesor.img_check.Visibility = vm.IsCheckVisible.Value.ToViewStates();
				viewAccesor.root.Click += (sender, args) => { };
				viewAccesor.root.LongClick += (sender, args) =>
				{
					var pos = base._recyclerView.GetChildAdapterPosition((View) sender);
					var viewmodel = Get(pos);
					viewmodel.CheckCommand.Execute(!viewmodel.IsCheckVisible.Value);
				};
				vm.IsCheckVisible
					.Skip(1)
					.ObserveOnUIDispatcher()
					.Subscribe(x => viewAccesor.img_check.Visibility = x.ToViewStates());

				viewAccesor.txt_time.Text = vm.ShortStartTime;
				viewAccesor.txt_minutes.Text = vm.Minutes;
				viewAccesor.txt_title.Text = vm.Title;
				viewAccesor.txt_title.SetMaxLines(vm.TitleMaxLines);
				viewAccesor.txt_language.Text = vm.LanguageId;
				viewAccesor.txt_language.Visibility = vm.IsLanguageVisible.ToViewStates();
				viewAccesor.txt_speaker_name.Text = vm.SpeakerName;
				viewAccesor.txt_speaker_name.SetMaxLines(vm.SpeakerNameMaxLines);
				viewAccesor.txt_speaker_name.Visibility = vm.IsNormalSession.ToViewStates();
```

こんな感じでかけて少し幸せ。