最近我安装了一个名为Chanel Fashion的新应用,在它的主页上有一种非常奇怪的滚动类型,你可以从GIF下面看到它,我非常怀疑它是任何类型的自定义滚动,我认为它是一个页面浏览,有什么提示我可以在Ffltter中实现这样的东西吗?

enter image description here

PS this blog试图在安卓系统中实现类似的功能,但在很多方面都有所不同.

P.s.2所以question人试图在IOS上实现它.

推荐答案

这是我的演示

demo chanel scroll

演示中的库:插值:^1.0.2+2

main.dart

import 'package:chanel_scroll_animation/chanel1/chanel1_page.dart';
import 'package:flutter/material.dart';
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: Chanel1Page(),
    );
  }
}

chanel1_page.dart

import 'package:chanel_scroll_animation/chanel1/项目飞奔';
import 'package:chanel_scroll_animation/chanel1/snap_list_view.dart';
import 'package:chanel_scroll_animation/models/model.dart';
import 'package:flutter/material.dart';


class Chanel1Page extends StatefulWidget {
  @override
  _Chanel1PageState createState() => _Chanel1PageState();
}

class _Chanel1PageState extends State<Chanel1Page> {
  ScrollController _scrollController;
  double y=0;
  double maxHeight=0;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _scrollController=new ScrollController();
    _scrollController.addListener(() {
      print("_scrollController.offset.toString() "+_scrollController.offset.toString());


      setState(() {
        y=_scrollController.offset;
      });

    });
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      final Size size=MediaQuery.of(context).size;
      setState(() {
        maxHeight=size.height/2;
      });

    });

  }


  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: SafeArea(
        child: maxHeight!=0?SnappingListView(
          controller: _scrollController,
            snapToInterval: maxHeight,
            scrollDirection: Axis.vertical,
          children: [

            Container(
              height:  ( models.length +1) * maxHeight,

              child: Column(
                children: [
                  for (int i = 0; i < models.length; i++)
                    Item(item: models[i],index: i,y: y,)
                ],
              ),
            )

          ],
        ):Container(),
      ),

    );
  }
}

项目飞奔

import 'package:chanel_scroll_animation/models/model.dart';
import 'package:flutter/material.dart';
import 'package:interpolate/interpolate.dart';

const double MIN_HEIGHT = 128;
class Item extends StatefulWidget {
  final Model item;
  final int index;
  final double y;
  Item({this.item,this.index,this.y});

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

class _ItemState extends State<Item> {

  Interpolate ipHeight;
  double maxHeight=0;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
   WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      final Size size=MediaQuery.of(context).size;
     maxHeight=size.height/2;
     initInterpolate();
   });
  }

  initInterpolate()
  {
    ipHeight=Interpolate(
      inputRange: [(widget.index-1)*maxHeight,widget.index*maxHeight],
      outputRange: [MIN_HEIGHT,maxHeight],
      extrapolate: Extrapolate.clamp,
    );
  }
  @override
  Widget build(BuildContext context) {
    final Size size=MediaQuery.of(context).size;
    double height=ipHeight!=null? ipHeight.eval(widget.y):MIN_HEIGHT;
    print("height "+height.toString());

    return Container(
      height: height,
      child: Stack(
        children: [
          Positioned.fill(
            child: Image.asset(
              widget.item.picture,
              fit: BoxFit.cover,
            ),
          ),
          Positioned(
            bottom:40,
            left: 30,
            right: 30,
            child: Column(
              children: [
                Text(
                  widget.item.subtitle,
                  style: TextStyle(fontSize: 16, color: Colors.white),
                ),
                SizedBox(
                  height: 10,
                ),
                Text(
                  widget.item.title.toUpperCase(),
                  style: TextStyle(fontSize: 24, color: Colors.white),
                  textAlign: TextAlign.center,
                ),
              ],
            ),
          )
        ],
      ),
    );
  }
}

snap_list_view.dart

import "package:flutter/widgets.dart";
import "dart:math";

class SnappingListView extends StatefulWidget {
  final Axis scrollDirection;
  final ScrollController controller;

  final IndexedWidgetBuilder itemBuilder;
  final List<Widget> children;
  final int itemCount;

  final double snapToInterval;
  final ValueChanged<int> onItemChanged;

  final EdgeInsets padding;

  SnappingListView(
      {this.scrollDirection,
        this.controller,
        @required this.children,
        @required this.snapToInterval,
        this.onItemChanged,
        this.padding = const EdgeInsets.all(0.0)})
      : assert(snapToInterval > 0),
        itemCount = null,
        itemBuilder = null;

  SnappingListView.builder(
      {this.scrollDirection,
        this.controller,
        @required this.itemBuilder,
        this.itemCount,
        @required this.snapToInterval,
        this.onItemChanged,
        this.padding = const EdgeInsets.all(0.0)})
      : assert(snapToInterval > 0),
        children = null;

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

class _SnappingListViewState extends State<SnappingListView> {
  int _lastItem = 0;

  @override
  Widget build(BuildContext context) {
    final startPadding = widget.scrollDirection == Axis.horizontal
        ? widget.padding.left
        : widget.padding.top;
    final scrollPhysics = SnappingListScrollPhysics(
        mainAxisStartPadding: startPadding, itemExtent: widget.snapToInterval);
    final listView = widget.children != null
        ? ListView(
        scrollDirection: widget.scrollDirection,
        controller: widget.controller,
        children: widget.children,

        physics: scrollPhysics,
        padding: widget.padding)
        : ListView.builder(
        scrollDirection: widget.scrollDirection,
        controller: widget.controller,
        itemBuilder: widget.itemBuilder,
        itemCount: widget.itemCount,

        physics: scrollPhysics,
        padding: widget.padding);
    return NotificationListener<ScrollNotification>(
        child: listView,
        onNotification: (notif) {
          if (notif.depth == 0 &&
              widget.onItemChanged != null &&
              notif is ScrollUpdateNotification) {
            final currItem =
                (notif.metrics.pixels - startPadding) ~/ widget.snapToInterval;
            if (currItem != _lastItem) {
              _lastItem = currItem;
              widget.onItemChanged(currItem);
            }
          }
          return false;
        });
  }
}

class SnappingListScrollPhysics extends ScrollPhysics {
  final double mainAxisStartPadding;
  final double itemExtent;

  const SnappingListScrollPhysics(
      {ScrollPhysics parent,
        this.mainAxisStartPadding = 0.0,
        @required this.itemExtent})
      : super(parent: parent);

  @override
  SnappingListScrollPhysics applyTo(ScrollPhysics ancestor) {
    return SnappingListScrollPhysics(
        parent: buildParent(ancestor),
        mainAxisStartPadding: mainAxisStartPadding,
        itemExtent: itemExtent);
  }

  double _getItem(ScrollPosition position) {
    return (position.pixels - mainAxisStartPadding) / itemExtent;
  }

  double _getPixels(ScrollPosition position, double item) {
    return min(item * itemExtent, position.maxScrollExtent);
  }

  double _getTargetPixels(
      ScrollPosition position, Tolerance tolerance, double velocity) {
    double item = _getItem(position);
    if (velocity < -tolerance.velocity)
      item -= 0.5;
    else if (velocity > tolerance.velocity) item += 0.5;
    return _getPixels(position, item.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;
}

Dart相关问答推荐

为具有通用函数字段的Generic类实现copyWith

在异步方法中,有没有更优雅的方式在DART中等待!=NULL?

如何防止 Riverpod 的 ConsumerWidget 重建管理 ThemeMode

在 Dart 中使用隔离优于异步的优势

在 Flutter 中从树中临 timeshift 除时保留小部件状态

寻找provider context 的flutter问题

Angular Dart 和 Polymer Dart 的区别

Dart JavaScript 与 jQuery 的互操作回调

错误:非抽象类InternalSelectableMathState

为什么这个文件会自动创建自己 generate_plugin_registrant.dart

使用元组解包样式交换两个变量的值

如何在Dart中测试流

如何更改Flatter DevTools的默认浏览器?

用于名称和数字省道的正则表达式

Dart:Iterable 与 List,总是使用 Iterable?

如何在 Dart 中读取控制台输入/标准输入?

Dart 中不同的 Map 实现有什么区别?

如何使用 Dart 构建枚举?

dart中的动态和对象有什么区别?

dart 折叠(Fold)与减少(Reduce)