likes
comments
collection
share

实现在Flutter中将PageView嵌入ListView并实现双向通信

作者站长头像
站长
· 阅读数 36

本文将探讨如何实现PageView和ListView之间的双向通信,使它们能够在页面切换、数据更新等方面进行有效的交互。我们将介绍一些实践的方法和技巧,以帮助开发人员充分利用这两个强大组件的潜力,并构建出更具交互性和动态性的应用程序

下面我们来看一下今天要分享的内容:

mixin关键字

在Dart中,mixin是一种强大的代码复用机制,它允许我们将可复用的代码片段注入到一个或多个类中,以增强类的功能和灵活性。使用mixin,我们可以在不修改类的继承关系的情况下,为类添加额外的方法、属性或其他功能。

使用mixin的好处是可以将相关的功能组织成独立的模块,并将其注入到多个类中,从而实现代码的复用。这种方式避免了多重继承可能带来的复杂性,并且使代码更加清晰、可维护和可扩展。

在Dart中,我们可以通过定义一个mixin,并使用on关键字指定mixin可以应用的类类型。然后,在需要使用mixin的类中使用with关键字将mixin注入,即可获得mixin中定义的方法、属性或其他功能。

通过使用mixin,我们可以轻松地将共享的代码片段注入到多个类中,避免代码的重复编写,并提高代码的可重用性和可扩展性。这种代码复用的机制为我们提供了更灵活、更高效的开发方式,使得代码的组织和维护变得更加简单和直观。

了解了mixin关键字下面我们就来看一下具体的代码实现:

WorkRefreshStateMixin

mixin WorkRefreshStateMixin<T extends StatefulWidget> on State<T> {
  ///refresh
  void onMixinRefresh();
}

ListPageDemo中调用:

 void onRefreshData() {
    workKeyMixin.currentState?.onMixinNotify();
    globalRefresh.currentState?.onMixinRefresh();
    Future.delayed(const Duration(milliseconds: 500)).then(
        (value) => Scrollable.ensureVisible(globalTabKey.currentContext!));
  }

HandleProcessView类中,通过使用with WorkRefreshStateMixinWorkRefreshStateMixin mixin注入到类中,以扩展其功能。其中,WorkRefreshStateMixin提供了一个抽象方法onMixinRefresh,用于处理刷新操作。

ListPageDemo类中,当需要触发HandleProcessView的刷新事件时,可以通过globalRefreshcurrentState来调用onMixinRefresh方法。具体而言,可以使用globalRefresh.currentState?.onMixinRefresh()来执行刷新逻辑。

这种方式实现了HandleProcessViewListPageDemo之间的双向通信。ListPageDemo作为父容器可以主动调用HandleProcessView的刷新方法,而HandleProcessView通过WorkRefreshStateMixin接收到刷新事件并执行相应的刷新逻辑。这种通信机制使得父子组件能够相互协作,实现数据更新和页面刷新的功能

class HandleProcessView extends StatefulWidget {
  final int? ticketId;
  final GlobalKey<WorkLogStateMixin>? tabKeyMixin;
  HandleProcessView({
    Key? key,
    this.ticketId,
    this.tabKeyMixin,
  }) : super(key: key);
  @override
  State<StatefulWidget> createState() => _HandleProcessViewState();
}

class _HandleProcessViewState extends State<HandleProcessView>
    with AutomaticKeepAliveClientMixin, WorkRefreshStateMixin {
  @override
  bool get wantKeepAlive => true;

  List<ProcessInfo> sourceList = [];
  @override
  void initState() {
    doRequest();
    super.initState();
  }

  @override
  void onMixinRefresh() {
    ///刷新页面
  }
  Future doRequest() async {}
  
  void _refresh() {
    if (mounted) {
      setState(() {});
      widget.tabKeyMixin?.currentState?.onMixinNotify();
    }
  }

  @override
  Widget build(BuildContext context) {
    return sourceList.length == 0
        ? Container(
            height: 800.rpx,
            child: Center(
              child: Text(
                "暂无数据",
                style: TextStyle(
                  color: Color(0xff999999),
                  fontSize: 28.rpx,
                ),
              ),
            ))
        : Container(
            padding: EdgeInsets.only(bottom: 32.rpx),
            child: ListView.builder(
              shrinkWrap: true,
              physics: NeverScrollableScrollPhysics(),
              itemBuilder: (BuildContext context, int index) {
                ProcessInfo? processInfo = sourceList[index];
                return _renderItemView(processInfo);
              },
              itemCount: sourceList.length,
            ),
          );
  }

  Widget _renderItemView(ProcessInfo? processInfo) {
    return Container(
      padding: EdgeInsets.only(
        left: 14.rpx,
        top: 24.rpx,
        right: 14.rpx,
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _renderTitle(processInfo),
          SizedBox(height: 8.rpx),
          _renderRemark(processInfo?.description),
        ],
      ),
    );
  }

  Widget _renderTitle(ProcessInfo? processInfo) {
    return Container(
      child: Row(
        children: [
          ClipOval(
            child: Container(
              height: 10.rpx,
              width: 10.rpx,
              color: Color(0xffD4D8DD),
            ),
          ),
          SizedBox(width: 10.rpx),
          Expanded(
            child: Text(
              "${processInfo?.createdTime ?? ""} ${processInfo?.user?.name ?? ""} ${processInfo?.user?.phone ?? ""}",
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
              style: TextStyle(
                color: Color(0xff55595C),
                fontSize: 28.rpx,
                height: 1.2,
                fontWeight: FontWeight.w500,
              ),
            ),
          ),
          Container(
            height: 38.rpx,
            child: TextButton(
              onPressed: () {},
              style: TextButton.styleFrom(
                padding: EdgeInsets.zero,
                minimumSize: Size(52.rpx, 38.rpx),
              ),
              child: Text(
                "删除",
                style: TextStyle(
                  color: Color(0xffFE2D1E),
                  fontSize: 26.rpx,
                  height: 1.2,
                  fontWeight: FontWeight.w500,
                ),
              ),
            ),
          )
        ],
      ),
    );
  }

  Widget _renderRemark(String? remark) {
    return Container(
      padding: EdgeInsets.only(left: 20.rpx),
      child: Text(
        remark ?? "--",
        style: TextStyle(
          color: Color(0xff696C70),
          fontSize: 26.rpx,
          height: 1.2,
        ),
      ),
    );
  }


}

HandleProcessView中的数据发生变化时要通知ExpandablePageView

 void _refresh() {
    if (mounted) {
      setState(() {});
      widget.tabKeyMixin?.currentState?.onMixinNotify();
    }
  }

ExpandablePageView中,通过使用with WorkLogStateMixinWorkLogStateMixin mixin注入到类中,以扩展其功能。WorkLogStateMixin mixin定义了两个方法:onMixinNotifyonMixinTabChanged

  1. onMixinNotify方法:该方法在接收到通知时被调用,用于更新ExpandablePageView的UI。在方法内部,通过调用setState方法来触发UI的重新构建。首先,它检查组件是否已经挂载,使用mounted属性进行判断,以避免在组件已被销毁后执行setState
  2. onMixinTabChanged方法:该方法在选项卡变化时被调用,用于处理选项卡的切换逻辑。在当前的实现中,它直接调用了_handleOnChange方法,而被注释的代码块表明原本使用了setState来触发UI的重新构建。_handleOnChange方法可能执行一些与选项卡变化相关的操作。

通过使用WorkLogStateMixin mixin,ExpandablePageView能够与WorkLogStateMixin的通知和选项卡变化进行交互,并根据需要更新自身的UI。这样,当WorkLogStateMixin发出通知或选项卡发生变化时,ExpandablePageView会相应地进行更新,以保持与其他组件的交互和数据的同步。

class ExpandablePageView extends StatefulWidget {
  final List<Widget> children;
  final Function(int index)? onPageChanged;
  const ExpandablePageView({
    Key? key,
    required this.children,
    this.onPageChanged,
  }) : super(key: key);

  @override
  State<ExpandablePageView> createState() => _ExpandablePageViewState();
}

class _ExpandablePageViewState extends State<ExpandablePageView>
    with TickerProviderStateMixin, WorkLogStateMixin {
  late PageController _pageController;
  late List<double> _heights;
  int _currentPage = 0;

  double get _currentHeight => _heights[_currentPage];

  @override
  void initState() {
    _heights = widget.children.map((e) => 0.0).toList();
    super.initState();
    _pageController = PageController()
      ..addListener(() {
        final newPage = _pageController.page?.round() ?? 0;
        if (_currentPage != newPage) {
          setState(() => _currentPage = newPage);
        }
      });
  }

  @override
  void onMixinNotify() {
    if (mounted) setState(() {});
  }

  @override
  void onMixinTabChanged(int index) {
    // setState(() {
    //   _handleOnChange(index);
    // });
    _handleOnChange(index);
  }

  /// 当切换Tab时
  void _handleOnChange(
    int index,
  ) {
    ///
    _pageController.jumpToPage(index);
    // _currentIndex = index;
  }

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TweenAnimationBuilder<double>(
      curve: Curves.easeInOutCubic,
      duration: const Duration(milliseconds: 100),
      tween: Tween<double>(begin: _heights[0], end: _currentHeight),
      builder: (context, value, child) => SizedBox(height: value, child: child),
      child: PageView(
        controller: _pageController,
        physics: const NeverScrollableScrollPhysics(),
        children: _sizeReportingChildren
            .asMap() //
            .map((index, child) => MapEntry(index, child))
            .values
            .toList(),
      ),
    );
  }

  List<Widget> get _sizeReportingChildren => widget.children
      .asMap() //
      .map(
        (index, child) => MapEntry(
          index,
          OverflowBox(
            //needed, so that parent won't impose its constraints on the children, thus skewing the measurement results.
            minHeight: 0,
            maxHeight: double.infinity,
            alignment: Alignment.topCenter,
            child: SizeReportingWidget(
              onSizeChange: (size) =>
                  setState(() => _heights[index] = size.height),
              child: Align(child: child),
            ),
          ),
        ),
      )
      .values
      .toList();
}

class SizeReportingWidget extends StatefulWidget {
  final Widget child;
  final ValueChanged<Size> onSizeChange;

  const SizeReportingWidget({
    Key? key,
    required this.child,
    required this.onSizeChange,
  }) : super(key: key);

  @override
  State<SizeReportingWidget> createState() => _SizeReportingWidgetState();
}

class _SizeReportingWidgetState extends State<SizeReportingWidget> {
  Size? _oldSize;

  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) => _notifySize());
    return widget.child;
  }

  void _notifySize() {
    if (!mounted) {
      return;
    }
    final size = context.size;
    if (_oldSize != size && size != null) {
      _oldSize = size;
      widget.onSizeChange(size);
    }
  }
}

具体的实现代码:

GlobalKey<WorkLogStateMixin> workKeyMixin = GlobalKey<WorkLogStateMixin>();
 ExpandablePageView(
              key: workKeyMixin,
              children: [
                HandleProcessView(
                  key: globalRefresh,
                  tabKeyMixin: workKeyMixin,
                  ticketId: 0,
                ),
                HandleLoggerView(
                  ticketId: 1,
                  tabKeyMixin: workKeyMixin,
                ),
              ],
            )

GlobalKey<WorkLogStateMixin>的作用是创建一个全局的键(key)用于在Flutter应用程序中标识WorkLogStateMixin类型的组件。

具体来说,GlobalKey是一个特殊的键(key)对象,用于唯一标识在Flutter应用程序中创建的组件。通过将GlobalKey<WorkLogStateMixin>分配给workKeyMixin变量,我们可以在整个应用程序中引用该键,并与使用相同键的组件进行通信和交互。

在这个特定的例子中,workKeyMixin用作HandleProcessView组件中的一个键(key),并通过传递给ExpandablePageViewtabKeyMixin属性,使得ExpandablePageViewHandleProcessView共享相同的键。这样,它们之间可以建立关联,并进行双向通信,例如通知和响应事件。

通过使用GlobalKey<WorkLogStateMixin>,我们可以在应用程序中的不同组件之间实现引用和通信,以实现组件之间的协作和交互。这种全局键的使用使得组件之间的连接更加灵活和方便。

嵌入PageViewListView是一项常见的需求。为了实现这一功能,我们介绍了一个重要的自定义组件——ExpandablePageViewExpandablePageView利用SizeReportingWidget获取页面内容的实际高度,并根据内容高度自适应调整列表项的高度。在这个过程中,关键的桥梁是通过使用GlobalKeyExpandablePageViewHandleProcessView之间建立通信和交互。

ExpandablePageView中,我们使用GlobalKey<WorkLogStateMixin>作为全局键,用于标识组件。这样,我们可以在应用程序中引用并与使用相同键的组件进行通信。通过共享相同的键,ExpandablePageViewHandleProcessView能够建立关联,并实现双向通信。

此外,ExpandablePageView实现了WorkLogStateMixin中的两个方法:onMixinNotifyonMixinTabChanged。这些方法通过调用setState来触发UI的更新,以保持与HandleProcessView的交互和数据的同步。当HandleProcessView中的数据发生变化时,它会通知ExpandablePageView重新构建UI,以确保页面内容的一致性。

通过使用GlobalKeymixin,我们能够实现在嵌入的PageViewListView之间进行双向通信。这种灵活的实现方式为开发人员提供了更多自由度,使得构建复杂且交互性强的界面变得更加简单和便捷。

综上所述,掌握在Flutter中将PageView嵌入ListView并实现双向通信的技巧对于开发高级界面至关重要。通过使用GlobalKeymixin,我们能够构建高度可定制的界面,并实现不同组件之间的数据传递和交互。持续学习和掌握这些技术,我们可以开发出功能丰富且用户友好的Flutter应用程序。

由于篇幅有限今天就为大家分享到这里!!!

希望对您有所帮助谢谢!!!

转载自:https://juejin.cn/post/7250669238419423289
评论
请登录