feature: slide animation frames

This commit is contained in:
Tsanie Lily 2020-05-14 11:19:31 +08:00
parent 08ad76d8de
commit 0d5a1108ab
3 changed files with 241 additions and 107 deletions

View File

@ -1,9 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<u:AdaptedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mdl="clr-namespace:Pixiview.Illust"
xmlns:u="clr-namespace:Pixiview.UI"
xmlns:util="clr-namespace:Pixiview.Utils"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
x:Class="Pixiview.Illust.ViewIllustPage"
ios:Page.UseSafeArea="False"
@ -13,43 +11,22 @@
<ContentPage.ToolbarItems>
<ToolbarItem Order="Primary" Clicked="Favorite_Clicked"
IconImageSource="{Binding FavoriteIcon}"/>
<ToolbarItem Order="Primary" Clicked="More_Clicked"
IconImageSource="{DynamicResource FontIconMore}"/>
</ContentPage.ToolbarItems>
<Grid Padding="{Binding PageTopMargin}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<CarouselView ItemsSource="{Binding Illusts}" HorizontalScrollBarVisibility="Never"
Position="{Binding CurrentPage}"
ItemTemplate="{StaticResource carouselView}"
IsScrollAnimated="{Binding IsScrollAnimated}"
PositionChanged="CarouselView_PositionChanged">
<CarouselView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="20"/>
</CarouselView.ItemsLayout>
<!--<CarouselView.ItemTemplate>
<DataTemplate x:DataType="mdl:IllustDetailItem">
<Grid>
<Image Source="{Binding Image}"
HorizontalOptions="Fill" VerticalOptions="Fill"
Aspect="AspectFit"
util:LongPressEffect.Command="{Binding LongPressed}"
util:LongPressEffect.CommandParameter="{Binding .}">
<Image.Effects>
<util:LongPressEffect/>
</Image.Effects>
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="Image_Tapped"/>
</Image.GestureRecognizers>
</Image>
<Frame HasShadow="False" Margin="0" Padding="20" CornerRadius="8"
IsVisible="{Binding Loading}"
HorizontalOptions="Center" VerticalOptions="Center"
BackgroundColor="{DynamicResource MaskColor}">
<ActivityIndicator IsRunning="True" IsVisible="True"
Color="{DynamicResource WindowColor}"/>
</Frame>
<ActivityIndicator IsRunning="True" IsVisible="{Binding Downloading}"
Margin="10"
HorizontalOptions="Start" VerticalOptions="Start"
Color="{DynamicResource TextColor}"/>
</Grid>
</DataTemplate>
</CarouselView.ItemTemplate>-->
</CarouselView>
<u:RoundLabel Text="{Binding PagePositionText}"
@ -58,5 +35,13 @@
HorizontalOptions="End" VerticalOptions="Start"
FontSize="Micro" TextColor="White"
IsVisible="{Binding IsPageVisible}"/>
<Slider Grid.Row="1" VerticalOptions="End"
Margin="{DynamicResource ScreenBottomPadding}"
MinimumTrackColor="{DynamicResource TintColor}"
IsEnabled="{Binding IsAnimateSliderEnabled}"
IsVisible="{Binding IsAnimateSliderVisible}"
Value="{Binding CurrentAnimeFrame, Mode=TwoWay}"
Maximum="{Binding MaximumFrame}"/>
</Grid>
</u:AdaptedPage>

View File

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Pixiview.Resources;
using Pixiview.UI;
using Pixiview.UI.Theme;
@ -22,10 +21,51 @@ namespace Pixiview.Illust
nameof(Illusts), typeof(IllustDetailItem[]), typeof(ViewIllustPage));
public static readonly BindableProperty PagePositionTextProperty = BindableProperty.Create(
nameof(PagePositionText), typeof(string), typeof(ViewIllustPage));
public static readonly BindableProperty IsPageVisibleProperty = BindableProperty.Create(
nameof(IsPageVisible), typeof(bool), typeof(ViewIllustPage));
public static readonly BindableProperty IllustItemProperty = BindableProperty.Create(
nameof(IllustItem), typeof(IllustItem), typeof(ViewIllustPage));
public static readonly BindableProperty CurrentPageProperty = BindableProperty.Create(
nameof(CurrentPage), typeof(int), typeof(ViewIllustPage), propertyChanged: OnCurrentPagePropertyChanged);
public static readonly BindableProperty IsScrollAnimatedProperty = BindableProperty.Create(
nameof(IsScrollAnimated), typeof(bool), typeof(ViewIllustPage), true);
public static readonly BindableProperty IsAnimateSliderVisibleProperty = BindableProperty.Create(
nameof(IsAnimateSliderVisible), typeof(bool), typeof(ViewIllustPage));
public static readonly BindableProperty IsAnimateSliderEnabledProperty = BindableProperty.Create(
nameof(IsAnimateSliderEnabled), typeof(bool), typeof(ViewIllustPage));
public static readonly BindableProperty CurrentAnimeFrameProperty = BindableProperty.Create(
nameof(CurrentAnimeFrame), typeof(double), typeof(ViewIllustPage), propertyChanged: OnCurrentAnimeFramePropertyChanged);
public static readonly BindableProperty MaximumFrameProperty = BindableProperty.Create(
nameof(MaximumFrame), typeof(double), typeof(ViewIllustPage), 1.0);
private static void OnCurrentPagePropertyChanged(BindableObject obj, object old, object @new)
{
var page = (ViewIllustPage)obj;
var index = (int)@new;
var items = page.Illusts;
var length = items.Length;
page.PagePositionText = $"{index + 1}/{length}";
var item = items[index];
if (!item.Loading && item.Image == null)
{
Task.Run(() => page.DoLoadImage(index));
}
if (index < length - 1)
{
item = items[index + 1];
if (!item.Loading && item.Image == null)
{
Task.Run(() => page.DoLoadImage(index + 1));
}
}
}
private static void OnCurrentAnimeFramePropertyChanged(BindableObject obj, object old, object @new)
{
var page = (ViewIllustPage)obj;
if (page.ugoira != null && page.IsAnimateSliderEnabled)
{
var frame = (double)@new;
page.ugoira.ToggleFrame((int)frame);
}
}
public ImageSource FavoriteIcon
{
@ -43,21 +83,41 @@ namespace Pixiview.Illust
get => (string)GetValue(PagePositionTextProperty);
set => SetValue(PagePositionTextProperty, value);
}
public bool IsPageVisible
public int CurrentPage
{
get => (bool)GetValue(IsPageVisibleProperty);
set => SetValue(IsPageVisibleProperty, value);
get => (int)GetValue(CurrentPageProperty);
private set => SetValue(CurrentPageProperty, value);
}
public IllustItem IllustItem
public bool IsScrollAnimated
{
get => (IllustItem)GetValue(IllustItemProperty);
set => SetValue(IllustItemProperty, value);
get => (bool)GetValue(IsScrollAnimatedProperty);
set => SetValue(IsScrollAnimatedProperty, value);
}
public bool IsAnimateSliderVisible
{
get => (bool)GetValue(IsAnimateSliderVisibleProperty);
set => SetValue(IsAnimateSliderVisibleProperty, value);
}
public bool IsAnimateSliderEnabled
{
get => (bool)GetValue(IsAnimateSliderEnabledProperty);
set => SetValue(IsAnimateSliderEnabledProperty, value);
}
public double CurrentAnimeFrame
{
get => (double)GetValue(CurrentAnimeFrameProperty);
set => SetValue(CurrentAnimeFrameProperty, value);
}
public double MaximumFrame
{
get => (double)GetValue(MaximumFrameProperty);
set => SetValue(MaximumFrameProperty, value);
}
public int CurrentPage { get; private set; }
public IllustItem IllustItem { get; private set; }
public bool IsPageVisible { get; private set; }
private readonly bool saveFavorites;
private readonly ICommand longPressed;
private readonly ImageSource fontIconLove;
private readonly ImageSource fontIconNotLove;
private IllustUgoiraData ugoiraData;
@ -67,7 +127,6 @@ namespace Pixiview.Illust
{
IllustItem = illust;
saveFavorites = save;
longPressed = new Command<IllustDetailItem>(Illust_LongPressed);
BindingContext = this;
fontIconLove = (ImageSource)Application.Current.Resources[ThemeBase.FontIconLove];
@ -77,7 +136,9 @@ namespace Pixiview.Illust
? fontIconLove
: fontIconNotLove;
Resources.Add("carouselView", GetCarouseTemplate());
var pageVisible = illust != null && illust.PageCount > 1;
IsPageVisible = pageVisible;
Resources.Add("carouselView", GetCarouseTemplate(pageVisible));
InitializeComponent();
@ -115,65 +176,111 @@ namespace Pixiview.Illust
Screen.SetHomeIndicatorAutoHidden(Shell.Current, false);
}
private DataTemplate GetCarouseTemplate()
private DataTemplate GetCarouseTemplate(bool multiPages)
{
var isAnime = IllustItem.IllustType == IllustType.Anime;
var tap = new TapGestureRecognizer();
tap.Tapped += Image_Tapped;
return new DataTemplate(() =>
{
// image
var image = new Image
{
HorizontalOptions = LayoutOptions.Fill,
VerticalOptions = LayoutOptions.Fill,
Aspect = Aspect.AspectFit
Aspect = Aspect.AspectFit,
GestureRecognizers = { tap }
}
.Binding(Image.SourceProperty, nameof(IllustDetailItem.Image));
if (isAnime)
// downloading
var downloading = new Frame
{
image.GestureRecognizers.Add(tap);
HasShadow = false,
Margin = default,
Padding = new Thickness(20),
CornerRadius = 8,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Content = new ActivityIndicator
{
IsRunning = true,
IsVisible = true
}
.DynamicResource(ActivityIndicator.ColorProperty, ThemeBase.WindowColor)
}
else
.Binding(IsVisibleProperty, nameof(IllustDetailItem.Loading))
.DynamicResource(BackgroundColorProperty, ThemeBase.MaskColor);
// loading original
var original = new ActivityIndicator
{
image.SetBinding(LongPressEffect.CommandProperty, nameof(IllustDetailItem.LongPressed));
image.SetBinding(LongPressEffect.CommandParameterProperty, ".");
image.Effects.Add(new LongPressEffect());
IsRunning = true,
Margin = new Thickness(10),
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start
}
.Binding(IsVisibleProperty, nameof(IllustDetailItem.Downloading))
.DynamicResource(ActivityIndicator.ColorProperty, ThemeBase.TextColor);
if (multiPages)
{
var tapPrevious = new TapGestureRecognizer();
tapPrevious.Tapped += TapPrevious_Tapped;
var tapNext = new TapGestureRecognizer();
tapNext.Tapped += TapNext_Tapped;
return new Grid
{
Children =
{
// image
image,
// tap holder
new Grid
{
RowDefinitions =
{
new RowDefinition(),
new RowDefinition(),
new RowDefinition()
},
Children =
{
new Label
{
GestureRecognizers = { tapPrevious }
},
new Label
{
GestureRecognizers = { tapNext }
}
.GridRow(2)
}
},
// downloading
downloading,
// loading original
original
}
};
}
return new Grid
{
Children =
{
// image
image,
new Frame
{
HasShadow = false,
Margin = default,
Padding = new Thickness(20),
CornerRadius = 8,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Content = new ActivityIndicator
{
IsRunning = true,
IsVisible = true
}
.DynamicResource(ActivityIndicator.ColorProperty, ThemeBase.WindowColor)
}
.Binding(IsVisibleProperty, nameof(IllustDetailItem.Loading))
.DynamicResource(BackgroundColorProperty, ThemeBase.MaskColor),
// downloading
downloading,
new ActivityIndicator
{
IsRunning = true,
Margin = new Thickness(10),
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start
}
.Binding(IsVisibleProperty, nameof(IllustDetailItem.Downloading))
.DynamicResource(ActivityIndicator.ColorProperty, ThemeBase.TextColor)
// loading original
original
}
};
});
@ -189,20 +296,14 @@ namespace Pixiview.Illust
var items = new IllustDetailItem[illust.PageCount];
if (items.Length > 1)
{
IsPageVisible = true;
PagePositionText = $"1/{items.Length}";
}
else
{
IsPageVisible = false;
}
for (var i = 0; i < items.Length; i++)
{
items[i] = new IllustDetailItem
{
Id = illust.Id,
LongPressed = longPressed
Id = illust.Id
};
if (i == 0)
{
@ -234,8 +335,7 @@ namespace Pixiview.Illust
{
tmp[i] = new IllustDetailItem
{
Id = illustItem.Id,
LongPressed = longPressed
Id = illustItem.Id
};
}
Illusts = items = tmp;
@ -277,6 +377,12 @@ namespace Pixiview.Illust
{
// anime
ugoiraData = Stores.LoadIllustUgoiraData(illustItem.Id);
if (ugoiraData != null && ugoiraData.body != null)
{
var length = ugoiraData.body.frames.Length;
MaximumFrame = length > 0 ? length : 1;
IsAnimateSliderVisible = true;
}
}
}
@ -309,24 +415,29 @@ namespace Pixiview.Illust
private void CarouselView_PositionChanged(object sender, PositionChangedEventArgs e)
{
var index = e.CurrentPosition;
CurrentPage = index;
var items = Illusts;
var length = items.Length;
PagePositionText = $"{index + 1}/{length}";
CurrentPage = e.CurrentPosition;
}
var item = items[index];
if (!item.Loading && item.Image == null)
private void TapPrevious_Tapped(object sender, EventArgs e)
{
var index = CurrentPage;
if (index > 0)
{
Task.Run(() => DoLoadImage(index));
IsScrollAnimated = false;
CurrentPage = index - 1;
IsScrollAnimated = true;
}
if (index < length - 1)
}
private void TapNext_Tapped(object sender, EventArgs e)
{
var index = CurrentPage;
var illusts = Illusts;
if (illusts != null && index < illusts.Length - 1)
{
item = items[index + 1];
if (!item.Loading && item.Image == null)
{
Task.Run(() => DoLoadImage(index + 1));
}
IsScrollAnimated = false;
CurrentPage = index + 1;
IsScrollAnimated = true;
}
}
@ -360,6 +471,7 @@ namespace Pixiview.Illust
if (ugoira != null)
{
var playing = !ugoira.IsPlaying;
IsAnimateSliderEnabled = !playing;
ugoira.TogglePlay(playing);
illustItem.IsPlaying = playing;
}
@ -381,10 +493,20 @@ namespace Pixiview.Illust
private void OnUgoiraFrameChanged(object sender, UgoiraEventArgs e)
{
e.DetailItem.Image = e.Image;
CurrentAnimeFrame = e.FrameIndex;
}
private async void Illust_LongPressed(IllustDetailItem item)
private async void More_Clicked(object sender, EventArgs e)
{
int p = CurrentPage;
var illusts = Illusts;
if (illusts == null || p < 0 || p >= illusts.Length)
{
return;
}
var item = illusts[p];
List<string> extras = new List<string>();
var share = ResourceHelper.Share;
var preview = Stores.GetPreviewImagePath(item.PreviewUrl);
@ -496,7 +618,6 @@ namespace Pixiview.Illust
set => SetValue(DownloadingProperty, value);
}
public string Id { get; set; }
public ICommand LongPressed { get; set; }
public string PreviewUrl { get; set; }
public string OriginalUrl { get; set; }
}

View File

@ -302,6 +302,7 @@ namespace Pixiview.Utils
private int index = 0;
public bool IsPlaying { get; private set; }
public readonly int FrameCount;
public event EventHandler<UgoiraEventArgs> FrameChanged;
public Ugoira(IllustUgoiraData illust, IllustDetailItem item)
@ -309,6 +310,7 @@ namespace Pixiview.Utils
ugoira = illust.body;
detailItem = item;
frames = new ImageSource[ugoira.frames.Length];
FrameCount = frames.Length;
timer = new Timer(OnTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
Task.Run(LoadFrames);
@ -332,6 +334,30 @@ namespace Pixiview.Utils
}
}
public void ToggleFrame(int frame)
{
if (IsPlaying)
{
// TODO: doesn't support change current frame when playing
return;
}
if (frame < 0 || frame >= frames.Length)
{
return;
}
var image = frames[frame];
if (image != null)
{
index = frame;
FrameChanged?.Invoke(this, new UgoiraEventArgs
{
DetailItem = detailItem,
Image = image,
FrameIndex = frame
});
}
}
private void OnTimerCallback(object state)
{
if (!IsPlaying)
@ -350,7 +376,8 @@ namespace Pixiview.Utils
FrameChanged?.Invoke(this, new UgoiraEventArgs
{
DetailItem = detailItem,
Image = frame
Image = frame,
FrameIndex = i
});
i++;
if (i >= frames.Length)
@ -498,5 +525,6 @@ namespace Pixiview.Utils
{
public IllustDetailItem DetailItem { get; set; }
public ImageSource Image { get; set; }
public int FrameIndex { get; set; }
}
}