Flutter:为用户滚动增加摩擦力,而不是弹簧

Posted

技术标签:

【中文标题】Flutter:为用户滚动增加摩擦力,而不是弹簧【英文标题】:Flutter: Add friction to user scrolling, not spring 【发布时间】:2021-01-25 09:38:56 【问题描述】:

我有一个带有自定义滚动物理类的列表视图,它定义了我想要的列表的滚动和弹簧效果。我已经设法按照我想要的方式设置了弹簧阻尼,但我似乎找不到任何关于使用户的阻力更重的设置。

我的意思是当用户拖动时我希望列表感觉有张力所以用户需要比平时拖动得更远,我将处理移动到列表中的下一项使用自定义滚动物理。

我希望它感觉像在火车站的转弯风格。你的身体会受到很大的压力,一旦你通过转弯风格就会重新回到中心。

列表:

child: ListView.builder(
  cacheExtent: MediaQuery.of(context).size.height * 2,
  padding: EdgeInsets.only(top: 0),
  itemExtent: constraints.maxHeight -
      SizeConfig.blockSizeVertical * 40,
  itemCount: channelList?.length ?? 0,
  controller: _controller,
  physics: _physics,
  itemBuilder: (BuildContext context, int index) =>
      buildList(index),
),

自定义滚动类:

import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';

class CustomScrollPhysics extends ScrollPhysics 
  final double itemDimension;
  static final SpringDescription customSpring =
      SpringDescription.withDampingRatio(
    mass: 4,
    stiffness: 150.0,
    ratio: 2.0,
  );

  @override
  double get dragStartDistanceMotionThreshold => 40;

  @override
  double get minFlingVelocity => double.infinity;

  @override
  double get maxFlingVelocity => double.infinity;

  @override
  double get minFlingDistance => double.infinity;

  CustomScrollPhysics(this.itemDimension, ScrollPhysics parent)
      : super(parent: parent);

  @override
  CustomScrollPhysics applyTo(ScrollPhysics ancestor) 
    return CustomScrollPhysics(
        itemDimension: itemDimension, parent: buildParent(ancestor));
  

  double _getPage(ScrollPosition position) 
    return position.pixels / itemDimension;
  

  double _getPixels(double page) 
    return page * itemDimension;
  

  double _getTargetPixels(
      ScrollPosition position, Tolerance tolerance, double velocity) 
    double page = _getPage(position);
    if (velocity < -tolerance.velocity) 
      page -= 0.01;
     else if (velocity > tolerance.velocity) 
      page += 0.01;
    
    return _getPixels(page.roundToDouble());
  

  @override
  Simulation createBallisticSimulation(
      ScrollMetrics position, double velocity) 
    // If we're out of range and not headed back in range, defer to the parent
    // ballistics, which should put us back in range at an item boundary.
    if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
        (velocity >= 0.0 && position.pixels >= position.maxScrollExtent))
      return super.createBallisticSimulation(position, (velocity));
    final Tolerance tolerance = this.tolerance;
    final double target = _getTargetPixels(position, tolerance, velocity);
    if (target != position.pixels) 
      return ScrollSpringSimulation(
          customSpring, position.pixels, target, velocity,
          tolerance: tolerance);
    
    return null;
  

  @override
  bool get allowImplicitScrolling => false;


/// Note: This Widget creates the ballistics model for a snap-to physics in a ListView.
/// Normally you might use the ListViewScrollView, however that widget currently
/// has a bug that prevents onTap detection of child widgets.

列表居中在屏幕上,大约是视图高度的 60%。

【问题讨论】:

ios 中,当您拖动“超出”限制时,它的行为与您描述的完全一样。我会通过简单地使用它来找到一个快速的解决方案。将您的“结束”移动到“太短”的位置,然后快速将“结束”设置为真正的结束,一旦用户实际到达结束。 您所描述的听起来像是设置了最大值snap(2nd acceleration derivative)。限制快照意味着更长的滚动会导致(最大)稳定增加的加速度变化率。或者,限制 jerk(第一加速度导数)或将 jerk 乘以 0 到 1 之间的因子也可能获得所需的结果。我不知道如何在颤振中实现它。 所以你要做的是实现速度随时间的变化,这样你就可以使用速度的导数。我不知道颤振是否已经提供了这一点,或者你是否需要自己做。 【参考方案1】:

如果您一次只需要在列表中显示一个项目并且项目的呈现将适合屏幕,则使用PageView 小部件可能会给您带来您所追求的效果。

【讨论】:

【参考方案2】:

试试这个,

import 'dart:math';
 
import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget 
      @override
      Widget build(BuildContext context) 
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          home: MyHomePage(),
        );
      
    
    
    class MyHomePage extends StatefulWidget 
      @override
      _MyHomePageState createState() => _MyHomePageState();
    
    
    class _MyHomePageState extends State<MyHomePage> 
      final _listController = ScrollController();
    
      final List<int> _list = List.generate(12, (index) => index);
    
      ScrollPhysics _scrollPhysics;
    
      @override
      void initState() 
        super.initState();
    
        _listController.addListener(() 
          if (_listController.position.haveDimensions && _scrollPhysics == null) 
            setState(() 
              var dimension =
                  _listController.position.maxScrollExtent / (_list.length - 4);
              _scrollPhysics = CustomScrollPhysics(itemDimension: dimension);
            );
          
        );
      
    
      @override
      Widget build(BuildContext context) 
        return Scaffold(
          body: SafeArea(
            child: ListView.builder(
              scrollDirection: Axis.vertical,
              controller: _listController,
              physics: _scrollPhysics,
              itemCount: _list.length,
              itemBuilder: (context, index) => Container(
                height: 300,
                width: double.infinity,
                color: randomColor,
                child: FlutterLogo(),
                margin: const EdgeInsets.all(2.0),
              ),
            ),
          ),
        );
      
    
      Color get randomColor =>
          Color((Random().nextDouble() * 0xFFFFFF).toInt() << 0).withOpacity(1.0);
    
    
    class CustomScrollPhysics extends ScrollPhysics 
      final double itemDimension;
    
      CustomScrollPhysics(this.itemDimension, ScrollPhysics parent)
          : super(parent: parent);
    
      @override
      CustomScrollPhysics applyTo(ScrollPhysics ancestor) 
        return CustomScrollPhysics(
            itemDimension: itemDimension, parent: buildParent(ancestor));
      
    
      double _getPage(ScrollPosition position) 
        return position.pixels / itemDimension;
      
    
      double _getPixels(double page) 
        return page * itemDimension;
      
    
      double _getTargetPixels(
          ScrollPosition position, Tolerance tolerance, double velocity) 
        double page = _getPage(position);
        if (velocity < -tolerance.velocity) 
          page -= 0.1;
         else if (velocity > tolerance.velocity) 
          page += 0.1;
        
        return _getPixels(page.roundToDouble());
      
    
      @override
      Simulation createBallisticSimulation(
          ScrollMetrics position, double velocity) 
        // If we're out of range and not headed back in range, defer to the parent
        // ballistics, which should put us back in range at a page boundary.
        if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
            (velocity >= 0.0 && position.pixels >= position.maxScrollExtent))
          return super.createBallisticSimulation(position, velocity);
        final Tolerance tolerance = this.tolerance;
        final double target = _getTargetPixels(position, tolerance, velocity);
        if (target != position.pixels)
          return ScrollSpringSimulation(spring, position.pixels, target, velocity,
              tolerance: tolerance);
        return null;
      
    
      @override
      bool get allowImplicitScrolling => false;
    

现在在颤振中,没有预先定义摩擦的方法, 但这可以通过一些自定义方式实现。

【讨论】:

以上是关于Flutter:为用户滚动增加摩擦力,而不是弹簧的主要内容,如果未能解决你的问题,请参考以下文章

弹力重力摩擦力

unity中汽车经过加速带如何实现加速

Flutter 布局问题(ListView 和 GridView)

Flutter Web - 文本字段滚动而不是选择长文本

Flutter Google Maps 不会横向滚动

如何更改滚动视图的 alpha 值