.NET MAUI(.NET 9)でカスタムテーマ対応トグルスイッチを作る方法|Switch拡張とContentView自作

.NET MAUI(.NET 9)で「見た目がデザインされたトグルスイッチ」を作り、ライト/ダークなどのテーマ切り替えと自然に連動させる実装方法をまとめます。標準のSwitchを“拡張して使う”方法と、ContentViewでトラック・つまみ・アイコン・アニメーションまで“完全に自作する”方法を、コピペして動かしやすい形で解説します。

目次

今回作りたい「カスタムテーマ付きトグルスイッチ」の要件を整理

まずは要件を分解すると、必要な要素がはっきりします。

  • XAMLから呼び出せる(=コントロール本体はC#で定義し、<local:... />で使える)
  • 凝った見た目(単なる色替えではなく、トラック形状・つまみ・アイコン・影・境界線などを作り込む)
  • テーマ切り替えと連動(スイッチのON/OFFでアプリのライト/ダークを切り替える。さらに色リソースも追従)
  • 再利用可能(ページごとに作り捨てず、複数画面で共通利用できる)

実装アプローチの全体像(おすすめの選び方)

アプローチできること向いているケース弱点
標準Switchの拡張
(Style / VisualStateManager / C#派生)
ON時の色、つまみ色、状態連動、最小のカスタムまず動くものを早く作りたい/OSネイティブ見た目を活かしたい参考画像のような“完全なデザイン再現”は難しい(プラットフォーム差が大きい)
ContentViewで完全自作
(BoxView/Border/Image/Animation)
トラック・つまみ・アイコン・アニメーション・影まで自由に設計画像に近いUIを再現したい/全プラットフォームで同じ見た目にしたい実装量は増える(ただし一度作れば再利用しやすい)
Handlerでネイティブ改造
(上級)
標準Switchの挙動を保ったまま、ネイティブ側の細部も調整標準Switchをベースにしつつ、どうしても足りない箇所だけ埋めたいプラットフォーム別コードが必要になりがち

結論として、参考画像のようにデザインされたスイッチを狙うなら、最終的にはContentViewで自作が最短ルートです。とはいえ、標準Switch拡張は「実装コストが低い」「OSの操作感を保てる」メリットがあるので、段階的に進めるのが現実的です。

先に決めておくと強い:テーマ設計を「色トークン化」する

テーマ連動の実装でよく起きる失敗が、「色をあちこちに直書きして後から変更地獄」になることです。最初からリソースキー(色トークン)を作り、ライト/ダークで差し替える前提にすると、スイッチだけでなく画面全体も一気に整います。

キー例用途ライト例ダーク例
ThemeSurface画面背景#FFFFFF#121212
ThemeText文字色#111111#F2F2F2
ThemeSwitchTrackOffスイッチOFF時トラック#D7D7D7#3A3A3A
ThemeSwitchTrackOnスイッチON時トラック#FFB000#FFB000
ThemeSwitchThumbつまみ背景#FFFFFF#1E1E1E
ThemeSwitchThumbBorderつまみ枠線#C7C7C7#555555

Light/DarkのResourceDictionaryを作る(差し替え前提)

例として、Themes/LightTheme.xamlとThemes/DarkTheme.xamlを用意します(色は例なのでプロジェクトのデザインに合わせて調整してください)。

&lt;!-- Themes/LightTheme.xaml --&gt;
&lt;ResourceDictionary
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="MyApp.Themes.LightTheme"&gt;

    &lt;Color x:Key="ThemeSurface"&gt;#FFFFFF&lt;/Color&gt;
    &lt;Color x:Key="ThemeText"&gt;#111111&lt;/Color&gt;

    &lt;Color x:Key="ThemeSwitchTrackOff"&gt;#D7D7D7&lt;/Color&gt;
    &lt;Color x:Key="ThemeSwitchTrackOn"&gt;#FFB000&lt;/Color&gt;
    &lt;Color x:Key="ThemeSwitchThumb"&gt;#FFFFFF&lt;/Color&gt;
    &lt;Color x:Key="ThemeSwitchThumbBorder"&gt;#C7C7C7&lt;/Color&gt;

&lt;/ResourceDictionary&gt;
&lt;!-- Themes/DarkTheme.xaml --&gt;
&lt;ResourceDictionary
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="MyApp.Themes.DarkTheme"&gt;

    &lt;Color x:Key="ThemeSurface"&gt;#121212&lt;/Color&gt;
    &lt;Color x:Key="ThemeText"&gt;#F2F2F2&lt;/Color&gt;

    &lt;Color x:Key="ThemeSwitchTrackOff"&gt;#3A3A3A&lt;/Color&gt;
    &lt;Color x:Key="ThemeSwitchTrackOn"&gt;#FFB000&lt;/Color&gt;
    &lt;Color x:Key="ThemeSwitchThumb"&gt;#1E1E1E&lt;/Color&gt;
    &lt;Color x:Key="ThemeSwitchThumbBorder"&gt;#555555&lt;/Color&gt;

&lt;/ResourceDictionary&gt;

ポイント:後からテーマを切り替えるなら、コントロール側でStaticResourceを多用すると反映されにくくなります。色やスタイルは基本的にDynamicResourceで参照して、辞書差し替えでUIが追従する構造にするのが安定です。

方法1:標準Switchをスタイル&状態でカスタムする

まずは標準Switchでどこまで行けるかを押さえます。標準SwitchはプラットフォームのネイティブUIに寄るため、完全に同じ見た目を再現するのは難しい一方、操作感とアクセシビリティが高いのが利点です。

最小構成:プロパティだけで色と状態を持つ

&lt;Switch
    OnColor="Orange"
    ThumbColor="Green"
    IsToggled="True" /&gt;
var themedSwitch = new Switch
{
    OnColor = Colors.Orange,
    ThumbColor = Colors.Green,
    IsToggled = true
};

ここで重要なのは、標準Switchは基本的に「ONのトラック色(OnColor)」と「つまみ色(ThumbColor)」の調整が中心で、OFF側のトラック色や形状などは深くいじれないことです。参考画像のようなデザインを目指す場合、後述の「完全自作」へ移行する判断が必要になります。

VisualStateManagerでON/OFF時の見た目を切り替える

ON/OFFでつまみ色などを切り替えたいときはVisualStateManagerが便利です。共通のスタイルにまとめると運用が楽になりますが、まずは直書きの例です。

&lt;Switch IsToggled="True" Scale="2"&gt;
    &lt;VisualStateManager.VisualStateGroups&gt;
        &lt;VisualStateGroupList&gt;
            &lt;VisualStateGroup x:Name="CommonStates"&gt;

                &lt;VisualState x:Name="On"&gt;
                    &lt;VisualState.Setters&gt;
                        &lt;Setter Property="ThumbColor" Value="MediumSpringGreen" /&gt;
                        &lt;Setter Property="OnColor" Value="Orange" /&gt;
                    &lt;/VisualState.Setters&gt;
                &lt;/VisualState&gt;

                &lt;VisualState x:Name="Off"&gt;
                    &lt;VisualState.Setters&gt;
                        &lt;Setter Property="ThumbColor" Value="Red" /&gt;
                        &lt;Setter Property="OnColor" Value="Gray" /&gt;
                    &lt;/VisualState.Setters&gt;
                &lt;/VisualState&gt;

            &lt;/VisualStateGroup&gt;
        &lt;/VisualStateGroupList&gt;
    &lt;/VisualStateManager.VisualStateGroups&gt;
&lt;/Switch&gt;

実務では、ON/OFFの色をテーマ辞書に置いて、Value="{DynamicResource ...}"で参照するのが定番です。

App.xamlに共通Styleを定義して、全画面で統一する

Switchをあちこちで使うなら、スタイル化が必須です。例として「テーマ辞書の色キー」を参照するSwitchスタイルを作ります。

&lt;!-- App.xaml(または Styles.xaml) --&gt;
&lt;Style x:Key="ThemedSwitchStyle" TargetType="Switch"&gt;
    &lt;Setter Property="Scale" Value="1.6" /&gt;
    &lt;Setter Property="OnColor" Value="{DynamicResource ThemeSwitchTrackOn}" /&gt;
    &lt;Setter Property="ThumbColor" Value="{DynamicResource ThemeSwitchThumb}" /&gt;
&lt;/Style&gt;

この時点で「テーマに応じた色の切り替え」はできます。ただし、トラックの形状、つまみの中のアイコン、影、境界線などを揃えて作り込むのは難しく、見た目の上限はあります。

SwitchをC#で定義してXAMLから呼び出す(派生コントロール)

要件にある「SwitchをC#で定義してXAMLから呼びたい」は、Switchを継承したクラスを作るのが一番シンプルです。初期値・共通ルールをコンストラクタに閉じ込められます。

using Microsoft.Maui.Controls;

namespace MyApp.Controls;

public class ThemedSwitch : Switch
{
    public ThemedSwitch()
    {
        // テーマ辞書に追従させる(辞書差し替え時に自動更新させる狙い)
        this.SetDynamicResource(OnColorProperty, "ThemeSwitchTrackOn");
        this.SetDynamicResource(ThumbColorProperty, "ThemeSwitchThumb");

        // 実務でよくやる:操作しやすいサイズに統一
        Scale = 1.6;

        // 任意:テストやUI自動化用
        AutomationId = "ThemedSwitch";
    }
}

XAMLからは通常のコントロールと同様に呼び出せます。

&lt;ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:MyApp.Controls"&gt;

    &lt;local:ThemedSwitch IsToggled="{Binding IsDarkMode}" /&gt;

&lt;/ContentPage&gt;

ここが限界点:標準Switchはプラットフォーム差が大きく、デザイン要求が強いと「OS標準UIの枠内での調整」になりがちです。参考画像のような“デザインされたスイッチ”に寄せるほど、次の「ContentViewで完全自作」が強くなります。

方法2:ContentViewで「完全カスタムのトグルスイッチ」を作る

ここからが本題です。標準Switchを使わず、トラック(背景)・つまみ(丸い部分)・アイコン・アニメーションを自前で作ります。こうすると、プラットフォーム差を抑えながら、参考画像に近いUIを作りやすくなります。

構成要素(どの部品で作るか)

部品MAUIコントロール役割実装のコツ
トラックBoxView(またはBorder)スイッチの背景バー角丸はCornerRadiusで。高さと幅を固定してデザインを安定させる
つまみBorder丸いノブ部分枠線・背景・影をつけて“デザイン感”を出す
アイコンImage太陽/月などを表示画像はResources/Imagesへ、ビルドアクションはMauiImage
レイアウトGridトラックの上にノブを重ねるノブはTranslationXで左右に移動させる
入力TapGestureRecognizerタップでON/OFFタップ領域を広くする(Padding)&連打で壊れない制御を入れる

まずは軽め:Switch + アイコンを横に並べる(ラップ型)

「完全自作の前に、雰囲気だけ出したい」なら、Switchの横にアイコンを付けるラップ型が手軽です。見た目の核は標準Switchに任せつつ、テーマスイッチらしさを演出できます。

using Microsoft.Maui.Controls;

namespace MyApp.Controls;

public class CustomSwitch : ContentView
{
    private readonly Image _switchImage;
    private readonly Switch _switch;

    public CustomSwitch()
    {
        _switch = new Switch { Scale = 1.6 };
        _switch.Toggled += OnCustomSwitchToggled;

        _switchImage = new Image
        {
            Source = "moon.png",
            HeightRequest = 20,
            WidthRequest = 20,
            VerticalOptions = LayoutOptions.Center
        };

        Content = new HorizontalStackLayout
        {
            Spacing = 10,
            Children = { _switchImage, _switch }
        };
    }

    private void OnCustomSwitchToggled(object? sender, ToggledEventArgs e)
    {
        _switchImage.Source = e.Value ? "sun.png" : "moon.png";
    }
}
&lt;local:CustomSwitch /&gt;

この方式は簡単ですが、トラックやノブのデザイン自体は標準Switchのままです。「参考画像のように、ノブ内にアイコンが入って、トラックも自分の色・形で、アニメーションも統一したい」なら次の方式が向いています。

本命:トラック・つまみ・アイコン・アニメーションまで自前(CustomThemeThumbSwitch)

ここでは、以下の仕様を満たすカスタムコントロールを作ります。

  • タップでON/OFF(IsToggledを保持)
  • ONで右へスライド、OFFで左へ戻る(アニメーション)
  • ON/OFFでトラック色・アイコンが変わる(太陽/月など)
  • テーマ辞書を差し替えても色が追従しやすい(DynamicResource運用)
  • XAMLから<local:CustomThemeThumbSwitch />で使える

カスタムコントロール本体(C#)

using Microsoft.Maui.Controls;

namespace MyApp.Controls;

public class CustomThemeThumbSwitch : ContentView
{
    // 外からBindingできるようにする(TwoWay推奨)
    public static readonly BindableProperty IsToggledProperty =
        BindableProperty.Create(
            nameof(IsToggled),
            typeof(bool),
            typeof(CustomThemeThumbSwitch),
            false,
            BindingMode.TwoWay,
            propertyChanged: OnIsToggledChanged);

    public bool IsToggled
    {
        get =&gt; (bool)GetValue(IsToggledProperty);
        set =&gt; SetValue(IsToggledProperty, value);
    }

    // 標準Switch風にToggledイベントも出す(コードビハインドで受けたい人向け)
    public event EventHandler&lt;ToggledEventArgs&gt;? Toggled;

    // 見た目パーツ
    private readonly Grid _root;
    private readonly BoxView _track;
    private readonly Border _thumb;
    private readonly Image _icon;

    // レイアウト確定後に「右端までの移動量」を計算する
    private double _onTranslationX;
    private bool _sizeReady;

    // 連打でアニメーションが壊れないようにガード
    private bool _isAnimating;

    public CustomThemeThumbSwitch()
    {
        // トラック
        _track = new BoxView
        {
            HeightRequest = 32,
            WidthRequest = 64,
            CornerRadius = 16,
            HorizontalOptions = LayoutOptions.Start,
            VerticalOptions = LayoutOptions.Center
        };
        _track.SetDynamicResource(BoxView.ColorProperty, "ThemeSwitchTrackOff");

        // アイコン
        _icon = new Image
        {
            Source = "moon.png",
            HeightRequest = 16,
            WidthRequest = 16,
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.Center
        };

        // つまみ(Borderで枠線や背景を付けやすくする)
        _thumb = new Border
        {
            HeightRequest = 28,
            WidthRequest = 28,
            StrokeThickness = 1,
            Padding = 0,
            Margin = new Thickness(2, 0),
            HorizontalOptions = LayoutOptions.Start,
            VerticalOptions = LayoutOptions.Center,
            Content = _icon
        };
        _thumb.SetDynamicResource(Border.BackgroundColorProperty, "ThemeSwitchThumb");
        _thumb.SetDynamicResource(Border.StrokeProperty, "ThemeSwitchThumbBorder");
        _thumb.StrokeShape = new RoundRectangle { CornerRadius = 14 };

        // ルート(トラックの上にノブを重ねる)
        _root = new Grid
        {
            WidthRequest = 64,
            HeightRequest = 32,
            Padding = new Thickness(0),
            HorizontalOptions = LayoutOptions.Start,
            VerticalOptions = LayoutOptions.Center
        };
        _root.Children.Add(_track);
        _root.Children.Add(_thumb);

        // タップで切り替え
        var tap = new TapGestureRecognizer();
        tap.Tapped += async (s, e) =&gt;
        {
            if (_isAnimating) return;
            IsToggled = !IsToggled; // propertyChangedで見た目更新 &amp; イベント発火
            await Task.CompletedTask;
        };
        _root.GestureRecognizers.Add(tap);

        // アクセシビリティ(読み上げやUIテストで役立つ)
        SemanticProperties.SetDescription(_root, "Theme toggle switch");
        AutomationId = "CustomThemeThumbSwitch";

        Content = _root;

        // サイズ確定後に移動量を計算
        _root.SizeChanged += (_, __) =&gt;
        {
            if (_sizeReady) return;
            if (_root.Width &lt;= 0 || _thumb.Width &lt;= 0) return;

            // 右端までの移動量 = トラック幅 - ノブ幅 - ノブの左右マージン
            var margin = _thumb.Margin.HorizontalThickness;
            _onTranslationX = _root.Width - _thumb.Width - margin;

            _sizeReady = true;
            ApplyVisual(IsToggled, animate: false);
        };

        // 初期反映(サイズ未確定なら翻訳は後で補正される)
        ApplyVisual(IsToggled, animate: false);
    }

    private static void OnIsToggledChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var control = (CustomThemeThumbSwitch)bindable;
        var value = (bool)newValue;

        control.ApplyVisual(value, animate: true);

        // 外側へ通知
        control.Toggled?.Invoke(control, new ToggledEventArgs(value));
    }

    private async void ApplyVisual(bool isOn, bool animate)
    {
        if (_isAnimating) return;
        _isAnimating = true;

        try
        {
            // 色はテーマ辞書のキーを参照(辞書差し替えに強い)
            var trackKey = isOn ? "ThemeSwitchTrackOn" : "ThemeSwitchTrackOff";
            _track.SetDynamicResource(BoxView.ColorProperty, trackKey);

            // アイコン切り替え(画像はResources/Imagesに配置)
            _icon.Source = isOn ? "sun.png" : "moon.png";

            // ノブ位置
            var targetX = isOn ? (_sizeReady ? _onTranslationX : 0) : 0;

            if (!animate)
            {
                _thumb.TranslationX = targetX;
                return;
            }

            // それっぽい気持ちよさを出すならSpring系イージングが相性良い
            await _thumb.TranslateTo(targetX, 0, 140, Easing.SpringOut);
        }
        finally
        {
            _isAnimating = false;
        }
    }
}

この実装の狙い:「色はテーマ辞書で管理」「ON/OFFの見た目はコントロール内部で完結」「外側にはIsToggledとToggledで通知」という分離にすることで、UIの作り込みとアプリのテーマ適用ロジックを混ぜない構成になります。

XAMLから使う(イベントでもBindingでもOK)

イベントで受ける(最短で動かす)

<VerticalStackLayout Spacing="16" Padding="20">


<Label Text="Theme Switch" />

<local:CustomThemeThumbSwitch Toggled="OnThemeToggled" />


 
private void OnThemeToggled(object sender, ToggledEventArgs e)
{
    var isDark = e.Value;

    // 例1:アプリテーマそのものを切り替える(OSテーマとは独立)
    Application.Current!.UserAppTheme = isDark ? AppTheme.Dark : AppTheme.Light;

    // 例2:リソース辞書も差し替える(自作テーマ色を全面反映したい場合)
    ((App)Application.Current).ApplyThemeDictionary(isDark);
}

MVVMでBindingする(実務向け)

&lt;local:CustomThemeThumbSwitch IsToggled="{Binding IsDarkMode}" /&gt;

この場合はViewModel側でIsDarkModeが変わったタイミングでテーマ適用処理を呼ぶ設計が多いです(ページごとのコードビハインドを薄くできます)。

スイッチでテーマ(ライト/ダーク)を切り替える実装パターン

テーマ切り替えには「OS/MAUIのテーマ機構」と「自前のResourceDictionary」をどう扱うかで、実装の流儀が分かれます。ここを決めておくと、後で迷いません。

方式やることメリット注意点
UserAppTheme切り替えApplication.Current.UserAppThemeを変更MAUI標準のテーマ判定に乗れるアプリ独自の色体系を全面反映するならリソース設計が必要
ResourceDictionary差し替えLight/Dark辞書をMergedDictionariesで入れ替え色トークンでアプリ全体を統制できるStaticResource参照だと切替が反映されないことがある
両方併用UserAppTheme + 辞書差し替えOS連動+自作UIの色も完全制御「どちらが正」か設計を決めておく必要あり

ResourceDictionaryを入れ替える(App側に適用メソッドを用意)

Appに「テーマ辞書を適用するメソッド」を用意して、どこからでも呼べるようにすると運用が楽です。

&lt;!-- App.xaml --&gt;
&lt;Application
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:themes="clr-namespace:MyApp.Themes"
    x:Class="MyApp.App"&gt;

    &lt;Application.Resources&gt;
        &lt;ResourceDictionary&gt;
            &lt;ResourceDictionary.MergedDictionaries&gt;
                &lt;!-- 初期はLightを入れておく(起動時に差し替えてもOK) --&gt;
                &lt;themes:LightTheme /&gt;
            &lt;/ResourceDictionary.MergedDictionaries&gt;

            &lt;!-- 例:画面全体のスタイルも色トークンで作る --&gt;
            &lt;Style x:Key="ThemedLabel" TargetType="Label"&gt;
                &lt;Setter Property="TextColor" Value="{DynamicResource ThemeText}" /&gt;
            &lt;/Style&gt;

            &lt;Style x:Key="ThemedPage" TargetType="ContentPage"&gt;
                &lt;Setter Property="BackgroundColor" Value="{DynamicResource ThemeSurface}" /&gt;
            &lt;/Style&gt;
        &lt;/ResourceDictionary&gt;
    &lt;/Application.Resources&gt;
&lt;/Application&gt;
using MyApp.Themes;

namespace MyApp;

public partial class App : Application
{
    private ResourceDictionary? _currentTheme;

    public App()
    {
        InitializeComponent();
        MainPage = new AppShell();

        // 起動時にOSテーマに合わせたい場合(任意)
        var isDark = RequestedTheme == AppTheme.Dark;
        ApplyThemeDictionary(isDark);
    }

    public void ApplyThemeDictionary(bool isDark)
    {
        var dictionaries = Resources.MergedDictionaries;

        if (_currentTheme != null)
            dictionaries.Remove(_currentTheme);

        _currentTheme = isDark ? new DarkTheme() : new LightTheme();
        dictionaries.Add(_currentTheme);
    }
}

運用のコツ:UI側の色参照は{DynamicResource ThemeText}のようにDynamicResourceを基本にします。そうすると、辞書の差し替えだけで画面全体が追従しやすくなります。

ページ側でのテーマ反映例(BoxViewやLabelも一緒に切り替える)

スイッチだけテーマっぽくても、画面が追従しないと一体感が出ません。テーマの効果が分かりやすいように、背景や文字にも色トークンを適用します。

&lt;ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:MyApp.Controls"
    Style="{StaticResource ThemedPage}"&gt;

    &lt;VerticalStackLayout Padding="20" Spacing="16"&gt;

        &lt;Label
            Text="My Own Custom Theme Toggle"
            FontSize="18"
            Style="{StaticResource ThemedLabel}" /&gt;

        &lt;BoxView
            HeightRequest="180"
            WidthRequest="180"
            HorizontalOptions="Center"
            VerticalOptions="Center"
            Color="{DynamicResource ThemeSwitchTrackOn}" /&gt;

        &lt;local:CustomThemeThumbSwitch
            Toggled="OnThemeToggled" /&gt;

    &lt;/VerticalStackLayout&gt;
&lt;/ContentPage&gt;
private void OnThemeToggled(object sender, ToggledEventArgs e)
{
    var isDark = e.Value;

    // アプリテーマを切り替え(必要なら)
    Application.Current!.UserAppTheme = isDark ? AppTheme.Dark : AppTheme.Light;

    // 自作テーマ辞書を差し替え
    ((App)Application.Current).ApplyThemeDictionary(isDark);
}

画像リソースの登録(太陽/月が出ないときの確認ポイント)

アイコンが表示されない原因の多くは、リソース配置・ビルドアクション・ファイル名の取り扱いです。

  • 配置先:Resources/Images配下(例:sun.png, moon.png)
  • ビルドアクション:MauiImage(Visual Studioのプロパティで確認)
  • ファイル名:英小文字中心が無難(特にAndroidは大小文字違いでハマりやすい)
症状よくある原因対処
画像が出ない配置フォルダが違う/ビルドアクションが違うResources/Imagesへ移動し、MauiImageにする
iOSだけ出ないサイズが極端に小さい/透明背景で見えていない一度大きめで確認し、最終サイズを調整
Androidでだけ出ないファイル名の大文字小文字違いSun.pngとsun.pngを混在させない

実務での完成度を上げるポイント

BindablePropertyを増やして「デザインの差し替え」に強くする

上記のCustomThemeThumbSwitchは最小構成ですが、実務では「画面ごとにサイズを変えたい」「アイコンを変えたい」「アニメーション速度を変えたい」が出ます。そこで、以下のようなパラメータをBindablePropertyにしておくと再利用性が跳ね上がります。

プロパティ例型意味おすすめデフォルト
OnIcon / OffIconstring表示する画像sun.png / moon.png
TrackWidth / TrackHeightdoubleトラックサイズ64 / 32
ThumbSizedoubleつまみ直径28
AnimationDurationuintアニメーション時間(ms)140

「要件が固い」段階では固定値でもOKですが、あとから複数画面に展開するなら、プロパティ化しておくと後が圧倒的に楽です。

クリック領域とアクセシビリティ(地味に重要)

デザインを作り込むほど、操作しづらくなる落とし穴があります。最低限、以下は入れておくと安心です。

  • タップ領域を広く:スイッチの見た目は小さくても、Paddingで触れる範囲を確保
  • 読み上げ:SemanticProperties.Descriptionで意味を付与
  • UIテスト:AutomationIdを付与

連打対策(アニメーション破綻を防ぐ)

タップ連打でノブが中途半端な位置に止まる問題は、実務でよく見ます。上の例では_isAnimatingでガードしていますが、さらに確実にするなら次の方針も有効です。

  • アニメーション中は入力無効:InputTransparentやフラグ制御
  • 最終状態を優先:連打されたら最後の状態だけ反映する(キャンセル可能なアニメーション設計)

標準Switchでやるべきか、自作Switchにするべきか(判断基準)

最後に、実務で迷いやすい判断を整理します。

要件おすすめ理由
ネイティブUIの見た目を崩したくない標準Switch拡張プラットフォーム標準の操作感・アクセシビリティが強い
参考画像に寄せたデザインを再現したいContentViewで自作形状・影・アイコン・アニメーションを自由に設計できる
ON/OFFでアプリ全体のテーマも切り替えたい併用(辞書差し替え+必要ならUserAppTheme)色トークンでUI全体を統制しつつ、MAUIのテーマ機構にも乗れる
プラットフォームごとの差を極力なくしたいContentViewで自作標準Switchは差が出やすいが、自作UIは自分の設計通りに統一できる

まとめ:段階的に作ると失敗しにくい

  • 手早く実装するなら、標準SwitchをStyle+VisualStateManagerで拡張し、C#の派生コントロールとしてXAMLから呼び出す。
  • 参考画像のようなUIを狙うなら、ContentViewでトラック・つまみ・アイコン・アニメーションを自作するのが近道。
  • テーマ連動は、色を直書きせず「色トークン(ResourceDictionary)」に寄せ、DynamicResource中心で組むと変更に強い。
  • スイッチのON/OFFをトリガーに、Application.Current.UserAppThemeやテーマ辞書差し替えを行えば、画面全体を一貫して切り替えられる。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次