読者です 読者をやめる 読者になる 読者になる

がりらぼ

WindowsRuntimeの応援ブログ

【ノンデザイナー向け】ちょっと質感のあるWindowsストアアプリを作ってみよう

WindowsRuntime XAML

前回と前々回でアプリのテーマカラーの一括変更と画像のタイル表示ができるようになりました。

今回はその2つの知識を使って、デザインの知識がない人でもなんかちょっといい感じのユーザーインターフェースを作ってみましょう。

Windowsストアアプリのデフォルトデザイン

Windowsストアアプリは基本的にフラットデザインのなかでも色は単一色で統一し、ビビッド(鮮明)な色を使ったデザインが多いです。

f:id:garicchi:20150107072652p:plain

Windowsストアアプリのデザインの方針がそんな感じなので多くのアプリがビビッドな色を使ったデザインとなっています。

f:id:garicchi:20150107073144p:plain

Color - Windows app development

今回はちょっと変化をつけて、マッドな質感のあるアプリを作ってみましょう。

ノイズテクスチャをつくろう

マッドな質感をだすためには、ノイズテクスチャが必要となります。

Noise Texture Generatorというサイトでノイズテクスチャを作ることができるのでこのサイトを利用しましょう。

Noise Texture Generator v2.1

Noise Opacityはだいたい1/10ぐらいがいい感じになりました。少なめがいいと思います。
Transparent Noise?に必ずチェックをいれましょう。

できたらDownloadNowを押します。

f:id:garicchi:20150107073931p:plain

ダウンロードしたpngファイルはソリューションエクスプローラーからAssetsフォルダを右クリックし、追加→既存の項目で追加しておきましょう。

f:id:garicchi:20150107074811p:plain

ベースカラーを決定しよう

カラーピッカーでテキトーに選択するとだいたい変な色になります(経験談)

というわけで色を選択するときは「色 人気」と検索してみんなに支持されている色を選択しましょう。

今回は人気色ランキングというサイトから色を選択します。

人気色ランキング - Popular Colors

みんなから支持されている色がランキングとして上がるので、僕みたいなデザインセンスのない人でもわりといい色が選べます。

f:id:garicchi:20150107075050p:plain

マッドな質感を出すノイズテクスチャの下のレイヤーにくる色なので、わりと濃いめを選ぶといいと思います。

AttachedThemeクラスとTileImageCanvasクラスを追加しよう

アプリのシステムリソースを一括変更するためにAttachedThemeクラスを、ノイズテクスチャをタイル表示するためにTileImageCanvasを、それぞれプロジェクトに作成しましょう。

AttachedTheme.cs

using System;
using System.Collections.Generic;
using System.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;

namespace Theme
{
    public class ThemeBrush
    {
        public string Name { get; set; }
        public int Level { get; set; }
    }
    public class AttachedTheme
    {
        //Level is light or deep or normal color level
        private static List<ThemeBrush> _systemBrushes = new List<ThemeBrush>()
        {
            new ThemeBrush{Name="ComboBoxItemSelectedBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ComboBoxItemSelectedPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="ComboBoxSelectedBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ComboBoxSelectedPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="HyperlinkForegroundThemeBrush",Level=0},
            new ThemeBrush{Name="HyperlinkPointerOverForegroundThemeBrush",Level=0},
            new ThemeBrush{Name="HyperlinkPressedForegroundBrush",Level=1},
            new ThemeBrush{Name="IMECandidateSelectedBackgroundThemeBrush",Level=2},
            new ThemeBrush{Name="ListBoxItemSelectedBackgroundThemeBrush",Level=2},
            new ThemeBrush{Name="ListBoxItemSelectedPointerOverBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ListViewItemDragBackgroundThemeBrush",Level=2},
            new ThemeBrush{Name="ListViewItemSelectedBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ListViewItemSelectedPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="ListViewItemSelectedPointerOverBorderThemeBrush",Level=0},
            new ThemeBrush{Name="ProgressBarForegroundThemeBrush",Level=1},
            new ThemeBrush{Name="ProgressBarIndeterminateForegroundThemeBrush",Level=0},
            new ThemeBrush{Name="SearchBoxButtonBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="SearchBoxButtonPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="SearchBoxHitHighlightForegroundThemeBrush",Level=1},
            new ThemeBrush{Name="SearchBoxHitHighlightSelectedForegroundThemeBrush",Level=0},
            new ThemeBrush{Name="SliderTrackDecreaseBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="SliderTrackDecreasePointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="SliderTrackDecreasePressedBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="TextSelectionHighlightColorThemeBrush",Level=2},
            new ThemeBrush{Name="ToggleSwitchCurtainBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ToggleSwitchCurtainPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="ToggleSwitchCurtainPressedBackgroundThemeBrush",Level=0},
        };

        public static SolidColorBrush GetThemeBrush(DependencyObject obj)
        {
            return (SolidColorBrush)obj.GetValue(ThemeBrushProperty);
        }

        public static void SetThemeBrush(DependencyObject obj, SolidColorBrush value)
        {
            obj.SetValue(ThemeBrushProperty, value);
        }

        // Using a DependencyProperty as the backing store for ThemeBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ThemeBrushProperty =
            DependencyProperty.RegisterAttached("ThemeBrush", typeof(SolidColorBrush), typeof(AttachedTheme), new PropertyMetadata(null, (s, e) =>
            {
                RewriteSystemResource(GetThemeBrush(s));
            }));



        public static SolidColorBrush GetLightThemeBrush(DependencyObject obj)
        {
            return (SolidColorBrush)obj.GetValue(LightThemeBrushProperty);
        }

        public static void SetLightThemeBrush(DependencyObject obj, SolidColorBrush value)
        {
            obj.SetValue(LightThemeBrushProperty, value);
        }

        // Using a DependencyProperty as the backing store for LightThemeBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LightThemeBrushProperty =
            DependencyProperty.RegisterAttached("LightThemeBrush", typeof(SolidColorBrush), typeof(AttachedTheme), new PropertyMetadata(null, (s, e) =>
            {
                RewriteSystemResource(GetLightThemeBrush(s),0);
            }));



        public static SolidColorBrush GetNormalThemeBrush(DependencyObject obj)
        {
            return (SolidColorBrush)obj.GetValue(NormalThemeBrushProperty);
        }

        public static void SetNormalThemeBrush(DependencyObject obj, SolidColorBrush value)
        {
            obj.SetValue(NormalThemeBrushProperty, value);
        }

        // Using a DependencyProperty as the backing store for NormalTheme.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NormalThemeBrushProperty =
            DependencyProperty.RegisterAttached("NormalThemeBrush", typeof(SolidColorBrush), typeof(AttachedTheme), new PropertyMetadata(null, (s, e) =>
            {
                RewriteSystemResource(GetNormalThemeBrush(s), 1);
            }));




        public static SolidColorBrush GetDeepThemeBrush(DependencyObject obj)
        {
            return (SolidColorBrush)obj.GetValue(DeepThemeBrushProperty);
        }

        public static void SetDeepThemeBrush(DependencyObject obj, SolidColorBrush value)
        {
            obj.SetValue(DeepThemeBrushProperty, value);
        }

        // Using a DependencyProperty as the backing store for DeepThemeBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DeepThemeBrushProperty =
            DependencyProperty.RegisterAttached("DeepThemeBrush", typeof(SolidColorBrush), typeof(AttachedTheme), new PropertyMetadata(null, (s, e) =>
            {
                RewriteSystemResource(GetDeepThemeBrush(s), 2);
            }));



        public static void RewriteSystemResource(SolidColorBrush themeBrush,int level)
        {
            if (themeBrush != null)
            {
                foreach (ThemeBrush tb in _systemBrushes)
                {
                    if (tb.Level == level)
                    {
                        Application.Current.Resources[tb.Name] = themeBrush;
                    }
                }
            }
        }

        public static void RewriteSystemResource(SolidColorBrush themeBrush)
        {
            if (themeBrush != null)
            {
                foreach (ThemeBrush tb in _systemBrushes)
                {
                    SolidColorBrush applyBrush = themeBrush;
                    Application.Current.Resources[tb.Name] = applyBrush;
                }
            }
        }


    }

}

TileImageCanvas.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;

namespace TileImage
{
    public class TileImageCanvas:Canvas
    {


        public Uri TileImageUri
        {
            get { return (Uri)GetValue(TileImageUriProperty); }
            set { SetValue(TileImageUriProperty, value); }
        }

        // Using a DependencyProperty as the backing store for TileImageUri.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TileImageUriProperty =
            DependencyProperty.Register("TileImageUri", typeof(Uri), typeof(TileImageCanvas), new PropertyMetadata(null,(s, e) =>
            {
                TileImageCanvas canvas = s as TileImageCanvas;
                canvas.LoadBitmapImageAsync().ContinueWith(async(param) =>
                {
                    await canvas.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                    {
                        canvas.UpdateTileImage();
                    });
                    
                });
                
            }));


        BitmapSource _source;
        List<Image> _tileImages;
        public TileImageCanvas()
        {
            _tileImages = new List<Image>();
            
            this.SizeChanged += (s, e) =>
            {
                UpdateTileImage();
            };
            
            
        }

        private async Task LoadBitmapImageAsync()
        {
            _source = new BitmapImage();
            StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(TileImageUri);
            IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read);
            await _source.SetSourceAsync(stream);
        }

        private void UpdateTileImage()
        {
            
            if (_source != null && _source.PixelWidth > 0 && _source.PixelHeight > 0)
            {
                int imgWidth = _source.PixelWidth;
                int imgHeight = _source.PixelHeight;

                foreach (Image i in _tileImages)
                {
                    Children.Remove(i);
                }
                _tileImages.Clear();

                for (int x = 0; x < (ActualWidth + imgWidth); x += imgWidth)
                {
                    for (int y = 0; y < (ActualHeight + imgHeight); y+=imgHeight)
                    {
                        Image img = new Image();
                        img.Source = _source;

                        Canvas.SetLeft(img,x);
                        Canvas.SetTop(img,y);

                        Children.Add(img);
                        _tileImages.Add(img);
                    }
                }

            }
        }
    }
}

f:id:garicchi:20150107075522p:plain

XAMLページを編集しよう

ここまでできたらいよいよXAML Pageを編集していきましょう。

AttachedThemeクラスとTileImageCanvasを使うために2つの名前空間をPageタグのxmlnsに追加します。

xmlns:tile="using:TileImage"
xmlns:t="using:Theme"

コントロールの色を決定します。AttachedThemeのThemeBrushプロパティをPageタグの属性に追加すると多くのコントロールの紫の色の部分を好きな色に変更できます。

今回はとりあえず白色にしておきます。

t:AttachedTheme.ThemeBrush="#ffffff"

ここまででPageタグの属性はこのようになります。

f:id:garicchi:20150107080040p:plain

ではPageの背景を作っていきましょう。

Pageタグの子要素にはGridがあると思います。そのGridの子要素として、TileImageBrushを追加しましょう。

<tile:TileImageCanvas Background="#122233" TileImageUri="ms-appx:///assets/noisy-texture-100x100-o8-d12-c-ffffff-t1.png">

Backgroundには先ほど選択した濃いめの色を、TileImageUriには作成、追加したノイズテクスチャのパスを指定します。

assetsフォルダを指定するにはms-appx:///assstes/と指定します。

f:id:garicchi:20150107080350p:plain

こんな感じにXAMLページを作成します。

ではデバッグしてみましょう。

f:id:garicchi:20150107080427p:plain

ノイズテクスチャを配置しただけでマッドな質感が表現できました。

ノイズテクスチャなしと比較すると質感がわかると思います。

f:id:garicchi:20150107080532p:plain

t:AttachedTheme.ThemeBrush=の値を補色にしたり、変えてみるといい感じのデザインになったりもします。

f:id:garicchi:20150107080914p:plain

いろいろ色の組み合わせを変えて素敵なデザインをつくってみましょう!

f:id:garicchi:20150107081406p:plain