Layout 颤振,自定义滚动效果

Layout 颤振,自定义滚动效果,layout,dart,scrollview,flutter,Layout,Dart,Scrollview,Flutter,我想实现一个类似于本视频的布局(5:50) 你将如何处理这个问题?我尝试使用ListView&GridLayout,但这似乎仅限于存档。我是否需要使用CustomMultiChildLayout()或CustomScrollView()之类的工具? 如有任何建议,将不胜感激,thx:) 更新: 据我所知,我需要使用CustomScrollView(如果我错了,请纠正我)。但我有点被颤振框架留给我的选项弄得不知所措。从文档中我不确定需要扩展哪些类,或者需要实现哪些接口来归档我的目标。我不知道我需要

我想实现一个类似于本视频的布局(5:50)

你将如何处理这个问题?我尝试使用ListView&GridLayout,但这似乎仅限于存档。我是否需要使用CustomMultiChildLayout()或CustomScrollView()之类的工具? 如有任何建议,将不胜感激,thx:)

更新: 据我所知,我需要使用CustomScrollView(如果我错了,请纠正我)。但我有点被颤振框架留给我的选项弄得不知所措。从文档中我不确定需要扩展哪些类,或者需要实现哪些接口来归档我的目标。我不知道我需要深入研究这个框架有多深。当涉及具有自定义滚动效果的条子和列表时,会涉及以下类:

  • 这实际上是实现滚动效果的渲染对象的基础。我想重新实施这项计划未免太过分了。但也许可以将其子类化并从那里开始(也可能是过度杀戮)
  • 如果我们在层次结构中走得更高,我们会找到抽象类RendersLiverMultiboxAdapter。一条有多个盒子子的长条。这为RenderLiverMultiboxAdapter提供了动态的子对象。这两个都是抽象类。那么,也许从这里开始,扩展这些类
  • 这扩展了RenderShielveMultiboxAdapter,并提供沿主轴布置的长方体子对象。子类由实现RenderLiverBoxChildManager的类交付。 实现RenderSliverBoxChildManager。因此,RenderLiverList和SliverMultiboxAdapterElement是RenderLiverMultiboxAdapter和RenderLiverBoxChildManager的具体实现。我想我可以扩展这些课程。但是如果我这样做,我无论如何都必须重新实现performLayout方法。那么,是否可以重用SliverMultiboxAdapter元素并扩展RendersLiverMultiboxAdapter
  • 该类最终创建渲染对象(一个RenderSliverList,其中SliverMultiboxAdapterElement作为子管理器),并向SliverMultiboxAdapterElement提供一个SliverChildDelegate,而SliverMultiboxAdapterElement又为其惰性地构建子对象。SliverList沿主轴将多个长方体子对象放置在线性阵列中。它使用一个扩展类来动态提供子对象。它可以放置在CustomScrollViews狭缝阵列中。这是在CustomScrollView中创建列表的最具体的片段。那么,我是否可以简单地用这个来存档我的目标,即根据视频进行布局?到目前为止,我试图为CustomScrollView提供一个ScrollController来截取滚动偏移量,然后根据滚动偏移量和元素索引构建子元素。但这样做时,scrollview不再滚动。仅当所有单元的总高度超过视口时,它才会滚动

那么,我真的需要扩展RenderShielveMultiboxAdapter并自己实现perfromLayout方法吗?对我来说,这似乎是现在唯一的选择…

从第一眼就很难理解条子的逻辑

但重要的是滑度学课程

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

导入'dart:math'作为数学;
进口“包装:颤振/基础.dart”;
导入“package:flatter/rendering.dart”;
导入“package:flatter/widgets.dart”;
进口“包装:颤振/材料.省道”;
void main()=>runApp(MyApp());
类MyApp扩展了无状态小部件{
@凌驾
小部件构建(构建上下文){
返回材料PP(
标题:“颤振演示”,
主题:主题数据(
主样本:颜色。蓝色,
),
家:脚手架(
正文:MyHomePage(),
),
);
}
}
类MyHomePage扩展StatefulWidget{
@凌驾
_MyHomePageState createState()=>\u MyHomePageState();
}
类_MyHomePageState扩展状态{
最终全局键=全局键();
RenderObject-ansestor;
@凌驾
void initState(){
WidgetsBinding.instance.addPostFrameCallback(_getPosition);
super.initState();
}
_getPosition(u41;{
设置状态(){
ansestor=_key.currentContext.FindEnderObject();
});
}
@凌驾
小部件构建(构建上下文){
返回布局生成器(生成器:(上下文、约束){
返回自定义滚动视图(
物理:ClampingScrollPhysics(),
键:_键,
条子:[
银条(
我最初是这样说的:是的,
ansestor:ansestor,
子项:\u项(
标题:“第一个标题”,
文件名:'item_1',
),
),
银条(
ansestor:ansestor,
子项:\u项(
标题:“第二个标题”,
文件名:'item_2',
),
),
银条(
ansestor:ansestor,
子项:\u项(
标题:“第三个标题”,
文件名:'item_3',
),
),
银条(
ansestor:ansestor,
子项:\u项(
标题:“第四个标题”,
文件名:'item_4',
),
),
银条(
ansestor:ansestor,
子项:\u项(
标题:“第五名”,
文件名:“第5项”,
),
),
银条(
ansestor:ansestor,
子项:\u项(
标题:“第一个标题”,
文件名:“第6项”,
),
),
滑动双轴适配器(
子:容器(
儿童:中心(
子项:文本('end'),
),
嗨
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,
                  ),
            ),
          ),
        );
      },
    );
  }
}