如何在Flutter移动应用中实现重叠滚动和手势区域

5anewei6  于 2023-02-05  发布在  Flutter
关注(0)|答案(2)|浏览(210)

我正在开发一个移动应用程序,其中包括一个页面上的数据图表。我希望能够根据用户输入滚动图表的部分,但不确定如何实现这一点。我是Flutter的新手,还在学习很多东西,所以我正在寻找一个设计指导,即使用什么组件以及如何构建这些组件来达到我想要的效果。
我目前使用ScrollerViewScrollControllers滚动整个页面,但这意味着顶行(标题和轴)加上左列(行标签)滚动到页面之外,还意味着用户必须使用滚动条滚动应用程序,而不是在屏幕上的任何地方使用手势。
我想实现一个解决方案,用户可以向上/向下滚动,但标题和轴保持不变,或者可以向左/向右滚动,但标题和行标签保持不变。

什么是最好的组件来实现这个效果?我想我可能必须实现RawGestureDetectorsCustomScrollViews,但不知道是否有一些更简单的开箱即用的组件来实现这个效果?我很乐意阅读/了解任何建议,以及如何实现这些自己,但是,有人能指导我在哪些开箱即用组件可能最适合做这件事以及这些组件可能需要如何构造(即,父和子关系)方面的方向吗?
我在stackoverflow上做了一个搜索,没有找到任何专门涉及这个问题的内容,除了这里有一个未回答的问题:如果有其他问题的答案或文章,有人可以指出我,那么我很高兴阅读这些太。

00jrzges

00jrzges1#

你要找的文件还没有找到,但是你可以看到two-dimensional scrolling here的预览版。
现在,我将使用带有SliverAppBar的CustomScrollView来实现黄色框。
您也可以尝试一下DataTable小部件。

unftdfkk

unftdfkk2#

我已经设法使用GestureDetectorAnimatedBuilder小部件实现了我想要的效果,需要做一些整理来设置用户可以滑动内容的距离,但是我已经设法使顶行轴和图表内容同步左右滑动,左侧标签和图表内容同步上下滑动。
我使用了带颜色的DecoratedBox来遮盖屏幕上我想隐藏滚动文本的区域,使用Stack来确保组件以正确的顺序绘制,以便滚动文本在遮罩下移动。我没有为应用程序的边缘而烦恼,因为我的图表在移动应用程序中是全屏显示的。然而,如果你想在屏幕的一个子区域使用这个效果,那么你需要在屏幕的其余部分实现屏蔽,这样文本就不会显示出来。
如果有人知道一个更好的方法来做到这一点,例如,只画屏幕上的区域,它是可见的用户,那么请张贴一个例子,因为这可能是一个更好的方法来做的事情。
如果有人想知道我是如何做到这一点的,下面是我的代码:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  double _left = 0.0;
  double _top = 0.0;
  late Offset _leftOnly = Offset(_left, 0.0);
  late Offset _leftTop = Offset(_left, _top);
  late Offset _topOnly = Offset(0.0, _top);

  void _setLeft(DragUpdateDetails details) {
    setState(() {
      _left += details.primaryDelta!;
      _leftTop = Offset(_left, _top);
      _leftOnly = Offset(_left, 0);
    });
  }

  void _setTop(DragUpdateDetails details) {
    setState(() {
      _top += details.primaryDelta!;
      _leftTop = Offset(_left, _top);
      _topOnly = Offset(0.0, _top);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.only(top: 200, left: 200),
          child: Column(
              // crossAxisAlignment: CrossAxisAlignment.center,
              // mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Stack(children: [
                  Row(children: const [
                    //
                    // Top-left box blank to push bottom row into correct space
                    SizedBox(
                      height: 60,
                      width: 100,
                    ),
                    //
                    // Top-right box blank to push bottom row into correct space
                    SizedBox(
                      height: 60,
                      width: 400,
                    ),
                  ]),
                  //
                  // Draw bottom row of boxes
                  //
                  Row(children: [
                    //
                    // Bottom-left box blank to push right-hand box into correct space
                    //
                    const SizedBox(
                      height: 200,
                      width: 100,
                    ),
                    //
                    // Bottom-right box with chart content
                    //
                    GestureDetector(
                      onHorizontalDragUpdate: _setLeft,
                      onVerticalDragUpdate: _setTop,
                      child: ConstrainedBox(
                        key: _bottomRight,
                        constraints:
                          const BoxConstraints(maxHeight: 200, maxWidth: 500),
                        child: DecoratedBox(
                          decoration: BoxDecoration(
                            border: Border.all(),
                          ),
                          child: OverflowBox(
                            maxHeight: double.infinity,
                            maxWidth: double.infinity,
                            alignment: Alignment.topLeft,
                            child: Slide(
                              offset: _leftTop,
                              child: Column(
                                children: const [
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                  Text(
                                      'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
                                ],
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                  ]),
                  //
                  // Bottom-left box drawn with content and mask for scrolling text
                  //
                  GestureDetector(
                    onVerticalDragUpdate: _setTop,
                    child: SizedBox(
                      height: 200,
                      width: 100,
                      child: DecoratedBox(
                        decoration: BoxDecoration(
                          border: Border.all(),
                          color: Colors.white,
                        ),
                        child: OverflowBox(
                          alignment: Alignment.topLeft,
                          maxHeight: double.infinity,
                          child: Slide(
                            offset: _topOnly,
                            child: Column(
                              children: const [
                                Text('Label'),
                                Text('Label'),
                                Text('Label'),
                                Text('Label'),
                                Text('Label'),
                                Text('Label'),
                                Text('Label'),
                                Text('Label'),
                                Text('Label'),
                                Text('Label'),
                                Text('Label'),
                                Text('Label'),
                                Text('Label'),
                                Text('Label'),
                              ],
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                  //
                  // Draw top row on boxes with masks to cover scrolling text
                  Row(children: [
                    //
                    // Top-left box blank to draw right hand axis box first
                    //
                    const SizedBox(
                      height: 60,
                      width: 100,
                    ),
                    //
                    // Top-right box draw axis with mask
                    //
                    GestureDetector(
                      onHorizontalDragUpdate: _setLeft,
                      child: SizedBox(
                        height: 60,
                        width: 400,
                        child: DecoratedBox(
                          decoration: BoxDecoration(
                            border: Border.all(),
                            color: Colors.white,
                          ),
                          child: OverflowBox(
                            maxWidth: double.infinity,
                            alignment: Alignment.centerLeft,
                            child: Slide(
                              offset: _leftOnly,
                              child: const Text(
                                  'axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--'),
                            ),
                          ),
                        ),
                      ),
                    ),
                  ]),
                  //
                  // Mask to hide text of axis on left
                  SizedBox(
                    height: 60,
                    width: 100,
                    child: DecoratedBox(
                      decoration: BoxDecoration(
                        color: Colors.white,
                        border: Border.all(),
                      ),
                      child: const Text('Heading'),
                    ),
                  ),
                ]),
              ]),
        ),
      ),
    );
  }
}

class Slide extends StatefulWidget {
  Slide({
    Key? key,
    required Widget child,
    required Offset offset,
  }) : super(key: key) {
    _child = child;
    _offset = offset;
  }

  late final Widget _child;
  late final Offset _offset;

  @override
  State<Slide> createState() => _SlideState();
}

class _SlideState extends State<Slide> with TickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(
    duration: const Duration(milliseconds: 200),
    vsync: this,
  );

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (BuildContext context, Widget? child) {
        return Transform.translate(
          offset: widget._offset,
          child: child,
        );
      },
      child: widget._child,
    );
  }
}

相关问题