自定义CustomscrollView

405 阅读1分钟
 _       _____   __   _   _____   _   _  __   __
| |     | ____| |  \ | | /  ___/ | | / /  \ \  / /
| |     | |__   |    | | | |___  | |/ /    \ / /
| |     |  __|  | | \   | ___  \ | |\ \     \  /
| |___  | |___  | | \  |  ___| | | | \ \    / /
|_____| |_____| |_|  _| /_____/ |_|  _\  /_/       

对于很多时候要用到滚动视图,渲染快速要用到 build index 方法 但是 每次都写很麻烦,于是就参照 iOS 方式简化代码步骤 整个分区来实现。

将需要添加的对象实现 TableViewDelegate 内的方法就可以实现简单的 scrollView 动态吸顶效果等 需要自己去拓展吧。

tableview 套 grid 的方法不是很好凑合着 慢慢去更新.

/*
* ***************************************************************
* 获取列表 tableView();
* 设置 sectionPadding() 设置的是区的边距    sliverPadding()  设置的是头部监听部分
* ***************************************************************
* 列表样式                            对应方法
* ***************************************************************
* ----------- SliverHeader -------  loadSliverHeader()  实现此方法  实现根据滚动变化的头部 可为空
*
* -----------tableHeader ---------   tableHeader()   表头
*
*  -------sectionHeader 区 头------   sectionHeader(section)
*  -------------------------------
*  |            list 区          |    rowCount(section)   listCell  数量
*  |            listCell         |    cellWithIndexPath(indexPath)  listCell
*  |            listCell         |    getSectionType(section) SectionType.list 默认为list
*  -------------------------------
* -------------- 区 尾 ------------    sectionFooter(section)
* -------------- 区 头 ------------
*  -------------------------------
*  |            grid区            |     getSectionType(section) SectionType.grid
*  |      gridItem  |  gridItem   |     getRowCount(section)   gridItem 数量
*  |      gridItem  |  gridItem   |
* ---------------------------------
*  ------------- 区尾 --------------
*  -------------- 区 头 ------------
*  -------------------------------
*  |            grid区            |    gridHorizontalCount(section) 横向排布数量
*  |     Item| Item  |   Item     |    gridVerticalSpacing(section) 纵向间距
*  |     Item| Item  |   Item     |    gridHorizontalSpacing(section) 横向间距
*  |                              |    gridAspectRatio(section) 宽高比例
* ---------------------------------
* ------------- 区尾 --------------
*
* --------------tableFooter--------    tableFooter()  表尾
* */
///使用方法
child:tableView(delegate:this),
//每个区有多少行
getRow(IndexPath indexPath)=> 10;

///类似iOS 的避免初学者觉得是套娃
Widget cellWithIndexPath(IndexPath indexPath){
    return Container();
}

滚动视图摘抄 常用使用方式

import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';

class TableView extends CustomScrollView {

  TableView({
    DragStartBehavior? dragStartBehavior,
   required List<Widget> slivers,
    ScrollController? scrollController,
  }):super(
    dragStartBehavior:dragStartBehavior?? DragStartBehavior.start,
    slivers:slivers,
    keyboardDismissBehavior:ScrollViewKeyboardDismissBehavior.manual,
    physics:  AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()),
    controller: scrollController,
  );

  static CustomScrollView tableView({required TableViewDelegate delegate,ScrollController? scrollController}){

    var sliverList = <Widget>[];

    var sliverHeader = delegate.loadSliverHeader();
    ///可伸缩的头部部分
    if(sliverHeader != null){
      var slh = SliverPadding(
        padding: delegate.sliverPadding(),
        sliver: sliverHeader,
      );
      sliverList.add(slh);
    }
    /// 伸缩头下面的唯一的一个头
    var tbHeader = delegate._getTableHeader();
    sliverList.add(tbHeader);

    /// 加载所有的区间内容
    for(var i = 0;i< delegate.getSection(); i++){

      var sliverHeader =delegate.loadSliverSectionHeader(i); //在中间某个区添加悬浮区头
      if(sliverHeader != null){
        sliverList.add(sliverHeader);  // 加载悬停式区头
      }
      var header = delegate._getSectionHeader(i);
      /// 加载区头
      sliverList.add(header);
      ///加载当前区的list 或 grid 控件 这时候只是加载到区,并没有加载任何 cell 默认加载的是 list 格式的
      if(delegate.getSectionType(i) == SectionType.grid){
        var grid =delegate. _getSliverGrid(i);// 中间嵌套grid 的情况
        sliverList.add(grid);
      }else{
        var list =delegate._getSliverList(i);
        sliverList.add(list);
      }
      /// 加载区尾内容
      var footer = delegate._getSectionFooter(i);
      sliverList.add(footer);
    }
    /// 加载表尾
    var tbFooter = delegate._getTableFooter();
    sliverList.add(tbFooter);
    return TableView(
      slivers: sliverList,
      scrollController: scrollController,
    );
  }
}

mixin TableViewDelegate {
  
  SectionType? getSectionType(section){
    return null;
  }

  EdgeInsets sectionPadding(int section){
    return EdgeInsets.zero;
  }

  _getSliverList(int section){
    return SliverPadding(
      padding: sectionPadding(section),
      sliver:SliverList(
        delegate: SliverChildBuilderDelegate((context, index){
          return cellWithIndexPath(IndexPath(section: section, row: index));
        },childCount: getRowCount(section)),
      ),
    );
  }
  
  /// 当嵌套有 grid 情况设置
  Widget gridCell(IndexPath indexPath){
    return Container();
  }

  ///当前区间横向排布数量
  int gridHorizontalCount(int section){
    return 2;
  }

  /// 纵向间距
  double gridVerticalSpacing(section){
    return  5;
  }

  /// 横向间距
  double gridHorizontalSpacing(section){
    return  5;
  }

  /// 宽高比例
  double gridAspectRatio(section){
    return 1;//宽高比例
  }

  _getSliverGrid(int section){
    return  SliverPadding(
      padding: sectionPadding(section),
      sliver: SliverGrid(
        delegate: SliverChildBuilderDelegate((context, index){
          return gridCell(IndexPath(section: section, row: index));
        },childCount: getRowCount(section),),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: gridHorizontalCount(section), //横向数量
          mainAxisSpacing: gridVerticalSpacing(section),  //纵向间距
          crossAxisSpacing: gridHorizontalSpacing(section),  //纵向宽度
          childAspectRatio: gridAspectRatio(section),
        ),
      ),
    );
  }

  SliverToBoxAdapter _getSectionHeader(int section){
    return SliverToBoxAdapter(
      child: sectionHeader(section)??Container(),
    );
  }
  SliverToBoxAdapter _getSectionFooter(int section){
    return SliverToBoxAdapter(
      child: sectionFooter(section)??Container(),
    );
  }
  SliverToBoxAdapter _getTableFooter(){
    return SliverToBoxAdapter(
      child: tableFooter()??Container(),
    );
  }
  SliverToBoxAdapter _getTableHeader(){
    return SliverToBoxAdapter(
      child: tableHeader()??Container(),
    );
  }

  /// 这里需要一种类似 demo15 中 的能够监听到变化伸缩的头部控件
  SliverPersistentHeader getSliverHeader(TableHeaderDelegate delegate,{bool pinned = false,bool floating = false}){
    return SliverPersistentHeader(delegate: delegate,pinned: pinned,floating: floating,);
  }

  /// 加载可伸缩式的头部部分 默认是空的 需要使用  getSliverHeader(---) 获取该方法
  SliverPersistentHeader? loadSliverHeader(){
    return null;
  }
  SliverPersistentHeader? loadSliverSectionHeader(int section){
    return null;
  }
  //头部的边距
  EdgeInsets sliverPadding(){
    return EdgeInsets.zero;
  }

  /// ListView 实现方法
  Widget cellWithIndexPath(IndexPath indexPath){
    return Container();
  }

  int getRowCount(section){
    return 0;
  }
  /// 实现方法 默认为 1
  int getSection(){
    return 1;
  }
  /// 区头 子类可选实现
  Widget? sectionHeader(section){
    return null;
  }
  /// 区尾
  Widget? sectionFooter(section){
    return null;
  }
  /// 表头
  Widget? tableHeader(){
    return null;
  }
  /// 表尾
  Widget? tableFooter(){
    return null;
  }
}
//TODO: ListView 索引 section + row
class IndexPath{
  int section;
  int row;
  IndexPath({
    required this.section,
    required this.row
  });
  @override
  String toString() {
    List;
    return "section : ${section}   row : ${row}";
  }

  IndexPath operator +(IndexPath a){
    return IndexPath(section: a.section + section, row: a.row + row);
  }
  IndexPath operator -(IndexPath a){
    return IndexPath(section: section-a.section, row: row-a.row);
  }
}
///列表区间类型
enum SectionType {
  list,
  grid,
}
/// shrinkOffset 变化的偏移量   overlapsContent 是否有遮盖部分
typedef Widget TableViewHeaderBuilder (BuildContext context, double shrinkOffset, bool overlapsContent);
/// 可伸缩式表头代理类
class TableHeaderDelegate extends SliverPersistentHeaderDelegate {
  final double maxHeight;
  final double minHeight;
  final TableViewHeaderBuilder builder;
  bool? rebuild; //是否允许重复 build 默认是yes
  Widget? contentItem;
  TableHeaderDelegate({
    required this.maxHeight,
    required this.minHeight,
    required this.builder,
    this.rebuild,
  });
  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    // overlapsContent  是否有重叠
    // shrinkOffset     减小的大小

    if(contentItem != null&&rebuild == false){ // 降低内容重绘频率 在需要时 使用 stateFullBuild 进行操作
      return contentItem!;
    }else{
      contentItem = SizedBox.expand(child: builder(context,shrinkOffset,overlapsContent),);
      return contentItem!;
    }
  }

  @override
  // TODO: implement maxExtent
  double get maxExtent =>  (minHeight > maxHeight)?minHeight:maxHeight;

  @override
  // TODO: implement minExtent
  double get minExtent => minHeight;

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    return true; //当前状态是否允许 ReBuilder
  }
}