实现在Flutter中将PageView嵌入ListView并实现双向通信
本文将探讨如何实现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 WorkRefreshStateMixin
将WorkRefreshStateMixin
mixin注入到类中,以扩展其功能。其中,WorkRefreshStateMixin
提供了一个抽象方法onMixinRefresh
,用于处理刷新操作。
在ListPageDemo
类中,当需要触发HandleProcessView
的刷新事件时,可以通过globalRefresh
的currentState
来调用onMixinRefresh
方法。具体而言,可以使用globalRefresh.currentState?.onMixinRefresh()
来执行刷新逻辑。
这种方式实现了HandleProcessView
和ListPageDemo
之间的双向通信。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 WorkLogStateMixin
将WorkLogStateMixin
mixin注入到类中,以扩展其功能。WorkLogStateMixin
mixin定义了两个方法:onMixinNotify
和onMixinTabChanged
。
onMixinNotify
方法:该方法在接收到通知时被调用,用于更新ExpandablePageView
的UI。在方法内部,通过调用setState
方法来触发UI的重新构建。首先,它检查组件是否已经挂载,使用mounted
属性进行判断,以避免在组件已被销毁后执行setState
。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),并通过传递给ExpandablePageView
的tabKeyMixin
属性,使得ExpandablePageView
和HandleProcessView
共享相同的键。这样,它们之间可以建立关联,并进行双向通信,例如通知和响应事件。
通过使用GlobalKey<WorkLogStateMixin>
,我们可以在应用程序中的不同组件之间实现引用和通信,以实现组件之间的协作和交互。这种全局键的使用使得组件之间的连接更加灵活和方便。
嵌入PageView
到ListView
是一项常见的需求。为了实现这一功能,我们介绍了一个重要的自定义组件——ExpandablePageView
。ExpandablePageView
利用SizeReportingWidget
获取页面内容的实际高度,并根据内容高度自适应调整列表项的高度。在这个过程中,关键的桥梁是通过使用GlobalKey
在ExpandablePageView
和HandleProcessView
之间建立通信和交互。
在ExpandablePageView
中,我们使用GlobalKey<WorkLogStateMixin>
作为全局键,用于标识组件。这样,我们可以在应用程序中引用并与使用相同键的组件进行通信。通过共享相同的键,ExpandablePageView
和HandleProcessView
能够建立关联,并实现双向通信。
此外,ExpandablePageView
实现了WorkLogStateMixin
中的两个方法:onMixinNotify
和onMixinTabChanged
。这些方法通过调用setState
来触发UI的更新,以保持与HandleProcessView
的交互和数据的同步。当HandleProcessView
中的数据发生变化时,它会通知ExpandablePageView
重新构建UI,以确保页面内容的一致性。
通过使用GlobalKey
和mixin
,我们能够实现在嵌入的PageView
和ListView
之间进行双向通信。这种灵活的实现方式为开发人员提供了更多自由度,使得构建复杂且交互性强的界面变得更加简单和便捷。
综上所述,掌握在Flutter中将PageView
嵌入ListView
并实现双向通信的技巧对于开发高级界面至关重要。通过使用GlobalKey
和mixin
,我们能够构建高度可定制的界面,并实现不同组件之间的数据传递和交互。持续学习和掌握这些技术,我们可以开发出功能丰富且用户友好的Flutter应用程序。
由于篇幅有限今天就为大家分享到这里!!!
希望对您有所帮助谢谢!!!
转载自:https://juejin.cn/post/7250669238419423289