美文网首页Flutter圈子Flutter中文社区Flutter
Flutter 仿探探编辑页图片展示控件

Flutter 仿探探编辑页图片展示控件

作者: 头发还没秃 | 来源:发表于2020-01-08 09:16 被阅读0次

    PS:页面所有图片均来自涂鸦智能平台

    功能点

    • 布局:Flow
    • 拖拽排序:Draggable + DragTarget

    代码少,逻辑比较简单,注释也比较详细,我就直接上代码了

    暴露给使用者的属性

    class TTAlbumLayout extends StatefulWidget {
    
      TTAlbumLayout(
        this.imagePaths,
        {
          this.onMoveCompleted,
          this.onTap,
          this.onLongPress,
          this.onAddImageTap,
          this.size,
          double spacing,
          double margin,
        }
      ): _margin = margin == null ? 10 : margin,
         _spacing = spacing == null ? 10 : spacing;
    
      // 图片地址,布局最多只有6个位置,
      // imagePaths.length > 6,取前6个值
      final List<String> imagePaths;
    
      // 布局长宽
      final double size;
    
      // Item与Item之间的间距,横向纵向一样的值
      final double _spacing;
    
      // 外边距,Item距离布局四边的距离
      final double _margin;
    
      // 拽拽成功回调,返回所拖拽Item拖拽前和拖拽后的索引
      final OnMoveCompleted onMoveCompleted;
    
      // 点击回调,返回当前点击Item的索引
      final ValueChanged<int> onTap;
    
      // 长按回调,返回当前长按Item的索引
      final ValueChanged<int> onLongPress;
    
      // 添加图片回调,无返回值
      final OnAddImageTap onAddImageTap;
    
      @override
      _TTAlbumLayoutState createState() => _TTAlbumLayoutState();
    }
    

    _TTAlbumLayoutState

    class _TTAlbumLayoutState extends State<TTAlbumLayout> {
    
      List<String> get _imagePaths => widget.imagePaths;
      // 正在拖拽的Item当前的位置
      int _moveIndex = -1;
    
      @override
      Widget build(BuildContext context) {
    
        // 布局长宽,不设置或者设置的值小于
        // (widget.margin*2 + widget.spacing*2)*2,
        // 则使用屏幕宽度作为布局的长宽
        double _size = widget.size;
        if (widget.size == null || widget.size < (widget._margin*2 + widget._spacing*2)*2) {
          _size = MediaQuery.of(context).size.width;
        }
    
        return Flow(
          delegate: _FlowDelegate(
            size: _size,
            spacing: widget._spacing,
            margin: widget._margin,
          ),
          children: _buildItems(
            _imagePaths,
            _size,
            widget._spacing,
            widget._margin,
          ),
        );
      }
    
      // 创建Item,先计算好每个Item的长宽
      List<Widget> _buildItems(List<String> imagePaths, double size, double spacing, double margin) {
        List<Widget> widgets = List();
        for (var i = 0; i < 6; i++) {
          // 小Item的长宽
          var imageWidth = (size - margin - margin - spacing * 2)*1/3;
          if (i == 0) {
            // 第一个大Item的长宽
            imageWidth = (size - margin - margin - spacing) - imageWidth;
          }
          // 填充Item,当需要展示图片小于6张时,多余的位置填充添加按钮
          if (imagePaths.length > 0 && i < imagePaths.length) {
            widgets.add(
                _buildDraggableWidget(i, imageWidth, imagePaths[i])
            );
          } else {
            widgets.add(
                _buildAddImageWidget(i, imageWidth)
            );
          }
        }
        return widgets;
      }
    
      // 拖拽
      Widget _buildDraggableWidget(int index, double imageWidth, String imagePath) {
        return Draggable(
          data: index,
          child: _buildDragTargetWidget(index, imageWidth, imagePath),
          feedback: _buildImageWidget(index, index == 0 ? imageWidth/4 : imageWidth/2, imagePath),
          childWhenDragging: _moveIndex == index ? Container(width: imageWidth, height: imageWidth,) : null,
          onDragStarted: () {
    //        PrintUtil.log({"拖动":"开始"});
            setState(() {
              _moveIndex = index;
            });
          },
          onDragCompleted: () {
    //        PrintUtil.log({"拖动":"成功"});
          },
          onDragEnd: (details) {
    //        PrintUtil.log({"拖动":"结束 details = $details"});
            setState(() {
              _moveIndex = -1;
            });
          },
          onDraggableCanceled: (velocity, offset) {
    //        PrintUtil.log({"拖动":"取消 velocity = $velocity, offset = $offset"});
          },
        );
      }
    
      // 拖拽事件接收
      Widget _buildDragTargetWidget(int index, double imageWidth, String imagePath) {
        return DragTarget(
          builder: (context, candidateData, rejectedData) {
            return _buildImageWidget(index, imageWidth, imagePath);
          },
          onWillAccept: (int moveIndex) {
    //        PrintUtil.log({"拖拽接收":"正在移动 $moveIndex, 经过 $index"});
            _moveIndex = index;
            sortItem(moveIndex, index);
            return true;
          },
          onAccept: (int moveIndex) {
    //        PrintUtil.log({"拖拽接收":"正在移动 $moveIndex, 松手 $index"});
            if (widget.onMoveCompleted != null) {
              widget.onMoveCompleted(moveIndex, index);
            }
          },
          onLeave: (int moveIndex) {
            _moveIndex = moveIndex;
            sortItem(index, moveIndex);
    //        PrintUtil.log({"拖拽接收":"正在移动 $moveIndex, 经过并已离开 $index"});
          },
        );
      }
    
      // 图片展示Item
      Widget _buildImageWidget(int index, double imageWidth, String imagePath) {
        return index == _moveIndex ? Container(width: imageWidth, height: imageWidth,) : GestureDetector(
          onTap: () {
    //        PrintUtil.log({"点击":"查看图片$index"});
            if (widget.onTap != null) {
              widget.onTap(index);
            }
          },
          onLongPress: () {
    //        PrintUtil.log({"长按":"删除图片$index"});
            if (widget.onLongPress != null) {
              widget.onLongPress(index);
            }
          },
          child: Material(
            color: Colors.lightGreen,
            child: Image.network(imagePath, width: imageWidth,
              height: imageWidth,
              fit: BoxFit.contain,),
          ),
        );
      }
    
      // 添加图片Item
      Widget _buildAddImageWidget(int index, double imageWidth) {
        return GestureDetector(
          onTap: () {
    //        PrintUtil.log({"点击":"添加图片$index"});
            if (widget.onAddImageTap != null) {
              widget.onAddImageTap();
            }
          },
          child: Container(
            alignment: Alignment.center,
            color: Colors.lightGreen,
            width: imageWidth,
            height: imageWidth,
            child: Icon(
              Icons.add,
              color: Colors.black54,
              size: imageWidth/2,
            ),
          ),
        );
      }
    
      // Item拖拽排序
      void sortItem(int moveIndex, int toIndex) {
        setState(() {
          String value = _imagePaths[moveIndex];
          _imagePaths.removeAt(moveIndex);
          _imagePaths.insert(toIndex, value);
        });
      }
    }
    

    _FlowDelegate

    Flow 通过 FlowDelegate 设置Item的位置和设置 Flow 布局的长宽

    class _FlowDelegate extends FlowDelegate {
    
      _FlowDelegate(
        {
          this.size,
          this.spacing,
          this.margin,
        }
      );
    
      final double size;
      final double spacing;
      final double margin;
    
      @override
      void paintChildren(FlowPaintingContext context) {
        for (int i = 0; i < context.childCount; i++) {
          if (i == 0) {
            context.paintChild(i,
              transform: new Matrix4.translationValues(
                margin,
                margin,
                0.0,
              ),
            );
          } else if (i == 1) {
            context.paintChild(i,
              transform: new Matrix4.translationValues(
                context.getChildSize(0).width + margin + spacing,
                margin,
                0.0,
              ),
            );
          } else if (i == 2) {
            context.paintChild(i,
              transform: new Matrix4.translationValues(
                context.getChildSize(0).width + margin + spacing,
                margin + context.getChildSize(1).width + spacing,
                0.0,
              ),
            );
          } else if (i == 3) {
            context.paintChild(i,
              transform: new Matrix4.translationValues(
                margin + context.getChildSize(0).width + spacing,
                margin + context.getChildSize(0).width + spacing,
                0.0,
              ),
            );
          } else if (i == 4) {
            context.paintChild(i,
              transform: new Matrix4.translationValues(
                margin + context.getChildSize(3).width + spacing,
                margin + context.getChildSize(0).width + spacing,
                0.0,
              ),
            );
          } else if (i == 5) {
            context.paintChild(i,
              transform: new Matrix4.translationValues(
                margin,
                margin + context.getChildSize(0).width + spacing,
                0.0,
              ),
            );
          }
        }
      }
    
      @override
      bool shouldRepaint(FlowDelegate oldDelegate) {
        return oldDelegate != this;
      }
    
      // 是否需要重新布局
      @override
      bool shouldRelayout(FlowDelegate oldDelegate) {
        return super.shouldRelayout(oldDelegate);
      }
    
      // 设置Flow的尺寸
      @override
      Size getSize(BoxConstraints constraints) {
        return constraints.constrain(Size(this.size, this.size));
    //    Size _preSize = super.getSize(constraints);
    //    Size _size = Size(_preSize.width, _preSize.width);
    //    return _size;
      }
    
      // 设置每个child的布局约束条件
      @override
      BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) {
        return super.getConstraintsForChild(i, constraints);
      }
    
    }
    

    使用

    List<String> imagePaths = [
      "https://images.tuyacn.com/smart/rule/cover/starry.png",
      "https://images.tuyacn.com/smart/rule/cover/house5.png",
      "https://images.tuyacn.com/smart/rule/cover/work.png",
      "https://images.tuyacn.com/smart/rule/cover/house6.png",
    ];
    
    TTAlbumLayout(
        imagePaths,
    //  size: 300,
    //  margin: 10,
    //  spacing: 10,
        onMoveCompleted: (int moveIndex, int toIndex) {
            Toast.text(context, "拖拽排序成功:moveIndex = $moveIndex, toIndex = $toIndex");
            // 注意:数据源在拖拽排序成功之后已经改变,所以这里不需要再手动去修改
    //      String value = imagePaths[moveIndex];
    //      imagePaths.removeAt(moveIndex);
    //      imagePaths.insert(toIndex, value);
         },
         onTap: (int index) {
             Toast.text(context, "点击查看图片$index");
         },
         onLongPress: (int index) {
             Toast.text(context, "长按删除图片$index");
         },
         onAddImageTap: () {
             Toast.text(context, "点击添加图片");
             setState(() {
                 imagePaths.add("https://images.tuyacn.com/smart/rule/cover/house.png");
             });
         },
    )
    

    相关文章

      网友评论

        本文标题:Flutter 仿探探编辑页图片展示控件

        本文链接:https://www.haomeiwen.com/subject/vcpjactx.html