本文章是两个flutter小组件listview的两个功能,这里做一个分享方便以后其他衍生特殊高级功能的开发,扩宽你的思路:
1.listview 中利用custom才能监听到第一个条目
new ListView.custom(
controller: controller,
cacheExtent: 1.0, // 只有设置了1.0 才能够准确的标记position 位置
childrenDelegate: MyChildrenDelegate(
(BuildContext context, int index) {
//Dismissible这个可以用作列表的横向滑动删除功能效果
return new Dismissible(
key: new Key(list[index]),
onDismissed: (direction) {
//被移除回掉
list.removeAt(index);
var item = list[index];
Scaffold.of(context).showSnackBar(
new SnackBar(content: new Text("$item")));
},
child: new ListTile(
title: new Text(list[index]),
));
},
childCount: list.length,
),
)
SliverChildBuilderDelegate
class _SaltedValueKey extends ValueKey{
const _SaltedValueKey(Key key): assert(key != null), super(key);
}
class MyChildrenDelegate extends SliverChildBuilderDelegate {
MyChildrenDelegate(
Widget Function(BuildContext, int) builder, {
int childCount,
bool addAutomaticKeepAlive = true,
bool addRepaintBoundaries = true,
}) : super(builder,
childCount: childCount,
addAutomaticKeepAlives: addAutomaticKeepAlive,
addRepaintBoundaries: addRepaintBoundaries);
// Return a Widget for the given Exception
Widget _createErrorWidget(dynamic exception, StackTrace stackTrace) {
final FlutterErrorDetails details = FlutterErrorDetails(
exception: exception,
stack: stackTrace,
library: 'widgets library',
context: ErrorDescription('building'),
);
FlutterError.reportError(details);
return ErrorWidget.builder(details);
}
@override
Widget build(BuildContext context, int index) {
assert(builder != null);
if (index < 0 || (childCount != null && index >= childCount))
return null;
Widget child;
try {
child = builder(context, index);
} catch (exception, stackTrace) {
child = _createErrorWidget(exception, stackTrace);
}
if (child == null)
return null;
final Key key = child.key != null ? _SaltedValueKey(child.key) : null;
if (addRepaintBoundaries)
child = RepaintBoundary(child: child);
if (addSemanticIndexes) {
final int semanticIndex = semanticIndexCallback(child, index);
if (semanticIndex != null)
child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child);
}
if (addAutomaticKeepAlives)
child = AutomaticKeepAlive(child: child);
return KeyedSubtree(child: child, key: key);
}
@override
void didFinishLayout(int firstIndex, int lastIndex) {
// TODO: implement didFinishLayout
super.didFinishLayout(firstIndex, lastIndex);
}
///监听 在可见的列表中 显示的第一个位置和最后一个位置
@override
double estimateMaxScrollOffset(int firstIndex, int lastIndex, double leadingScrollOffset, double trailingScrollOffset) {
print('firstIndex sss : $firstIndex, lastIndex ssss : $lastIndex, leadingScrollOffset ssss : $leadingScrollOffset,'
'trailingScrollOffset ssss : $trailingScrollOffset ' );
return super.estimateMaxScrollOffset(firstIndex, lastIndex, leadingScrollOffset, trailingScrollOffset);
}
}
当然这个功能用上面的说的功能也可以实现,但是有便捷的代码为什么不用呢,哈哈。下面就贴上我的代码(代码量非常简洁,这里就不放git库了,开源给大家看了,直接拷贝进项目就可以使用了)。
/*
* @Author: Cao Shixin
* @Date: 2021-02-22 14:47:01
* @LastEditors: Cao Shixin
* @LastEditTime: 2021-02-23 10:56:57
* @Description: 可自带偏移滚动列表
* @Email: [email protected]
* @Company: BrainCo
*/
import 'dart:math';
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class AutoOffsetListView extends StatefulWidget {
//滚动方位
Axis scrollDirection;
//内部显示的item数组
List items;
AutoOffsetListView(
{this.items, this.scrollDirection = Axis.vertical, Key key})
: super(key: key) {
items ??= [];
scrollDirection ??= Axis.vertical;
}
@override
_AutoOffsetListViewState createState() => _AutoOffsetListViewState();
}
class _AutoOffsetListViewState extends State
with WidgetsBindingObserver {
ScrollController _controller;
Point _contentCenter;
@override
void initState() {
super.initState();
_controller = ScrollController();
}
@override
Widget build(BuildContext context) {
var contentSize = MediaQuery.of(context).size;
_contentCenter = Point(contentSize.width / 2, contentSize.height / 2);
return ListView.builder(
scrollDirection: widget.scrollDirection,
controller: _controller,
cacheExtent: 1.0, // 只有设置了1.0 才能够准确的标记position 位置
itemBuilder: (BuildContext context, int index) {
return AutoOffsetListItemView(
item: widget.items[index],
centerBack: (center) {
var _offset = _controller.offset;
if (widget.scrollDirection == Axis.horizontal) {
_offset += (center.x - _contentCenter.x);
} else {
_offset += (center.y - _contentCenter.y);
}
if (_offset < 0) {
_offset = 0;
} else if (_offset > _controller.position.maxScrollExtent) {
_offset = _controller.position.maxScrollExtent;
}
_controller.animateTo(_offset,
duration: Duration(
milliseconds: 200,
),
curve: Curves.ease);
});
},
itemCount: widget.items?.length ?? 0,
);
}
}
typedef WidgetCenterBack = Function(Point center);
// ignore: must_be_immutable
class AutoOffsetListItemView extends StatefulWidget {
WidgetCenterBack centerBack;
AutoOffsetListItem item;
AutoOffsetListItemView({this.item, this.centerBack});
@override
_AutoOffsetListItemViewState createState() => _AutoOffsetListItemViewState();
}
class _AutoOffsetListItemViewState extends State {
@override
Widget build(BuildContext context) {
return InkWell(
child: AbsorbPointer(
child: widget.item.child,
),
onTap: _scrollExcursion,
);
}
void _scrollExcursion() {
RenderObject renderObject = context.findRenderObject();
//获取元素尺寸
Rect rect = renderObject.paintBounds;
//获取元素位置
var vector3 = renderObject.getTransformTo(null)?.getTranslation();
if (widget.centerBack != null) {
widget.centerBack(Point(
vector3.x + rect.size.width / 2, vector3.y + rect.size.height / 2));
}
if (widget.item?.onTap != null) {
widget.item.onTap();
}
}
}
class AutoOffsetListItem {
//这里widget设置的点击事件无效,如果想使用点击效果,实现下面的onTap即可
Widget child;
//点击区域的回调,不使用可以不用实现
VoidCallback onTap;
AutoOffsetListItem(this.child, {this.onTap});
}
class _SaltedValueKey extends ValueKey {
const _SaltedValueKey(Key key)
: assert(key != null),
super(key);
}
使用示例:
AutoOffsetListView(
items: _list,
scrollDirection: Axis.horizontal,
),
注意,这个里面是一个listview,不要说你外面不加上一个宽或者高来限制(横向展示列表你就加一个container高限制,纵向展示你就加一个contain宽限制,或者宽都加也适合),用我的组件AutoOffsetListView报错,你不限制直接用listview也是会错的!(不是我的代码错误,哈哈)。
最后祝你使用愉快,财源广进,健康平安。