Плавная прокрутка веб-страницы на событии wheelEvent при просмотре страницы

#flutter #flutter-web #mousewheel #smooth-scrolling #flutter-pageview

#flutter #flutter-web #колесо мыши #плавная прокрутка #flutter-просмотр страницы

Вопрос:

С помощью приведенного ниже кода

 import 'package:flutter/material.dart';

void main() => runApp(MyApp());

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

  @override
  Widget build(BuildContext context) => MaterialApp(
        home: const MyHomePage(),
      );
}

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

  @override
  Widget build(BuildContext context) => DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            title: const Center(
            child: Text('use the mouse wheel to scroll')),
            bottom: TabBar(
              tabs: const [
                Center(child: Text('ScrollView')),
                Center(child: Text('PageView'))
              ],
            ),
          ),
          body: TabBarView(
            children: [
              SingleChildScrollView(
                child: Column(
                  children: [
                    for (int i = 0; i < 10; i  )
                      Container(
                        height: MediaQuery.of(context).size.height,
                        child: const Center(
                          child: FlutterLogo(size: 80),
                        ),
                      ),
                  ],
                ),
              ),
              PageView(
                scrollDirection: Axis.vertical,
                children: [
                  for (int i = 0; i < 10;   i)
                    const Center(
                      child: FlutterLogo(size: 80),
                    ),
                ],
              ),
            ],
          ),
        ),
      );
}
  

Вы можете увидеть, запустив его на dartpad или из этого видео,

использование колеса мыши для прокрутки PageView обеспечивает посредственный опыт (в лучшем случае),

Это известная проблема # 35687 # 32120, но я пытаюсь найти обходной путь

для достижения либо плавной прокрутки для PageView , либо, по крайней мере, предотвращения «заикания».

Может ли кто-нибудь помочь мне или указать правильное направление?

Я не уверен, что проблема в PageScrollPhysics ;

У меня есть внутреннее ощущение, что проблема может быть в wheelEvent

поскольку прокрутка с помощью мультитач работает отлично

Комментарии:

1. вот очень неудачный взлом dartpad.dartlang.org/c3476573514298b9885b78a0275c744c

Ответ №1:

Проблема возникает из-за цепочки событий:

  1. пользователь поворачивает колесо мыши на одну ступеньку,
  2. Scrollable получает PointerSignal и вызывает jumpTo метод,
  3. _PagePosition Метод jumpTo (производный от ScrollPositionWithSingleContext ) обновляет положение прокрутки и вызывает goBallistic метод,
  4. запрошенный из PageScrollPhysics моделирования возвращает позицию обратно к исходному значению, поскольку полученное смещение на одну отметку слишком мало для переворачивания страницы,
  5. еще одна отметка и процесс, повторяемый с шага (1).

Один из способов устранить проблему — выполнить задержку перед вызовом goBallistic метода. Это можно сделать в классе _PagePosition, однако класс является частным, и мы должны исправить Flutter SDK:

 // <FlutterSDK>/packages/flutter/lib/src/widgets/page_view.dart
// ...

class _PagePosition extends ScrollPositionWithSingleContext implements PageMetrics {
  //...

  // add this code to fix issue (mostly borrowed from ScrollPositionWithSingleContext):
  Timer timer;

  @override
  void jumpTo(double value) {
    goIdle();
    if (pixels != value) {
      final double oldPixels = pixels;
      forcePixels(value);
      didStartScroll();
      didUpdateScrollPositionBy(pixels - oldPixels);
      didEndScroll();
    }
    if (timer != null) timer.cancel();
    timer = Timer(Duration(milliseconds: 200), () {
      goBallistic(0.0);
      timer = null;
    });
  }

  // ...
}
  

Другой способ — заменить jumpTo на animateTo. Это можно сделать без исправления Flutter SDK, но выглядит сложнее, потому что нам нужно отключить PointerSignalEvent прослушиватель по умолчанию:

 import 'dart:async';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class PageViewLab extends StatefulWidget {
  @override
  _PageViewLabState createState() => _PageViewLabState();
}

class _PageViewLabState extends State<PageViewLab> {
  final sink = StreamController<double>();
  final pager = PageController();

  @override
  void initState() {
    super.initState();
    throttle(sink.stream).listen((offset) {
      pager.animateTo(
        offset,
        duration: Duration(milliseconds: 200),
        curve: Curves.ease,
      );
    });
  }

  @override
  void dispose() {
    sink.close();
    pager.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Mouse Wheel with PageView'),
      ),
      body: Container(
        constraints: BoxConstraints.expand(),
        child: Listener(
          onPointerSignal: _handlePointerSignal,
          child: _IgnorePointerSignal(
            child: PageView.builder(
              controller: pager,
              scrollDirection: Axis.vertical,
              itemCount: Colors.primaries.length,
              itemBuilder: (context, index) {
                return Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Container(color: Colors.primaries[index]),
                );
              },
            ),
          ),
        ),
      ),
    );
  }

  Stream<double> throttle(Stream<double> src) async* {
    double offset = pager.position.pixels;
    DateTime dt = DateTime.now();
    await for (var delta in src) {
      if (DateTime.now().difference(dt) > Duration(milliseconds: 200)) {
        offset = pager.position.pixels;
      }
      dt = DateTime.now();
      offset  = delta;
      yield offset;
    }
  }

  void _handlePointerSignal(PointerSignalEvent e) {
    if (e is PointerScrollEvent amp;amp; e.scrollDelta.dy != 0) {
      sink.add(e.scrollDelta.dy);
    }
  }
}

// workaround https://github.com/flutter/flutter/issues/35723
class _IgnorePointerSignal extends SingleChildRenderObjectWidget {
  _IgnorePointerSignal({Key key, Widget child}) : super(key: key, child: child);

  @override
  RenderObject createRenderObject(_) => _IgnorePointerSignalRenderObject();
}

class _IgnorePointerSignalRenderObject extends RenderProxyBox {
  @override
  bool hitTest(BoxHitTestResult result, {Offset position}) {
    final res = super.hitTest(result, position: position);
    result.path.forEach((item) {
      final target = item.target;
      if (target is RenderPointerListener) {
        target.onPointerSignal = null;
      }
    });
    return res;
  }
}

  

Вот демо на CodePen.

Комментарии:

1. Я пытался использовать animateTo, но я получил эту ошибку: в page_view. _PagePosition.new.получить пиксели [как пиксели] ( localhost: 49511/packages / flutter/src/widgets /… ) в main_layout. _MainLayoutState.new.throttle ( localhost:49511/packages/one_page/… ) в throttle.next (<анонимный>) в _AsyncStarImpl.new.runBody ( localhost:49511/dart_sdk.js:31858:40 ) в Object._microtaskLoop dart_sdk.js:39175:13) в _startMicrotaskLoop (dart_sdk.js: 39181:13) на локальном хостинге: 49511/dart_sdk.js:34688:9

Ответ №2:

Очень похоже, но проще в настройке:

добавьте smooth_scroll_web ^ 0.0.4 в свой pubspec.yaml

 ...
dependencies:
    ...
    smooth_scroll_web: ^0.0.4
...
  

Использование:

 import 'package:smooth_scroll_web/smooth_scroll_web.dart';
import 'package:flutter/material.dart';
import 'dart:math'; // only for demo

class Page extends StatefulWidget {
  @override
  PageState createState() => PageState();
}

class PageState extends State<Page> {
  final ScrollController _controller = new ScrollController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("SmoothScroll Example"),
      ),
      body: SmoothScrollWeb(
        controller: controller,
        child: Container(
            height: 1000,
            child: ListView(
              physics: NeverScrollableScrollPhysics(),
              controller: _controller,
              children: [
                // Your content goes here, thoses children are only for demo
                for (int i = 0; i < 100; i  )
                  Container(
                    height: 60,
                    color: Color.fromARGB(1, 
                      Random.secure().nextInt(255),
                      Random.secure().nextInt(255),
                      Random.secure().nextInt(255)),
                  ),
              ],
            ),
          ),
      ),
    );
  }
}
  

Спасибо тебе, хоббист!

Обратитесь к проблеме flutter # 32120 на Github.

Комментарии:

1. спасибо, я в курсе проблемы github.com/flutter/flutter/issues/32120#issuecomment-725913608

2. также я настоятельно рекомендую НЕ использовать пакеты без тестов gitlab.com/dezso15/smoothscrollweb/-/tree/master /…

3. Мне нравится эта библиотека, она намного улучшает прокрутку с помощью flutter, но только сегодня я заметил, что если я прокручиваю с помощью контроллера прокрутки, плавная прокрутка не «замечает» этого, и когда я пытаюсь прокрутить колесо мыши, оно телепортирует меня обратно в то место, где я нажал на кнопку (которая использовала контроллер прокрутки для прокрутки)

Ответ №3:

Я знаю, что с этого вопроса прошло почти 1,5 года, но я нашел способ, который работает бесперебойно. Возможно, это будет очень полезно для тех, кто это прочитает. Добавьте прослушиватель к вашему контроллеру просмотра страниц с помощью этого кода (вы можете вносить изменения в продолжительность или следующую страницу / animateToPage / jumpToPage и т.д.):

 pageController.addListener(() {
  if (pageController.position.userScrollDirection == ScrollDirection.reverse) {
    pageController.nextPage(duration: const Duration(milliseconds: 60), curve: Curves.easeIn);
  } else if (pageController.position.userScrollDirection == ScrollDirection.forward) {
    pageController.previousPage(duration: const Duration(milliseconds: 60), curve: Curves.easeIn);
  }
});
  

Ответ №4:

Проблема заключается в пользовательских настройках, в том, как конечный пользователь настроил прокрутку с помощью своей мыши. У меня есть мышь Logitech, которая позволяет мне включать или выключать функцию плавной прокрутки с помощью опций Logitech. Когда я включаю плавную прокрутку, она работает отлично и прокручивается по мере необходимости, но в случае отключения плавной прокрутки она также отключается в проекте. Поведение задано конечным пользователем.

Тем не менее, если требуется принудительно выполнить прокрутку для плавной прокрутки, это можно сделать, только установив соответствующие анимации. На данный момент прямого способа нет.

введите описание изображения здесь

Комментарии:

1. извините, приятель, но я боюсь, что вы упускаете мою точку зрения, как вы можете видеть из обеих проблем, которые я связал выше, либо «wheelEvent», либо «PageViewScrollPhysics» не работают должным образом, это утверждение, а НЕ вопрос. Я уверен, что в конечном итоге ошибка будет исправлена / реализована функция, я ищу обходной путь или какую-нибудь полезную информацию о том, как исправить это самостоятельно, или даже просто смягчить проблему. Указание пользователям изменить настройки мыши НЕ является РЕШЕНИЕМ