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

  • 2
    いいね
  • 0
    コメント

最低限で

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

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

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


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スクリプトでごにょごにょすれば自動的に作成されるようになる。


                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();

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