颤动,自定义滚动效果

时间:2017-12-26 13:56:37

标签: layout dart scrollview flutter

我想实现此视频中的布局(5:50)https://www.youtube.com/watch?v=KYUTQQ1usZE&index=1&list=PL23Revp-82LKxKN9SXqQ5Nxaa1ZpYEQuaadd#t=05m50s

你会如何解决这个问题?我尝试使用ListView& GridLayout,但这似乎仅限于存档。我是否需要使用CustomMultiChildLayout(https://docs.flutter.io/flutter/widgets/CustomMultiChildLayout-class.html)或CustomScrollView(https://docs.flutter.io/flutter/widgets/CustomScrollView-class.html)之类的东西?  任何建议将不胜感激,thx:)

更新: 据我所知,我需要使用CustomScrollView(如果我错了,请纠正我)。但是我对Flutter框架留给我的选项感到有些不知所措。我不确定从文档中我需要扩展哪些类,或者我需要实现哪些接口来存档我的目标。我不知道我需要深入了解框架。当涉及到具有自定义滚动效果的条子和列表时,涉及以下类:

  • RenderSliver这实际上是实现滚动效果的渲染对象的基础。我想重新实现它会有点过头了。但也许是它的子类并从那里开始(也许是矫枉过正的)?
  • RenderSliverMultiBoxAdaptor如果我们在层次结构中走高,我们会找到抽象类RenderSliv​​erMultiBoxAdaptor。有多个盒子儿的条子。 A RenderSliverBoxChildManager这为RenderSliv​​erMultiBoxAdaptor动态提供了孩子。这些都是抽象类。那么可以从这里开始并扩展这些类?
  • RenderSliverList这扩展了RenderSliv​​erMultiBoxAdaptor并提供沿主轴布置的盒子。子类由实现RenderSliv​​erBoxChildManager的类传递。 SliverMultiBoxAdaptorElement实现了RenderSliv​​erBoxChildManager。因此RenderSliv​​erList和SliverMultiBoxAdaptorElement是RenderSliv​​erMultiBoxAdaptor和RenderSliv​​erBoxChildManager的具体实现。我以为我可以扩展这些课程。但是,如果我这样做,我无论如何都必须重新实现performLayout方法。那么可能重用SliverMultiBoxAdaptorElement并扩展RenderSliv​​erMultiBoxAdaptor?
  • SliverList此类最终创建渲染对象(带有SliverMultiBoxAdaptorElement的RenderSliv​​erList作为子管理器),并为SliverMultiBoxAdaptorElement提供SliverChildDelegate,而SliverMultiBoxAdaptorElement又为SliverMultiBoxAdaptorWidget延迟构建子级。 SliverList沿主轴将多个盒子项放置在线性阵列中。它使用一个扩展SliverChildDelegate的类来为孩子们提供动态。它可以放在CustomScrollViews细长条阵列中。这是在CustomScrollView中创建列表的最具体的条子。那么我是否也可以将我的目标归档为根据视频进行布局?到目前为止,我尝试为CustomScrollView提供一个ScrollController来拦截滚动偏移,然后根据滚动偏移量和元素索引SliverChildBuilderDelegate来构建子元素。但是这样做时,scrollview不再滚动。当所有单元格的总高度超过视口时,它仅滚动。

所以我真的需要扩展RenderSliv​​erMultiBoxAdaptor并自己实现perfromLayout方法吗?对我来说,它似乎是现在唯一的选择......

1 个答案:

答案 0 :(得分:1)

从一开始就很难理解银条的逻辑。

但是重要的是SliverGeometry类

  • paintOrigin-将其视为增量类型。当您想制作小部件时 固定在屏幕上,则需要从顶部推动它。
  • constraints.scrollOffset显示逻辑位置的滚动偏移量 小部件。
  • scrollExtent显示小部件的逻辑高度。帮助小部件 知道您滚动了所有条子。

enter image description here

import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final GlobalKey _key = GlobalKey();

  RenderObject ansestor;
  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback(_getPosition);

    super.initState();
  }

  _getPosition(_) {
    setState(() {
      ansestor = _key.currentContext.findRenderObject();
    });
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      return CustomScrollView(
        physics: ClampingScrollPhysics(),
        key: _key,
        slivers: <Widget>[
          CustomSliver(
            isInitiallyExpanded: true,
            ansestor: ansestor,
            child: _Item(
              title: 'first title',
              fileName: 'item_1',
            ),
          ),
          CustomSliver(
            ansestor: ansestor,
            child: _Item(
              title: 'second title',
              fileName: 'item_2',
            ),
          ),
          CustomSliver(
            ansestor: ansestor,
            child: _Item(
              title: 'third title',
              fileName: 'item_3',
            ),
          ),
          CustomSliver(
            ansestor: ansestor,
            child: _Item(
              title: 'fourth title',
              fileName: 'item_4',
            ),
          ),
          CustomSliver(
            ansestor: ansestor,
            child: _Item(
              title: 'fifth title',
              fileName: 'item_5',
            ),
          ),
          CustomSliver(
            ansestor: ansestor,
            child: _Item(
              title: 'first title',
              fileName: 'item_6',
            ),
          ),
          SliverToBoxAdapter(
            child: Container(
              child: Center(
                child: Text('end'),
              ),
              height: 1200,
              color: Colors.green.withOpacity(0.3),
            ),
          ),
        ],
      );
    });
  }
}

class CustomSliver extends SingleChildRenderObjectWidget {
  CustomSliver({
    this.child,
    Key key,
    this.ansestor,
    this.isInitiallyExpanded = false,
  }) : super(key: key);

  final RenderObject ansestor;
  final bool isInitiallyExpanded;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return CustomRenderSliver(
      isInitiallyExpanded: isInitiallyExpanded,
    );
  }

  @override
  void updateRenderObject(
    BuildContext context,
    CustomRenderSliver renderObject,
  ) {
    renderObject.ansestor = ansestor;
    renderObject.markNeedsLayout();
  }

  final Widget child;
}

class CustomRenderSliver extends RenderSliverSingleBoxAdapter {
  CustomRenderSliver({
    RenderBox child,
    this.isInitiallyExpanded,
  }) : super(child: child);

  final double max = 250;
  final double min = 100;

  RenderObject ansestor;
  final bool isInitiallyExpanded;
  void performLayout() {
    var constraints = this.constraints;

    double distanceToTop;

    double maxExtent;

    if (ansestor != null) {
      distanceToTop = child.localToGlobal(Offset.zero, ancestor: ansestor).dy;
    }

    if (ansestor == null) {
      if (isInitiallyExpanded) {
        maxExtent = max;
      } else {
        maxExtent = min;
      }
    } else {
      if (constraints.scrollOffset > 0) {
        maxExtent = (max - constraints.scrollOffset).clamp(0.0, max);
      } else if (distanceToTop < max) {
        maxExtent = min + (3 * (250 - distanceToTop) / 5);
      } else {
        maxExtent = min;
      }
    }

    child.layout(
      constraints.asBoxConstraints(maxExtent: maxExtent),
      parentUsesSize: true,
    );

    var paintExtent = math.min(maxExtent, constraints.remainingPaintExtent);

    geometry = SliverGeometry(
      paintOrigin: maxExtent == 0 ? 0.0 : constraints.scrollOffset,
      scrollExtent: max,
      paintExtent: paintExtent,
      maxPaintExtent: paintExtent,
      hasVisualOverflow: true,
    );

    constraints = constraints.copyWith(remainingPaintExtent: double.infinity);
    setChildParentData(child, constraints, geometry);
  }
}

class _Item extends StatelessWidget {
  const _Item({
    Key key,
    @required this.title,
    @required this.fileName,
  }) : super(key: key);

  final String title;
  final String fileName;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        return Container(
          height: 250,
          decoration: BoxDecoration(
            image: DecorationImage(
              image: AssetImage('assets/images/$fileName.png'),
              fit: BoxFit.fitWidth,
            ),
          ),
          child: Padding(
            padding: const EdgeInsets.only(top: 40),
            child: Text(
              title,
              style: Theme.of(context).textTheme.headline4.copyWith(
                    color: Colors.white,
                    fontWeight: FontWeight.bold,
                    fontSize: 60,
                  ),
            ),
          ),
        );
      },
    );
  }
}