flutter - 在垂直 PageView 中包裹不同高度的项目 - Flutter

标签 flutter dart flutter-layout flutter-listview flutter-pageview

我有一个垂直 PageView具有不同的项目高度:
enter image description here
但我想根据每个元素的高度包装它们。
我在这里想要的最终结果:
enter image description here
我们应该怎么做 ?

最佳答案

使用 垂直页面构建器和堆栈中的内容 .通过这种方式,您可以在拥有自己的布局的同时利用滚动动画。
enter image description here
这是我写的插件。随意使用它并将其转换为 pub.dev 插件。
用法:

import 'package:flutter/material.dart';

import 'VerticalPageViewer.dart';

class StackTest extends StatefulWidget {
  const StackTest({Key? key}) : super(key: key);

  @override
  State<StackTest> createState() => _StackTestState();
}

class _StackTestState extends State<StackTest> {
  final List<Widget> images = [
    Container(
      decoration: BoxDecoration(
        color: Colors.red,
        borderRadius: BorderRadius.all(Radius.circular(10)),
      ),
    ),
    Container(
      decoration: BoxDecoration(
        color: Colors.cyan,
        borderRadius: BorderRadius.all(Radius.circular(10)),
      ),
    ),
    Container(
      decoration: BoxDecoration(
        color: Colors.grey,
        borderRadius: BorderRadius.all(Radius.circular(10)),
      ),
    ),
  ];
  List<double> testHeight = [100, 300, 500];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Dynamic Height PageView',
          style: TextStyle(color: Colors.white),
        ),
        centerTitle: true,
      ),
      body: SafeArea(
        child: Container(
          child: DynamicHeightPageView(
            heightList: testHeight,
            children: images,
            onSelectedItem: (index) {
              print("index: $index");
            },
          ),
        ),
      ),
    );
  }
}

DynamicHeightPageView 类:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:collection/collection.dart';

typedef PageChangedCallback = void Function(double? page);
typedef PageSelectedCallback = void Function(int index);

class DynamicHeightPageView extends StatefulWidget {
  final List<double> heightList;
  final List<Widget> children;
  final double cardWidth;
  final ScrollPhysics? physics;
  final PageChangedCallback? onPageChanged;
  final PageSelectedCallback? onSelectedItem;
  final int initialPage;

  DynamicHeightPageView({
    required this.heightList,
    required this.children,
    this.physics,
    this.cardWidth = 300,
    this.onPageChanged,
    this.initialPage = 0,
    this.onSelectedItem,
  }) : assert(heightList.length == children.length);

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

class _DynamicHeightPageViewState extends State<DynamicHeightPageView> {
  double? currentPosition;
  PageController? controller;

  @override
  void initState() {
    super.initState();
    currentPosition = widget.initialPage.toDouble();
    controller = PageController(initialPage: widget.initialPage);

    controller!.addListener(() {
      setState(() {
        currentPosition = controller!.page;

        if (widget.onPageChanged != null) {
          Future(() => widget.onPageChanged!(currentPosition));
        }

        if (widget.onSelectedItem != null && (currentPosition! % 1) == 0) {
          Future(() => widget.onSelectedItem!(currentPosition!.toInt()));
        }
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      return GestureDetector(
        onTap: () {
          print("Current Element index tab: ${currentPosition!.round()}");
        },
        child: Stack(
          children: [
            CardController(
              cardWidth: widget.cardWidth,
              heightList: widget.heightList,
              children: widget.children,
              currentPosition: currentPosition,
              cardViewPagerHeight: constraints.maxHeight,
              cardViewPagerWidth: constraints.maxWidth,
            ),
            Positioned.fill(
              child: PageView.builder(
                physics: widget.physics,
                scrollDirection: Axis.vertical,
                itemCount: widget.children.length,
                controller: controller,
                itemBuilder: (context, index) {
                  return Container();
                },
              ),
            )
          ],
        ),
      );
    });
  }
}

class CardController extends StatelessWidget {
  final double? currentPosition;
  final List<double> heightList;
  final double cardWidth;
  final double cardViewPagerHeight;
  final double? cardViewPagerWidth;
  final List<Widget>? children;

  CardController({
    this.children,
    this.cardViewPagerWidth,
    required this.cardWidth,
    required this.cardViewPagerHeight,
    required this.heightList,
    this.currentPosition,
  });

  @override
  Widget build(BuildContext context) {
    List<Widget> cardList = [];

    for (int i = 0; i < children!.length; i++) {
      var cardHeight = heightList[i];

      var cardTop = getTop(cardHeight, cardViewPagerHeight, i, heightList);
      var cardLeft = (cardViewPagerWidth! / 2) - (cardWidth / 2);

      Widget card = Positioned(
        top: cardTop,
        left: cardLeft,
        child: Container(
          width: cardWidth,
          height: cardHeight,
          child: children![i],
        ),
      );

      cardList.add(card);
    }

    return Stack(
      children: cardList,
    );
  }

  double getTop(
      double cardHeight, double viewHeight, int i, List<double> heightList) {
    double diff = (currentPosition! - i);
    double diffAbs = diff.abs();

    double basePosition = (viewHeight / 2) - (cardHeight / 2);

    if (diffAbs == 0) {
      //element in focus
      return basePosition;
    }

    int intCurrentPosition = currentPosition!.toInt();
    double doubleCurrentPosition = currentPosition! - intCurrentPosition;

    //calculate distance between to-pull elements
    late double pullHeight;
    if (heightList.length > intCurrentPosition + 1) {
      //check for end of list
      pullHeight = heightList[intCurrentPosition] / 2 +
          heightList[intCurrentPosition + 1] / 2;
    } else {
      pullHeight = heightList[intCurrentPosition] / 2;
    }

    if (diff >= 0) {
      //before focus element
      double afterListSum = heightList.getRange(i, intCurrentPosition + 1).sum;

      return (viewHeight / 2) -
          afterListSum +
          heightList[intCurrentPosition] / 2 -
          pullHeight * doubleCurrentPosition;
    } else {
      //after focus element
      var beforeListSum = heightList.getRange(intCurrentPosition, i).sum;
      return (viewHeight / 2) +
          beforeListSum -
          heightList[intCurrentPosition] / 2 -
          pullHeight * doubleCurrentPosition;
    }
  }
}

关于flutter - 在垂直 PageView 中包裹不同高度的项目 - Flutter,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68963982/

相关文章:

dart - 使用 Map 而不是 List 的 Flutter listview

dart - 如何在纸张下拉菜单中检索当前输入

Flutter – 连续两个文本,左边一个优雅地溢出

dart - 有没有可能在执行之前就知道 pop 会把你带到哪里?

dart - flutter 对齐按钮到抽屉底部

Flutter - 第一个屏幕仅在真实的 iOS 设备上损坏,在 Android 和模拟器上都不会损坏

validation - 如果我在TextField的 'setState'中调用 'onChange',首个字母会丢失

Flutter 如何以编程方式退出应用程序

Flutter TextField 填充和边框颜色不会变为灰色

input - Flutter - SingleChildScrollView 在键盘弹出时放置在屏幕顶部