Недавно мне понадобилось быстро сделать простую галерею для просмотра фотографий. Чтоб она сама по себе напоминала стандартную галерею, которая встроена в телефон, с таким же анимированным поведением при переключении картинки. Зум фотографии, как оказалось, был необязателен.
Сразу в голову мне пришла мысль о горизонтальном ListBox’e с невидимым ScrollBar’ом. Быстро реализовав этот подход, я сразу увидел недостаток. При скроллинге ListBox не останавливался на конкретной следующей фотографии, а скроллил дальше. Это было проблемой, но выхода у меня не было и я принялся её решать.
В Silverlight 4 Toolkit есть замечательный класс GestureListener. Это обработчик жестов, который вешается на определённый контрол и даёт разработчику информацию о тех или иных движениях пальцев в виде соответствующих событий. В нашем случае, жест, который применяется при просмотре фотографий, это Flick. Он обрабатывается достаточно легко. Но ещё нужно было учитывать, что пользователь может провести пальцем медленно, фотография поменяется, но фликом это уже не будет.
Идея была такова: отследить начало движения пальца (момента, когда пользователь прикоснулся к экрану, событие DragStarted), конец движения (момента, когда пользователь отпустил палец, событие DragCompleted), взять некую дельту (то значение, на которое пользователь сместил палец в процессе своего движения, суммируется/вычитается в событии DragDelta) и сравнить её с неким магическим числом, которое представляет собой смещение фотографии, при достижении которого, фотография прокрутится на следующую, и сделать что-то наподобие MyListBox.ScrollToHorizontallOffset(deltaOffset), т.е. прокрутить фотографию. Если же пользователь совсем немножко двинул пальцем, то фотография должна остаться той же.
Когда и эта деталь функциональности была реализована, дала знать о себе другая проблема. Переход между фотографиями был очень резкий. Не хватало анимации. Казалось бы, эта проблема должна решаться очень просто: созданием анимации типа DoubleAnimation, вычислением расстояния на основе дельты, насколько нужно сдвигать фотографию, расчёт начальной и конечной координат сдвига, задание времени, в течение которого эта анимация должна прокрутиться, и запуском этой анимации. У ListBox’а должно было меняться свойство HorizontallOffset.
Я так и не смог взять в толк, почему эта анимация проигрывалась так же резко, как и обычно ListBox прокручивается программно через ScrollIntoView(…) или ScrollToHorizontallOffset(…). Здесь я зашёл в тупик. Анимация просто не хотел работать. Ещё больше меня поразил тот факт, что HorizontalOffset оказалось только get-свойством, задать его можно было только через метод ScrollToHorizontallOffset(…). Из этого тупика я выйти так и не смог и решил подумать, как можно по-другому обыграть эту проблему.
И тут меня осенило. А почему бы собственно не использовать для этого такой элемент управления как Pivot? Ведь он просто создан для этого! С виду он представляет собой элемент управления для создания различных табов. Настройки телефона и настройки приложения переключаются между собой именно с помощью этого контрола. Календарь также позволяется переключить режим “day” в режим “agenda”.
Но самое главное, то, что он обладает искомой анимацией при переключении этих самых табов.
Таким образом, моя задача свелась к тому, что просто переопределить ItemTemplate и HeaderTemplate. В первом случае мы создаём DataTemplate с одним лишь элементом – Image, а во втором просто подставляем какую-нибудь заглушку, не ContentControl, чтоб мы не видели заголовка в каждой фотографии (если этого не сделать, то в заголовок будет всегда подставляться ToString() вашего доменного объекта, коллекция из которых служит для источника данных в Pivot’e).
Таким образом, решение этой сложной на первый взгляд задачи представляется в виде нескольких строчек:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="PhotoGalleryDataTemplate">
<Image Source="{Binding Data}"
VerticalAlignment="Center">
</Image>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
<Grid x:Name="LayoutRoot"
Background="Black">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<controls:Pivot ItemTemplate="{StaticResource PhotoGalleryDataTemplate}"
ItemsSource="{Binding Pictures}"
VerticalAlignment="Center">
<controls:Pivot.HeaderTemplate>
<DataTemplate>
<Border/>
</DataTemplate>
</controls:Pivot.HeaderTemplate>
</controls:Pivot>
</Grid>
Сверху в ресурсах страницы лежит наш DataTemplate для одного айтема, а снизу в самой разметке страницы мы видим наш Pivot.
Единственный «недостаток» данного решения: при прокрутке фотографии, как бы там пользователь ни манипулировал пальцами, сначала старая фотография полностью «уезжает» с экрана, и только после этого «приезжает» другая. Вот такое вот стандартное поведение у Pivot’a, и ничего с этим не поделаешь. Эта проблема решается с помощью замены Pivot’a на почти аналогичный ему элемент управления Panorama. И пусть вас не смущает то, что справа всегда виден следующий элемент из списка, в нашем случае фотография. Необходимо всего лишь поиграться со свойством Margin, а именно выставить нужное значение левой границе нашего Image, чтоб его не было видно справа. Но нас вполне устроил и этот вариант, так даже выглядело более оригинально и с изюминкой.
Из этого всего я снова вынес урок о том, что всё гениальное – просто .
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: