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.

201 lines
6.9 KiB

using Avalonia.Controls;
1 month ago
using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using ReactiveUI.Avalonia;
1 month ago
using ReactiveUI;
using System.Reactive.Disposables;
using AuroraDesk.Presentation.ViewModels.Pages;
1 month ago
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace AuroraDesk.Presentation.Views.Pages;
/// <summary>
/// 图片浏览页面视图
1 month ago
///
/// 核心功能:
/// - 实现滚动检测,当滚动接近底部时自动加载更多图片
/// - 距底部小于500px时触发加载
/// - 防止重复加载的保护逻辑
/// </summary>
public partial class ImageGalleryPageView : ReactiveUserControl<ImageGalleryPageViewModel>
{
1 month ago
private ScrollViewer? _scrollViewer;
private CancellationTokenSource? _scrollDetectionCancellationToken;
private CancellationTokenSource? _layoutCheckCancellationToken;
private bool _isLoadingMore = false; // 防止重复加载
public ImageGalleryPageView()
{
InitializeComponent();
1 month ago
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);
}
1 month ago
/// <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;
}
}
}