Flutter ListView获取页面显示的第一个和最后一个item、点击item自动偏移居中ListView

本文章是两个flutter小组件listview的两个功能,这里做一个分享方便以后其他衍生特殊高级功能的开发,扩宽你的思路:

一、ListView获取页面显示的第一个和最后一个item

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);
  }
}

 

二、listview点击item自动居中

当然这个功能用上面的说的功能也可以实现,但是有便捷的代码为什么不用呢,哈哈。下面就贴上我的代码(代码量非常简洁,这里就不放git库了,开源给大家看了,直接拷贝进项目就可以使用了)。

/*
 * @Author: Cao Shixin
 * @Date: 2021-02-22 14:47:01
 * @LastEditors: Cao Shixin
 * @LastEditTime: 2021-02-23 10:56:57
 * @Description: 可自带偏移滚动列表
 * @Email: cao_shixin@yahoo.com
 * @Company: BrainCo
 */

import 'dart:math';

import 'package:flutter/material.dart';

// ignore: must_be_immutable
class AutoOffsetListView extends StatefulWidget {
  //滚动方位
  Axis scrollDirection;
  //内部显示的item数组
  List<AutoOffsetListItem> items;

  AutoOffsetListView(
      {this.items, this.scrollDirection = Axis.vertical, Key key})
      : super(key: key) {
    items ??= <AutoOffsetListItem>[];
    scrollDirection ??= Axis.vertical;
  }

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

class _AutoOffsetListViewState extends State<AutoOffsetListView>
    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<AutoOffsetListItemView> {
  @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<Key> {
  const _SaltedValueKey(Key key)
      : assert(key != null),
        super(key);
}

使用示例:

AutoOffsetListView(
        items: _list,
        scrollDirection: Axis.horizontal,
      ),

注意,这个里面是一个listview,不要说你外面不加上一个宽或者高来限制(横向展示列表你就加一个container高限制,纵向展示你就加一个contain宽限制,或者宽都加也适合),用我的组件AutoOffsetListView报错,你不限制直接用listview也是会错的!(不是我的代码错误,哈哈)。

最后祝你使用愉快,财源广进,健康平安。

 

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值