You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter:点击按钮实现ListView自动滚到底部并返回顶部

问题描述

在包含ListView的主视图中调用了由GestureDetector包裹的Container,点击Container可显示/隐藏带滑入滑出动画的网页。当前存在体验问题:

  • 显示网页后,用户需手动滚动ListView才能查看完整网页
  • 隐藏网页后,用户需手动滚动回顶部

需求:点击显示网页时ListView自动滚动到底部,点击隐藏时自动滚动回顶部。

解决方案

核心思路是让子组件(SlidingWebViewWidget)通知主视图状态变化,再由主视图控制ListView的ScrollController执行滚动操作。具体修改如下:

1. 修正主视图的ScrollController生命周期

将ScrollController从build方法内移到State类成员变量,避免每次重建控制器导致失效:

class ADJParity_Screen extends StatefulWidget {
  const ADJParity_Screen({super.key});

  @override
  State<ADJParity_Screen> createState() => _ADJParity_ScreenState();
}

class _ADJParity_ScreenState extends State<ADJParity_Screen> {
  late ScrollController _scrollController;

  @override
  void initState() {
    super.initState();
    _scrollController = ScrollController();
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Main View'),
        titleTextStyle:
            const TextStyle(color: Colors.deepOrangeAccent, fontSize: 23),
      ),
      body: ListView(
        controller: _scrollController,
        children: <Widget>[
          const Center(
            child: Image(
              image: AssetImage('images/aaaBR.png'),
              height: 175,
              width: 175,
            ),
          ),
          const SizedBox(height: 10),
          Container(
            height: 80,
            width: 380,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(10.0),
              color: Colors.green.shade600,
            ),
            child: const Center(
              child: Padding(
                padding: EdgeInsets.only(left: 8.0),
                child: Text(
                  "This is text",
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
          ),
          const SizedBox(height: 40),
          SlidingWebViewWidget(
            url: 'https://url-link-here-example',
            onVisibilityChanged: (isVisible) {
              if (isVisible) {
                _scrollController.animateTo(
                  _scrollController.position.maxScrollExtent,
                  duration: const Duration(milliseconds: 300),
                  curve: Curves.easeOut,
                );
              } else {
                _scrollController.animateTo(
                  0,
                  duration: const Duration(milliseconds: 300),
                  curve: Curves.easeOut,
                );
              }
            },
          ),
        ],
      ),
    );
  }
}

2. 给SlidingWebViewWidget添加状态回调

修改子组件,添加回调参数,在切换网页可见性时通知主视图:

class SlidingWebViewWidget extends StatefulWidget {
  final String url;
  final ValueChanged<bool>? onVisibilityChanged;

  const SlidingWebViewWidget({
    super.key,
    required this.url,
    this.onVisibilityChanged,
  });

  @override
  _SlidingWebViewWidgetState createState() => _SlidingWebViewWidgetState();
}

class _SlidingWebViewWidgetState extends State<SlidingWebViewWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<Offset> _slideAnimation;
  bool _isVisible = false;

  final WebViewController _controller = WebViewController();

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );

    _slideAnimation = Tween<Offset>(
      begin: const Offset(1, 0),
      end: const Offset(0, 0),
    ).animate(CurvedAnimation(
      parent: _animationController,
      curve: Curves.easeInOut,
    ));

    _controller
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse(widget.url));
  }

  void toggleVisibility() {
    setState(() {
      _isVisible = !_isVisible;
      _isVisible
          ? _animationController.forward()
          : _animationController.reverse();
      widget.onVisibilityChanged?.call(_isVisible);
    });
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    double screenHeight = MediaQuery.of(context).size.height;
    return Column(
      children: [
        GestureDetector(
          onTap: toggleVisibility,
          child: Container(
            height: 60,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(10.0),
              color: Colors.orangeAccent,
            ),
            child: const Center(
              child: Text(
                "Visualize",
                style: TextStyle(color: Colors.black, fontSize: 25),
              ),
            ),
          ),
        ),
        SlideTransition(
          position: _slideAnimation,
          child: Container(
            height: screenHeight / 2,
            color: Colors.black38,
            child: WebPage(url: widget.url),
          ),
        ),
      ],
    );
  }
}

关键说明

  • ScrollController作为成员变量初始化,确保生命周期与组件一致,避免控制器失效
  • 通过ValueChanged<bool>回调实现子父组件状态通信,让主视图控制ListView滚动
  • 滚动动画时长与网页滑入/滑出动画保持一致(300ms),保证体验连贯

内容的提问来源于stack exchange,提问作者Spicy Pumpkin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 03:24:55