Живые плитки Windows Phone

  • Tutorial

Для нетерпеливых — речь пойдет о динамической генерации фоновых изображений для живых плиток, созданию прозрачных плиток для обновления 8.1 и локализации названий приложений.


Более информативная живая плитка


Разрабатывая очередное приложение появилась необходимость отображать достаточно большой объём данных прямо на живой плитке.
Хотя и существует аж три стандартных шаблона, их было не достаточно для реализации некоторых идей. Спросив у Google относительно возможностей гибкого форматирования текста на плитках, оказалось, что на данный момент это не возможно.
Но, в результате поиска решения, довольно часто встречался оригинальный подход других разработчиков — если нельзя выводить форматированный текст на плитке, то этот текст можно показать картинкой. При таком подходе возможно отобразить на плитке что угодно, как угодно с любой сложностью разметки.

Прежде всего нужно знать какие размеры изображений нам нужны. Собственно, размеры в пикселах указаны на сайте для разработчиков. Именно таких размеров фоновые изображения нужно будет генерировать на лету при обновлении данных.

Сам процесс генерации фонового изображения не очень сложный.
Пример кода
using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Example.Common
{
    public static class TileImage
    {
        public static Uri Render(
            String title,
            string row1,
            bool row1Bold,
            bool row1Italic,
            string row2,
            bool row2Bold,
            bool row2Italic)
        {
            var bitmap = new WriteableBitmap(Constants.TileWidth, Constants.TileHeight);
            var canvas = new Grid
            {
                Width = bitmap.PixelWidth,
                Height = bitmap.PixelHeight
            };
            var background = new Canvas
            {
                Height = bitmap.PixelHeight,
                Width = bitmap.PixelWidth,
                Background = new SolidColorBrush(Constants.TileBackgroundColor)
            };

            #region title
            var titleBlock = new TextBlock
            {
                Text = title,
                FontWeight = FontWeights.Bold,
                TextAlignment = TextAlignment.Left,
                VerticalAlignment = VerticalAlignment.Stretch,
                Margin = new Thickness(Constants.TilePadding),
                TextWrapping = TextWrapping.NoWrap,
                Foreground = new SolidColorBrush(Constants.TileForegroundColor),
                FontSize = Constants.TileTitleFontSize,
                Width = bitmap.PixelWidth - Constants.TilePadding * 2
            };
            #endregion

            #region first row
            var firstRowBlock = new TextBlock
            {
                Text = row1,
                TextAlignment = TextAlignment.Left,
                VerticalAlignment = VerticalAlignment.Stretch,
                Margin = new Thickness(Constants.TilePadding, Constants.TilePadding * 2 + Constants.TileTitleFontSize, Constants.TilePadding, Constants.TilePadding),
                TextWrapping = TextWrapping.NoWrap,
                Foreground = new SolidColorBrush(Constants.TileForegroundColor),
                FontSize = Constants.TileTextFontSize,
                Width = bitmap.PixelWidth - Constants.TilePadding * 2
            };
            if (row1Bold)
            {
                firstRowBlock.FontWeight = FontWeights.Bold;
            }
            if (row1Italic)
            {
                firstRowBlock.FontStyle = FontStyles.Italic;
            }
            #endregion

            #region second row
            var secondRowBlock = new TextBlock
            {
                Text = row2,
                TextAlignment = TextAlignment.Left,
                VerticalAlignment = VerticalAlignment.Stretch,
                Margin = new Thickness(Constants.TilePadding, Constants.TilePadding * 3 + Constants.TileTitleFontSize + Constants.TileTextFontSize, Constants.TilePadding, Constants.TilePadding),
                TextWrapping = TextWrapping.Wrap,
                Foreground = new SolidColorBrush(Constants.TileForegroundColor),
                FontSize = Constants.TileTextFontSize,
                Width = bitmap.PixelWidth - Constants.TilePadding * 2
            };
            if (row2Bold)
            {
                secondRowBlock.FontWeight = FontWeights.Bold;
            }
            if (row2Italic)
            {
                secondRowBlock.FontStyle = FontStyles.Italic;
            }
            #endregion

            canvas.Children.Add(titleBlock);
            canvas.Children.Add(firstRowBlock);
            canvas.Children.Add(secondRowBlock);

            bitmap.Render(background, null);
            bitmap.Render(canvas, null);
            bitmap.Invalidate();

            using (var imageStream = new IsolatedStorageFileStream(Constants.TilePath, FileMode.Create, Constants.AppStorage))
            {
                bitmap.SaveJpeg(imageStream,bitmap.PixelWidth,bitmap.PixelHeight,0,90);
            }
            return new Uri("isostore:" + Constants.TilePath, UriKind.Absolute);
        }

    }
}


Проще говоря создается canvas, на который набрасываются произвольные элементы и сохраняется в виде JPG файла в локальном хранилище.
Теперь, не составит труда использовать файл из локального хранилища в качестве фона для плитки.

Из примера видно, что маленькая плитка выглядит без изменений, а вот плитка среднего размера уже использует сгенерированное изображение:


Так же можно сгенерировать изображения и для других размеров плитки. Например, на маленькой можно показать основную информацию сокращенно, на средней чуть больше места и можно добавить больше данных, ну а на широкой можно конечно уже не экономить, но все же располагать всё нужно максимально удобно.
Пример такого подхода можно увидеть на изображении в заголовке статьи.

А теперь прозрачная!


Как многие знают, скоро наши Windows Phone обновятся до новой версии 8.1. Нужно к этому подготовиться.
Одним из новшеств обновления — это возможность добавить фоновое изображение на стартовый экран. Для того чтобы наша плитка соответствовала новым тенденциям и не заслоняла фон рабочего стола, нужно сделать фоновое изображение плитки прозрачным.

Здесь нас ожидает небольшой неприятный сюрприз — изображение можно сохранить только в формате JPG. Коллеги из Microsoft почему то решили что этого будет достаточно. Что ж, придётся изобретать велосипед.

В моей работе я всегда уверен, что с подобными проблемами кто-то сталкивался до меня и, очень часто, я оказываюсь прав. Этот случай не исключение. На просторах необъятного интернета, а точнее на ToolStack, нашлась готовая библиотека PNGWriter, которая позволяет генерировать PNG.

Применение библиотеки крайне простое, она просто добавляет метод WritePNG в WriteableBitmap класс:

var bitmap = new WriteableBitmap(Constants.TileWidth, Constants.TileHeight);
bitmap.WritePNG(imageStream);

Принцип применения остается все тот же — сохраняем изображение в локальном хранилище и используем его как фон для живой плитки. Для версии WP8.0 плитка будет иметь цвет темы, ну а в новой WP8.1 сквозь плитку будет виден фон стартового экрана.

Результат будет очень интересным. Фон будет просматриваться через плитку, также будет проявляться эффект параллакса при прокрутке стартового экрана:


Локализация названия приложения


Для приложений, которые не имеют определенного бренда или легко узнаваемого имени, может понадобиться локализация названия. Логично что «Skype» или «WhatsApp» переводить не нужно, но функциональные названия, например, «фонарик», «камера» стоит локализовать.

Задача по локализации названия приложения может показаться не тривиальной для рядового разработчика. Но инструкция как это сделать написана очень детально и, на самом деле, все значительно проще.

Не хочу повторять весь текст инструкции с сайта Microsoft. Хочу остановиться только на небольших изменениях, которые мне кажется упростят процесс локализации.

Microsoft рекомендует создавать один проект для генерации всех локализированных DLL. Мне показалось немного муторным постоянно переименовывать, копировать созданный DLL файл. Поэтому я создал несколько проектов под каждую локализацию, которую будет поддерживать мое приложение. И в каждом проекте настроил деплоймент сразу в корень проекта Windows Phone.
Для этого был немного изменен путь к папке собранного DLL файла.
Для локали по умолчанию:


Для всех остальных локалей (номер в Target Name соответствует номеру локали):


Теперь можно смело изменять значения в ресурсных таблицах. Каждый раз при сборке Windows Phone проекта сначала будут собираться DLL файлы с локализацией и заменять устаревшие в корне основного проекта. Таким образом мы избегаем манипуляций с переносом DLL файлов в ручную.

Текст названия приложения в списке телефона будет зависеть от локали, указанной в телефоне. В моем примере это будет выглядеть вот так:




Так же стоит заметить, что при публикации приложения в Marketplace появилась возможность указывать альтернативные названия для приложения. Было бы логично добавить туда все локализированные названия.

Заключение


Используя такие нехитрые подходы приложение станет более удобными и интересными.
Будем надеяться что Microsoft продолжит развивать среду разработки и возможности Windows Phone API и в новых версиях нам не придётся изобретать такие велосипеды.

Приятной разработки.

Скачать готовый проект и ознакомиться с кодом можно на GitHub:
https://github.com/vbilenko/WPExample01

Список ссылок:
Инфопульс Украина
Creating Value, Delivering Excellence
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 11

    +1
    В MSP Toolkit есть несколько шаблонов и генератор изображений для тайлов. Они были прозрачными ещё до wp7.8.

    Вообще, прозрачный тайл важен не только для картинки на фоне, но и когда, например, пользователь акцентный цвет меняет, а тайлы у некоторых прог остаются предыдущего цвета.
      0
      Хм… спасибо за наводку. Нужно будет ознакомиться.
      0
      Простите что не по теме, но очень бесит битая кириллица в пуше инстаграма. Никто не знает как до них достучаться, куда написать?
        0
        Воспроизвожу сейчас в WP8.1, но, кажется, в мажорной 8 всё так же было плохо.
          0
          Что, в приложении нет никакого адреса/кнопочки для фидбэка?
            0
            Есть ссылки на портянку лицензионного соглашения и довольно бесполезный хелп-центр: help.instagram.com/
            0
            Используйте 6tag
              +1
              Сейчас будет жуткий оффтоп…

              Уж не знаю насколько будет ползена моя информация, но все же если рассматривать альтернативы, то много хорошего слышал о Lomogram. они даже на хабре пиарились.

              Но у них странная история получилась. Судя по записи на странице фейсбуке, паблишер (который изначально пиарил) их «кинул». Заменил Lomogram на некий Metamorphosa, на который народ стал жаловаться, что «Метамарфоза не торт». В итоге оригинальный разработчик выпустил Lomogram+ с поэтессами и преферансом. Короче целая драмма разыгралась.

              Интересно было бы послушать публичную версию представителей паблишера sashaeve и самого разработчика (уж не знаю, есть ли он на хабре).
                0
                Все же Lomogram это редактор а не клиент инстаграма.
            0
            var canvas = new Grid...
            var background = new Canvas

            Прикольный способ именования ))

            Сюда же — я не понял, а для каких таких целей создавать отдельный background, когда абсолютно то же самое (судя по одинаковой ширине и высоте) можно получить так:

            var canvas = new Grid { Width = bitmap.PixelWidth, Height = bitmap.PixelHeight, Background = new SolidColorBrush(Constants.TileBackgroundColor) }; .

            Тогда отпадет необходимость дважды рендериться и

            bitmap.Render(background, null); bitmap.Render(canvas, null);

            превратится в

            bitmap.Render(canvas, null);
              0
              Видимо так исторически сложилось. В любом коде всегда есть что улучшить )

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое