diff --git a/lib/bean/widget/scrollable_wrapper.dart b/lib/bean/widget/scrollable_wrapper.dart new file mode 100644 index 00000000..926e10ac --- /dev/null +++ b/lib/bean/widget/scrollable_wrapper.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/gestures.dart'; + +/// 滚动容器 +/// 支持鼠标滚轮滚动和拖动滚动 +/// 传入ListView的scrollController +class ScrollableWrapper extends StatelessWidget { + final Widget child; + final ScrollController scrollController; + + const ScrollableWrapper({ + super.key, + required this.child, + required this.scrollController, + }); + + @override + Widget build(BuildContext context) { + return MouseRegion( + child: Listener( + onPointerSignal: (pointerSignal) { + // 鼠标滚轮滚动 + if (pointerSignal is PointerScrollEvent && + scrollController.hasClients) { + scrollController.position.moveTo( + scrollController.offset + pointerSignal.scrollDelta.dy, + curve: Curves.linear, + ); + } + }, + child: GestureDetector( + onPanUpdate: (details) { + // 拖动滚动 + if (scrollController.hasClients) { + scrollController.position.moveTo( + scrollController.offset - details.delta.dx, + curve: Curves.linear, + ); + } + }, + child: child, + ), + ), + ); + } +} diff --git a/lib/pages/popular/popular_page.dart b/lib/pages/popular/popular_page.dart index a3440517..14f19a8e 100644 --- a/lib/pages/popular/popular_page.dart +++ b/lib/pages/popular/popular_page.dart @@ -12,6 +12,7 @@ import 'package:window_manager/window_manager.dart'; import 'package:kazumi/bean/appbar/sys_app_bar.dart'; import 'package:logger/logger.dart'; import 'package:kazumi/utils/logger.dart'; +import 'package:kazumi/bean/widget/scrollable_wrapper.dart'; class PopularPage extends StatefulWidget { const PopularPage({super.key}); @@ -376,74 +377,81 @@ class _PopularPageState extends State '偶像', '治愈', ]; + + final ScrollController tagScrollController = ScrollController(); + return Row( children: [ Expanded( - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: tags.length, - itemBuilder: (context, index) { - final filter = tags[index]; - return Padding( - padding: const EdgeInsets.only(top: 8, bottom: 8, left: 8), - child: filter == popularController.currentTag - ? FilledButton( - child: Text(filter), - onPressed: () async { - scrollController.jumpTo(0.0); - setState(() { - timeout = false; - popularController.currentTag = ''; - searchLoading = true; - }); - await popularController - .queryBangumiListFeed( - tag: popularController.currentTag, - ) - .then((_) { - if (popularController.bangumiList.isEmpty && - mounted) { - setState(() { - timeout = true; - }); - } - }); - setState(() { - searchLoading = false; - }); - }, - ) - : FilledButton.tonal( - child: Text(filter), - onPressed: () async { - _focusNode.unfocus(); - scrollController.jumpTo(0.0); - setState(() { - timeout = false; - popularController.currentTag = filter; - keywordController.text = ''; - showSearchBar = false; - searchLoading = true; - }); - await popularController - .queryBangumiListFeed( - tag: popularController.currentTag, - ) - .then((_) { - if (popularController.bangumiList.isEmpty && - mounted) { - setState(() { - timeout = true; - }); - } - }); - setState(() { - searchLoading = false; - }); - }, - ), - ); - }, + child: ScrollableWrapper( + scrollController: tagScrollController, + child: ListView.builder( + controller: tagScrollController, + scrollDirection: Axis.horizontal, + itemCount: tags.length, + itemBuilder: (context, index) { + final filter = tags[index]; + return Padding( + padding: const EdgeInsets.only(top: 8, bottom: 8, left: 8), + child: filter == popularController.currentTag + ? FilledButton( + child: Text(filter), + onPressed: () async { + scrollController.jumpTo(0.0); + setState(() { + timeout = false; + popularController.currentTag = ''; + searchLoading = true; + }); + await popularController + .queryBangumiListFeed( + tag: popularController.currentTag, + ) + .then((_) { + if (popularController.bangumiList.isEmpty && + mounted) { + setState(() { + timeout = true; + }); + } + }); + setState(() { + searchLoading = false; + }); + }, + ) + : FilledButton.tonal( + child: Text(filter), + onPressed: () async { + _focusNode.unfocus(); + scrollController.jumpTo(0.0); + setState(() { + timeout = false; + popularController.currentTag = filter; + keywordController.text = ''; + showSearchBar = false; + searchLoading = true; + }); + await popularController + .queryBangumiListFeed( + tag: popularController.currentTag, + ) + .then((_) { + if (popularController.bangumiList.isEmpty && + mounted) { + setState(() { + timeout = true; + }); + } + }); + setState(() { + searchLoading = false; + }); + }, + ), + ); + }, + ), ), ), // Tooltip(