You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
6.9 KiB
200 lines
6.9 KiB
using Avalonia.Controls;
|
|
using Avalonia.Controls.Primitives;
|
|
using Avalonia.LogicalTree;
|
|
using Avalonia.Markup.Xaml;
|
|
using ReactiveUI.Avalonia;
|
|
using ReactiveUI;
|
|
using System.Reactive.Disposables;
|
|
using AuroraDesk.Presentation.ViewModels.Pages;
|
|
using System;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Avalonia.Threading;
|
|
using Avalonia.VisualTree;
|
|
|
|
namespace AuroraDesk.Presentation.Views.Pages;
|
|
|
|
/// <summary>
|
|
/// 图片浏览页面视图
|
|
///
|
|
/// 核心功能:
|
|
/// - 实现滚动检测,当滚动接近底部时自动加载更多图片
|
|
/// - 距底部小于500px时触发加载
|
|
/// - 防止重复加载的保护逻辑
|
|
/// </summary>
|
|
public partial class ImageGalleryPageView : ReactiveUserControl<ImageGalleryPageViewModel>
|
|
{
|
|
private ScrollViewer? _scrollViewer;
|
|
private CancellationTokenSource? _scrollDetectionCancellationToken;
|
|
private CancellationTokenSource? _layoutCheckCancellationToken;
|
|
private bool _isLoadingMore = false; // 防止重复加载
|
|
|
|
public ImageGalleryPageView()
|
|
{
|
|
InitializeComponent();
|
|
this.WhenActivated(disposables =>
|
|
{
|
|
// 当视图激活时,设置滚动检测
|
|
Dispatcher.UIThread.Post(() => SetupScrollDetection(), DispatcherPriority.Loaded);
|
|
|
|
// 当视图停用时,清理资源
|
|
disposables.Add(Disposable.Create(() =>
|
|
{
|
|
_scrollDetectionCancellationToken?.Cancel();
|
|
_scrollDetectionCancellationToken?.Dispose();
|
|
_layoutCheckCancellationToken?.Cancel();
|
|
_layoutCheckCancellationToken?.Dispose();
|
|
}));
|
|
});
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
AvaloniaXamlLoader.Load(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置滚动检测
|
|
/// 监听 ScrollViewer 的滚动事件,当接近底部时加载更多
|
|
/// </summary>
|
|
private void SetupScrollDetection()
|
|
{
|
|
// 查找 ScrollViewer
|
|
_scrollViewer = this.FindControl<ScrollViewer>("MainScrollViewer");
|
|
if (_scrollViewer == null)
|
|
{
|
|
_scrollViewer = this.GetLogicalDescendants().OfType<ScrollViewer>().FirstOrDefault();
|
|
}
|
|
|
|
if (_scrollViewer != null)
|
|
{
|
|
// 绑定滚动事件
|
|
_scrollViewer.ScrollChanged += OnScrollChanged;
|
|
|
|
// 监听布局完成事件,确保在布局完成后检查是否需要加载更多
|
|
_scrollViewer.LayoutUpdated += OnLayoutUpdated;
|
|
|
|
// 延迟检查一次,确保在初始加载完成后能自动加载更多
|
|
_ = Task.Run(async () =>
|
|
{
|
|
await Task.Delay(800);
|
|
await Dispatcher.UIThread.InvokeAsync(async () =>
|
|
{
|
|
if (!_isLoadingMore && ViewModel != null)
|
|
{
|
|
await CheckAndLoadMoreAsync();
|
|
}
|
|
}, DispatcherPriority.Background);
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 布局更新时检查是否需要加载更多
|
|
/// </summary>
|
|
private void OnLayoutUpdated(object? sender, EventArgs e)
|
|
{
|
|
// 只在布局稳定后检查一次,避免频繁触发
|
|
if (!_isLoadingMore && ViewModel != null && _scrollViewer != null)
|
|
{
|
|
// 防抖:取消之前的布局检查任务
|
|
_layoutCheckCancellationToken?.Cancel();
|
|
_layoutCheckCancellationToken?.Dispose();
|
|
_layoutCheckCancellationToken = new CancellationTokenSource();
|
|
|
|
var token = _layoutCheckCancellationToken.Token;
|
|
|
|
// 检查是否需要加载更多(延迟检查,避免在布局过程中频繁触发)
|
|
_ = Task.Run(async () =>
|
|
{
|
|
await Task.Delay(200, token);
|
|
if (token.IsCancellationRequested)
|
|
return;
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(async () =>
|
|
{
|
|
if (!token.IsCancellationRequested && !_isLoadingMore && ViewModel != null)
|
|
{
|
|
await CheckAndLoadMoreAsync();
|
|
}
|
|
}, DispatcherPriority.Background);
|
|
}, token);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 滚动时检查是否需要加载更多
|
|
/// </summary>
|
|
private void OnScrollChanged(object? sender, ScrollChangedEventArgs e)
|
|
{
|
|
// 防抖:取消之前的检测任务
|
|
_scrollDetectionCancellationToken?.Cancel();
|
|
_scrollDetectionCancellationToken?.Dispose();
|
|
_scrollDetectionCancellationToken = new CancellationTokenSource();
|
|
|
|
var token = _scrollDetectionCancellationToken.Token;
|
|
|
|
// 延迟200ms检测,避免频繁触发
|
|
_ = Task.Delay(200, token).ContinueWith(async _ =>
|
|
{
|
|
if (!token.IsCancellationRequested && ViewModel != null && _scrollViewer != null)
|
|
{
|
|
await CheckAndLoadMoreAsync();
|
|
}
|
|
}, token);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查是否需要加载更多图片
|
|
/// 当滚动位置距底部小于500px时,触发加载
|
|
/// </summary>
|
|
private async Task CheckAndLoadMoreAsync()
|
|
{
|
|
if (ViewModel == null || _scrollViewer == null || _isLoadingMore)
|
|
return;
|
|
|
|
try
|
|
{
|
|
// 如果正在加载初始数据,不触发加载更多
|
|
if (ViewModel.IsLoading)
|
|
return;
|
|
|
|
// 获取滚动信息
|
|
var scrollOffset = _scrollViewer.Offset.Y;
|
|
var viewportHeight = _scrollViewer.Viewport.Height;
|
|
var extentHeight = _scrollViewer.Extent.Height;
|
|
|
|
if (extentHeight <= 0 || viewportHeight <= 0)
|
|
return;
|
|
|
|
// 计算距离底部的距离
|
|
var maxScrollOffset = extentHeight - viewportHeight;
|
|
var distanceToBottom = maxScrollOffset - scrollOffset;
|
|
|
|
// 如果距离底部小于500px,且还有更多内容未加载,则触发加载
|
|
if (distanceToBottom < 500 && ViewModel.Images.Count < ViewModel.TotalImageCount)
|
|
{
|
|
_isLoadingMore = true;
|
|
|
|
try
|
|
{
|
|
// 调用 ViewModel 加载更多
|
|
await ViewModel.LoadMoreImagesAsync();
|
|
}
|
|
finally
|
|
{
|
|
// 延迟重置标志,避免短时间内重复触发
|
|
await Task.Delay(300);
|
|
_isLoadingMore = false;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// 忽略错误,避免影响UI
|
|
System.Diagnostics.Debug.WriteLine($"检查加载更多时出错: {ex.Message}");
|
|
_isLoadingMore = false;
|
|
}
|
|
}
|
|
}
|
|
|