├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── boardview.iml ├── example ├── BoardItemObject.dart ├── BoardListObject.dart └── example.dart ├── images └── example.gif ├── lib ├── board_item.dart ├── board_list.dart ├── boardview.dart └── boardview_controller.dart ├── pubspec.lock ├── pubspec.yaml └── test └── boardview_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | ios/.generated/ 9 | ios/Flutter/Generated.xcconfig 10 | ios/Runner/GeneratedPluginRegistrant.* 11 | 12 | \.idea/ 13 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.0.1] - TODO: Add release date. 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2020, Jacob Bonk 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/boardview.svg)](https://pub.dev/packages/boardview) 2 | 3 | # Flutter BoardView 4 | This is a custom widget that can create a draggable BoardView or also known as a kanban. The view can be reordered with drag and drop. 5 | 6 | ## Installation 7 | Just add ``` boardview ``` to the ``` pubspec.yaml ``` file. 8 | 9 | ## Usage Example 10 | 11 | To get started you can look inside the ``` /example``` folder. This package is broken into 3 core parts 12 | 13 | ![Example](https://github.com/jakebonk/FlutterBoardView/blob/master/images/example.gif?raw=true) 14 | 15 | ### BoardView 16 | 17 | The BoardView class takes in a List of BoardLists. It can also take in a BoardViewController which is can be used to animate to positions in the BoardView 18 | 19 | ``` dart 20 | 21 | BoardViewController boardViewController = new BoardViewController(); 22 | 23 | List _lists = List(); 24 | 25 | BoardView( 26 | lists: _lists, 27 | boardViewController: boardViewController, 28 | ); 29 | 30 | ``` 31 | 32 | ### BoardList 33 | 34 | The BoardList has several callback methods for when it is being dragged. The header item is a Row and expects a List as its object. The header item on long press will begin the drag process for the BoardList. 35 | 36 | ``` dart 37 | 38 | BoardList( 39 | onStartDragList: (int listIndex) { 40 | 41 | }, 42 | onTapList: (int listIndex) async { 43 | 44 | }, 45 | onDropList: (int listIndex, int oldListIndex) { 46 | 47 | }, 48 | headerBackgroundColor: Color.fromARGB(255, 235, 236, 240), 49 | backgroundColor: Color.fromARGB(255, 235, 236, 240), 50 | header: [ 51 | Expanded( 52 | child: Padding( 53 | padding: EdgeInsets.all(5), 54 | child: Text( 55 | "List Item", 56 | style: TextStyle(fontSize: 20), 57 | ))), 58 | ], 59 | items: items, 60 | ); 61 | 62 | ``` 63 | 64 | ### BoardItem 65 | 66 | The BoardItem view has several callback methods that get called when dragging. A long press on the item field widget will begin the drag process. 67 | 68 | ``` dart 69 | 70 | BoardItem( 71 | onStartDragItem: (int listIndex, int itemIndex, BoardItemState state) { 72 | 73 | }, 74 | onDropItem: (int listIndex, int itemIndex, int oldListIndex, 75 | int oldItemIndex, BoardItemState state) { 76 | 77 | }, 78 | onTapItem: (int listIndex, int itemIndex, BoardItemState state) async { 79 | 80 | }, 81 | item: Card( 82 | child: Padding( 83 | padding: const EdgeInsets.all(8.0), 84 | child: Text("Board Item"), 85 | ), 86 | ) 87 | ); 88 | 89 | ``` 90 | -------------------------------------------------------------------------------- /boardview.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/BoardItemObject.dart: -------------------------------------------------------------------------------- 1 | class BoardItemObject{ 2 | 3 | String? title; 4 | 5 | BoardItemObject({this.title}){ 6 | if(this.title == null){ 7 | this.title = ""; 8 | } 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /example/BoardListObject.dart: -------------------------------------------------------------------------------- 1 | import 'BoardItemObject.dart'; 2 | 3 | class BoardListObject{ 4 | 5 | String? title; 6 | List? items; 7 | 8 | BoardListObject({this.title,this.items}){ 9 | if(this.title == null){ 10 | this.title = ""; 11 | } 12 | if(this.items == null){ 13 | this.items = []; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /example/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:boardview/board_item.dart'; 2 | import 'package:boardview/board_list.dart'; 3 | import 'package:boardview/boardview_controller.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:boardview/boardview.dart'; 6 | 7 | import 'BoardItemObject.dart'; 8 | import 'BoardListObject.dart'; 9 | 10 | class BoardViewExample extends StatelessWidget { 11 | 12 | 13 | 14 | List _listData = [ 15 | BoardListObject(title: "List title 1"), 16 | BoardListObject(title: "List title 2"), 17 | BoardListObject(title: "List title 3") 18 | ]; 19 | 20 | 21 | //Can be used to animate to different sections of the BoardView 22 | BoardViewController boardViewController = new BoardViewController(); 23 | 24 | 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | List _lists = []; 29 | for (int i = 0; i < _listData.length; i++) { 30 | _lists.add(_createBoardList(_listData[i]) as BoardList); 31 | } 32 | return BoardView( 33 | lists: _lists, 34 | boardViewController: boardViewController, 35 | ); 36 | } 37 | 38 | Widget buildBoardItem(BoardItemObject itemObject) { 39 | return BoardItem( 40 | onStartDragItem: (int? listIndex, int? itemIndex, BoardItemState? state) { 41 | 42 | }, 43 | onDropItem: (int? listIndex, int? itemIndex, int? oldListIndex, 44 | int? oldItemIndex, BoardItemState? state) { 45 | //Used to update our local item data 46 | var item = _listData[oldListIndex!].items![oldItemIndex!]; 47 | _listData[oldListIndex].items!.removeAt(oldItemIndex!); 48 | _listData[listIndex!].items!.insert(itemIndex!, item); 49 | }, 50 | onTapItem: (int? listIndex, int? itemIndex, BoardItemState? state) async { 51 | 52 | }, 53 | item: Card( 54 | child: Padding( 55 | padding: const EdgeInsets.all(8.0), 56 | child: Text(itemObject.title!), 57 | ), 58 | )); 59 | } 60 | 61 | Widget _createBoardList(BoardListObject list) { 62 | List items = []; 63 | for (int i = 0; i < list.items!.length; i++) { 64 | items.insert(i, buildBoardItem(list.items![i]) as BoardItem); 65 | } 66 | 67 | return BoardList( 68 | onStartDragList: (int? listIndex) { 69 | 70 | }, 71 | onTapList: (int? listIndex) async { 72 | 73 | }, 74 | onDropList: (int? listIndex, int? oldListIndex) { 75 | //Update our local list data 76 | var list = _listData[oldListIndex!]; 77 | _listData.removeAt(oldListIndex!); 78 | _listData.insert(listIndex!, list); 79 | }, 80 | headerBackgroundColor: Color.fromARGB(255, 235, 236, 240), 81 | backgroundColor: Color.fromARGB(255, 235, 236, 240), 82 | header: [ 83 | Expanded( 84 | child: Padding( 85 | padding: EdgeInsets.all(5), 86 | child: Text( 87 | list.title!, 88 | style: TextStyle(fontSize: 20), 89 | ))), 90 | ], 91 | items: items, 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /images/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakebonk/FlutterBoardView/4761f1937d96fc3b158b6801bbbdc18b39557438/images/example.gif -------------------------------------------------------------------------------- /lib/board_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:boardview/board_list.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | 5 | typedef void OnDropItem(int? listIndex, int? itemIndex,int? oldListIndex,int? oldItemIndex, BoardItemState state); 6 | typedef void OnTapItem(int? listIndex, int? itemIndex, BoardItemState state); 7 | typedef void OnStartDragItem( 8 | int? listIndex, int? itemIndex, BoardItemState state); 9 | typedef void OnDragItem(int oldListIndex, int oldItemIndex, int newListIndex, 10 | int newItemIndex, BoardItemState state); 11 | 12 | class BoardItem extends StatefulWidget { 13 | final BoardListState? boardList; 14 | final Widget? item; 15 | final int? index; 16 | final OnDropItem? onDropItem; 17 | final OnTapItem? onTapItem; 18 | final OnStartDragItem? onStartDragItem; 19 | final OnDragItem? onDragItem; 20 | final bool draggable; 21 | 22 | const BoardItem( 23 | {Key? key, 24 | this.boardList, 25 | this.item, 26 | this.index, 27 | this.onDropItem, 28 | this.onTapItem, 29 | this.onStartDragItem, 30 | this.draggable = true, 31 | this.onDragItem}) 32 | : super(key: key); 33 | 34 | @override 35 | State createState() { 36 | return BoardItemState(); 37 | } 38 | } 39 | 40 | class BoardItemState extends State with AutomaticKeepAliveClientMixin{ 41 | late double height; 42 | double? width; 43 | 44 | @override 45 | bool get wantKeepAlive => true; 46 | 47 | void onDropItem(int? listIndex, int? itemIndex) { 48 | if (widget.onDropItem != null) { 49 | widget.onDropItem!(listIndex, itemIndex,widget.boardList!.widget.boardView!.startListIndex,widget.boardList!.widget.boardView!.startItemIndex, this); 50 | } 51 | widget.boardList!.widget.boardView!.draggedItemIndex = null; 52 | widget.boardList!.widget.boardView!.draggedListIndex = null; 53 | if(widget.boardList!.widget.boardView!.listStates[listIndex!].mounted) { 54 | widget.boardList!.widget.boardView!.listStates[listIndex].setState(() { }); 55 | } 56 | } 57 | 58 | void _startDrag(Widget item, BuildContext context) { 59 | if (widget.boardList!.widget.boardView != null) { 60 | widget.boardList!.widget.boardView!.onDropItem = onDropItem; 61 | if(widget.boardList!.mounted) { 62 | widget.boardList!.setState(() { }); 63 | } 64 | widget.boardList!.widget.boardView!.draggedItemIndex = widget.index; 65 | widget.boardList!.widget.boardView!.height = context.size!.height; 66 | widget.boardList!.widget.boardView!.draggedListIndex = 67 | widget.boardList!.widget.index; 68 | widget.boardList!.widget.boardView!.startListIndex = 69 | widget.boardList!.widget.index; 70 | widget.boardList!.widget.boardView!.startItemIndex = widget.index; 71 | widget.boardList!.widget.boardView!.draggedItem = item; 72 | if (widget.onStartDragItem != null) { 73 | widget.onStartDragItem!( 74 | widget.boardList!.widget.index, widget.index, this); 75 | } 76 | widget.boardList!.widget.boardView!.run(); 77 | if(widget.boardList!.widget.boardView!.mounted) { 78 | widget.boardList!.widget.boardView!.setState(() { }); 79 | } 80 | } 81 | } 82 | 83 | void afterFirstLayout(BuildContext context) { 84 | try { 85 | height = context.size!.height; 86 | width = context.size!.width; 87 | }catch(e){} 88 | } 89 | 90 | @override 91 | Widget build(BuildContext context) { 92 | WidgetsBinding.instance! 93 | .addPostFrameCallback((_) => afterFirstLayout(context)); 94 | if (widget.boardList!.itemStates.length > widget.index!) { 95 | widget.boardList!.itemStates.removeAt(widget.index!); 96 | } 97 | widget.boardList!.itemStates.insert(widget.index!, this); 98 | return GestureDetector( 99 | onTapDown: (otd) { 100 | if(widget.draggable) { 101 | RenderBox object = context.findRenderObject() as RenderBox; 102 | Offset pos = object.localToGlobal(Offset.zero); 103 | RenderBox box = widget.boardList!.context.findRenderObject() as RenderBox; 104 | Offset listPos = box.localToGlobal(Offset.zero); 105 | widget.boardList!.widget.boardView!.leftListX = listPos.dx; 106 | widget.boardList!.widget.boardView!.topListY = listPos.dy; 107 | widget.boardList!.widget.boardView!.topItemY = pos.dy; 108 | widget.boardList!.widget.boardView!.bottomItemY = 109 | pos.dy + object.size.height; 110 | widget.boardList!.widget.boardView!.bottomListY = 111 | listPos.dy + box.size.height; 112 | widget.boardList!.widget.boardView!.rightListX = 113 | listPos.dx + box.size.width; 114 | 115 | widget.boardList!.widget.boardView!.initialX = pos.dx; 116 | widget.boardList!.widget.boardView!.initialY = pos.dy; 117 | } 118 | }, 119 | onTapCancel: () {}, 120 | onTap: () { 121 | if (widget.onTapItem != null) { 122 | widget.onTapItem!(widget.boardList!.widget.index, widget.index, this); 123 | } 124 | }, 125 | onLongPress: () { 126 | if(!widget.boardList!.widget.boardView!.widget.isSelecting && widget.draggable) { 127 | _startDrag(widget, context); 128 | } 129 | }, 130 | child: widget.item, 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lib/board_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:boardview/board_item.dart'; 2 | import 'package:boardview/boardview.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | 6 | typedef void OnDropList(int? listIndex,int? oldListIndex); 7 | typedef void OnTapList(int? listIndex); 8 | typedef void OnStartDragList(int? listIndex); 9 | 10 | class BoardList extends StatefulWidget { 11 | final List? header; 12 | final Widget? footer; 13 | final List? items; 14 | final Color? backgroundColor; 15 | final Color? headerBackgroundColor; 16 | final BoardViewState? boardView; 17 | final OnDropList? onDropList; 18 | final OnTapList? onTapList; 19 | final OnStartDragList? onStartDragList; 20 | final bool draggable; 21 | 22 | const BoardList({ 23 | Key? key, 24 | this.header, 25 | this.items, 26 | this.footer, 27 | this.backgroundColor, 28 | this.headerBackgroundColor, 29 | this.boardView, 30 | this.draggable = true, 31 | this.index, this.onDropList, this.onTapList, this.onStartDragList, 32 | }) : super(key: key); 33 | 34 | final int? index; 35 | 36 | @override 37 | State createState() { 38 | return BoardListState(); 39 | } 40 | } 41 | 42 | class BoardListState extends State with AutomaticKeepAliveClientMixin{ 43 | List itemStates = []; 44 | ScrollController boardListController = new ScrollController(); 45 | 46 | void onDropList(int? listIndex) { 47 | if(widget.onDropList != null){ 48 | widget.onDropList!(listIndex,widget.boardView!.startListIndex); 49 | } 50 | widget.boardView!.draggedListIndex = null; 51 | if(widget.boardView!.mounted) { 52 | widget.boardView!.setState(() { 53 | 54 | }); 55 | } 56 | } 57 | 58 | void _startDrag(Widget item, BuildContext context) { 59 | if (widget.boardView != null && widget.draggable) { 60 | if(widget.onStartDragList != null){ 61 | widget.onStartDragList!(widget.index); 62 | } 63 | widget.boardView!.startListIndex = widget.index; 64 | widget.boardView!.height = context.size!.height; 65 | widget.boardView!.draggedListIndex = widget.index!; 66 | widget.boardView!.draggedItemIndex = null; 67 | widget.boardView!.draggedItem = item; 68 | widget.boardView!.onDropList = onDropList; 69 | widget.boardView!.run(); 70 | if(widget.boardView!.mounted) { 71 | widget.boardView!.setState(() {}); 72 | } 73 | } 74 | } 75 | 76 | @override 77 | bool get wantKeepAlive => true; 78 | 79 | @override 80 | Widget build(BuildContext context) { 81 | List listWidgets = []; 82 | if (widget.header != null) { 83 | Color? headerBackgroundColor = Color.fromARGB(255, 255, 255, 255); 84 | if (widget.headerBackgroundColor != null) { 85 | headerBackgroundColor = widget.headerBackgroundColor; 86 | } 87 | listWidgets.add(GestureDetector( 88 | onTap: (){ 89 | if(widget.onTapList != null){ 90 | widget.onTapList!(widget.index); 91 | } 92 | }, 93 | onTapDown: (otd) { 94 | if(widget.draggable) { 95 | RenderBox object = context.findRenderObject() as RenderBox; 96 | Offset pos = object.localToGlobal(Offset.zero); 97 | widget.boardView!.initialX = pos.dx; 98 | widget.boardView!.initialY = pos.dy; 99 | 100 | widget.boardView!.rightListX = pos.dx + object.size.width; 101 | widget.boardView!.leftListX = pos.dx; 102 | } 103 | }, 104 | onTapCancel: () {}, 105 | onLongPress: () { 106 | if(!widget.boardView!.widget.isSelecting && widget.draggable) { 107 | _startDrag(widget, context); 108 | } 109 | }, 110 | child: Container( 111 | color: widget.headerBackgroundColor, 112 | child: Row( 113 | mainAxisSize: MainAxisSize.max, 114 | mainAxisAlignment: MainAxisAlignment.center, 115 | children: widget.header!), 116 | ))); 117 | 118 | } 119 | if (widget.items != null) { 120 | listWidgets.add(Container( 121 | child: Flexible( 122 | fit: FlexFit.loose, 123 | child: new ListView.builder( 124 | shrinkWrap: true, 125 | physics: ClampingScrollPhysics(), 126 | controller: boardListController, 127 | itemCount: widget.items!.length, 128 | itemBuilder: (ctx, index) { 129 | if (widget.items![index].boardList == null || 130 | widget.items![index].index != index || 131 | widget.items![index].boardList!.widget.index != widget.index || 132 | widget.items![index].boardList != this) { 133 | widget.items![index] = new BoardItem( 134 | boardList: this, 135 | item: widget.items![index].item, 136 | draggable: widget.items![index].draggable, 137 | index: index, 138 | onDropItem: widget.items![index].onDropItem, 139 | onTapItem: widget.items![index].onTapItem, 140 | onDragItem: widget.items![index].onDragItem, 141 | onStartDragItem: widget.items![index].onStartDragItem, 142 | ); 143 | } 144 | if (widget.boardView!.draggedItemIndex == index && 145 | widget.boardView!.draggedListIndex == widget.index) { 146 | return Opacity( 147 | opacity: 0.0, 148 | child: widget.items![index], 149 | ); 150 | } else { 151 | return widget.items![index]; 152 | } 153 | }, 154 | )))); 155 | } 156 | 157 | if (widget.footer != null) { 158 | listWidgets.add(widget.footer!); 159 | } 160 | 161 | Color? backgroundColor = Color.fromARGB(255, 255, 255, 255); 162 | 163 | if (widget.backgroundColor != null) { 164 | backgroundColor = widget.backgroundColor; 165 | } 166 | if (widget.boardView!.listStates.length > widget.index!) { 167 | widget.boardView!.listStates.removeAt(widget.index!); 168 | } 169 | widget.boardView!.listStates.insert(widget.index!, this); 170 | 171 | return Container( 172 | margin: EdgeInsets.all(8), 173 | decoration: BoxDecoration(color: backgroundColor), 174 | child: Column( 175 | mainAxisSize: MainAxisSize.min, 176 | mainAxisAlignment: MainAxisAlignment.start, 177 | children: listWidgets as List, 178 | )); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /lib/boardview.dart: -------------------------------------------------------------------------------- 1 | library boardview; 2 | 3 | import 'dart:math'; 4 | 5 | import 'package:boardview/boardview_controller.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/widgets.dart'; 8 | import 'dart:core'; 9 | import 'package:boardview/board_list.dart'; 10 | import 'package:vs_scrollbar/vs_scrollbar.dart'; 11 | 12 | class BoardView extends StatefulWidget { 13 | final List? lists; 14 | final double width; 15 | Widget? middleWidget; 16 | double? bottomPadding; 17 | bool isSelecting; 18 | bool? scrollbar; 19 | ScrollbarStyle? scrollbarStyle; 20 | BoardViewController? boardViewController; 21 | int dragDelay; 22 | 23 | Function(bool)? itemInMiddleWidget; 24 | OnDropBottomWidget? onDropItemInMiddleWidget; 25 | BoardView({Key? key, this.itemInMiddleWidget,this.scrollbar,this.scrollbarStyle,this.boardViewController,this.dragDelay=300,this.onDropItemInMiddleWidget, this.isSelecting = false, this.lists, this.width = 280, this.middleWidget, this.bottomPadding}) : super(key: key); 26 | 27 | @override 28 | State createState() { 29 | return BoardViewState(); 30 | } 31 | } 32 | 33 | typedef void OnDropBottomWidget(int? listIndex, int? itemIndex,double percentX); 34 | typedef void OnDropItem(int? listIndex, int? itemIndex); 35 | typedef void OnDropList(int? listIndex); 36 | 37 | class BoardViewState extends State with AutomaticKeepAliveClientMixin { 38 | Widget? draggedItem; 39 | int? draggedItemIndex; 40 | int? draggedListIndex; 41 | double? dx; 42 | double? dxInit; 43 | double? dyInit; 44 | double? dy; 45 | double? offsetX; 46 | double? offsetY; 47 | double? initialX = 0; 48 | double? initialY = 0; 49 | double? rightListX; 50 | double? leftListX; 51 | double? topListY; 52 | double? bottomListY; 53 | double? topItemY; 54 | double? bottomItemY; 55 | double? height; 56 | int? startListIndex; 57 | int? startItemIndex; 58 | 59 | bool canDrag = true; 60 | 61 | ScrollController boardViewController = new ScrollController(); 62 | 63 | List listStates = []; 64 | 65 | OnDropItem? onDropItem; 66 | OnDropList? onDropList; 67 | 68 | bool isScrolling = false; 69 | 70 | bool _isInWidget = false; 71 | 72 | GlobalKey _middleWidgetKey = GlobalKey(); 73 | 74 | var pointer; 75 | 76 | @override 77 | bool get wantKeepAlive => true; 78 | 79 | @override 80 | void initState() { 81 | super.initState(); 82 | if(widget.boardViewController != null){ 83 | widget.boardViewController!.state = this; 84 | } 85 | } 86 | 87 | void moveDown() { 88 | if(topItemY != null){ 89 | topItemY = topItemY! + listStates[draggedListIndex!].itemStates[draggedItemIndex! + 1].height; 90 | } 91 | if(bottomItemY != null){ 92 | bottomItemY = bottomItemY! + listStates[draggedListIndex!].itemStates[draggedItemIndex! + 1].height; 93 | } 94 | var item = widget.lists![draggedListIndex!].items![draggedItemIndex!]; 95 | widget.lists![draggedListIndex!].items!.removeAt(draggedItemIndex!); 96 | var itemState = listStates[draggedListIndex!].itemStates[draggedItemIndex!]; 97 | listStates[draggedListIndex!].itemStates.removeAt(draggedItemIndex!); 98 | if(draggedItemIndex != null){ 99 | draggedItemIndex = draggedItemIndex! + 1; 100 | } 101 | widget.lists![draggedListIndex!].items!.insert(draggedItemIndex!, item); 102 | listStates[draggedListIndex!].itemStates.insert(draggedItemIndex!, itemState); 103 | if(listStates[draggedListIndex!].mounted) { 104 | listStates[draggedListIndex!].setState(() {}); 105 | } 106 | } 107 | 108 | void moveUp() { 109 | if(topItemY != null){ 110 | topItemY = topItemY! - listStates[draggedListIndex!].itemStates[draggedItemIndex! - 1].height; 111 | } 112 | if(bottomItemY != null){ 113 | bottomItemY = bottomItemY!-listStates[draggedListIndex!].itemStates[draggedItemIndex! - 1].height; 114 | } 115 | var item = widget.lists![draggedListIndex!].items![draggedItemIndex!]; 116 | widget.lists![draggedListIndex!].items!.removeAt(draggedItemIndex!); 117 | var itemState = listStates[draggedListIndex!].itemStates[draggedItemIndex!]; 118 | listStates[draggedListIndex!].itemStates.removeAt(draggedItemIndex!); 119 | if(draggedItemIndex != null){ 120 | draggedItemIndex = draggedItemIndex! - 1; 121 | } 122 | widget.lists![draggedListIndex!].items!.insert(draggedItemIndex!, item); 123 | listStates[draggedListIndex!].itemStates.insert(draggedItemIndex!, itemState); 124 | if(listStates[draggedListIndex!].mounted) { 125 | listStates[draggedListIndex!].setState(() {}); 126 | } 127 | } 128 | 129 | void moveListRight() { 130 | var list = widget.lists![draggedListIndex!]; 131 | var listState = listStates[draggedListIndex!]; 132 | widget.lists!.removeAt(draggedListIndex!); 133 | listStates.removeAt(draggedListIndex!); 134 | if(draggedListIndex != null){ 135 | draggedListIndex = draggedListIndex! + 1; 136 | } 137 | widget.lists!.insert(draggedListIndex!, list); 138 | listStates.insert(draggedListIndex!, listState); 139 | canDrag = false; 140 | if (boardViewController != null && boardViewController.hasClients) { 141 | int? tempListIndex = draggedListIndex; 142 | boardViewController 143 | .animateTo(draggedListIndex! * widget.width, duration: new Duration(milliseconds: 400), curve: Curves.ease) 144 | .whenComplete(() { 145 | RenderBox object = listStates[tempListIndex!].context.findRenderObject() as RenderBox; 146 | Offset pos = object.localToGlobal(Offset.zero); 147 | leftListX = pos.dx; 148 | rightListX = pos.dx + object.size.width; 149 | Future.delayed(new Duration(milliseconds: widget.dragDelay), () { 150 | canDrag = true; 151 | }); 152 | }); 153 | } 154 | if(mounted){ 155 | setState(() {}); 156 | } 157 | } 158 | 159 | void moveRight() { 160 | var item = widget.lists![draggedListIndex!].items![draggedItemIndex!]; 161 | var itemState = listStates[draggedListIndex!].itemStates[draggedItemIndex!]; 162 | widget.lists![draggedListIndex!].items!.removeAt(draggedItemIndex!); 163 | listStates[draggedListIndex!].itemStates.removeAt(draggedItemIndex!); 164 | if(listStates[draggedListIndex!].mounted) { 165 | listStates[draggedListIndex!].setState(() {}); 166 | } 167 | if(draggedListIndex != null){ 168 | draggedListIndex = draggedListIndex! + 1; 169 | } 170 | double closestValue = 10000; 171 | draggedItemIndex = 0; 172 | for (int i = 0; i < listStates[draggedListIndex!].itemStates.length; i++) { 173 | if (listStates[draggedListIndex!].itemStates[i].mounted && listStates[draggedListIndex!].itemStates[i].context != null) { 174 | RenderBox box = listStates[draggedListIndex!].itemStates[i].context.findRenderObject() as RenderBox; 175 | Offset pos = box.localToGlobal(Offset.zero); 176 | var temp = (pos.dy - dy! + (box.size.height / 2)).abs(); 177 | if (temp < closestValue) { 178 | closestValue = temp; 179 | draggedItemIndex = i; 180 | dyInit = dy; 181 | } 182 | } 183 | } 184 | widget.lists![draggedListIndex!].items!.insert(draggedItemIndex!, item); 185 | listStates[draggedListIndex!].itemStates.insert(draggedItemIndex!, itemState); 186 | canDrag = false; 187 | if(listStates[draggedListIndex!].mounted) { 188 | listStates[draggedListIndex!].setState(() {}); 189 | } 190 | if (boardViewController != null && boardViewController.hasClients) { 191 | int? tempListIndex = draggedListIndex; 192 | int? tempItemIndex = draggedItemIndex; 193 | boardViewController 194 | .animateTo(draggedListIndex! * widget.width, duration: new Duration(milliseconds: 400), curve: Curves.ease) 195 | .whenComplete(() { 196 | RenderBox object = listStates[tempListIndex!].context.findRenderObject() as RenderBox; 197 | Offset pos = object.localToGlobal(Offset.zero); 198 | leftListX = pos.dx; 199 | rightListX = pos.dx + object.size.width; 200 | RenderBox box = listStates[tempListIndex].itemStates[tempItemIndex!].context.findRenderObject() as RenderBox; 201 | Offset itemPos = box.localToGlobal(Offset.zero); 202 | topItemY = itemPos.dy; 203 | bottomItemY = itemPos.dy + box.size.height; 204 | Future.delayed(new Duration(milliseconds: widget.dragDelay), () { 205 | canDrag = true; 206 | }); 207 | }); 208 | } 209 | if(mounted){ 210 | setState(() { }); 211 | } 212 | } 213 | 214 | void moveListLeft() { 215 | var list = widget.lists![draggedListIndex!]; 216 | var listState = listStates[draggedListIndex!]; 217 | widget.lists!.removeAt(draggedListIndex!); 218 | listStates.removeAt(draggedListIndex!); 219 | if(draggedListIndex != null){ 220 | draggedListIndex = draggedListIndex! - 1; 221 | } 222 | widget.lists!.insert(draggedListIndex!, list); 223 | listStates.insert(draggedListIndex!, listState); 224 | canDrag = false; 225 | if (boardViewController != null && boardViewController.hasClients) { 226 | int? tempListIndex = draggedListIndex; 227 | boardViewController 228 | .animateTo(draggedListIndex! * widget.width, duration: new Duration(milliseconds: widget.dragDelay), curve: Curves.ease) 229 | .whenComplete(() { 230 | RenderBox object = listStates[tempListIndex!].context.findRenderObject() as RenderBox; 231 | Offset pos = object.localToGlobal(Offset.zero); 232 | leftListX = pos.dx; 233 | rightListX = pos.dx + object.size.width; 234 | Future.delayed(new Duration(milliseconds: widget.dragDelay), () { 235 | canDrag = true; 236 | }); 237 | }); 238 | } 239 | if(mounted) { 240 | setState(() {}); 241 | } 242 | } 243 | 244 | void moveLeft() { 245 | var item = widget.lists![draggedListIndex!].items![draggedItemIndex!]; 246 | var itemState = listStates[draggedListIndex!].itemStates[draggedItemIndex!]; 247 | widget.lists![draggedListIndex!].items!.removeAt(draggedItemIndex!); 248 | listStates[draggedListIndex!].itemStates.removeAt(draggedItemIndex!); 249 | if(listStates[draggedListIndex!].mounted) { 250 | listStates[draggedListIndex!].setState(() {}); 251 | } 252 | if(draggedListIndex != null){ 253 | draggedListIndex = draggedListIndex! - 1; 254 | } 255 | double closestValue = 10000; 256 | draggedItemIndex = 0; 257 | for (int i = 0; i < listStates[draggedListIndex!].itemStates.length; i++) { 258 | if (listStates[draggedListIndex!].itemStates[i].mounted && listStates[draggedListIndex!].itemStates[i].context != null) { 259 | RenderBox box = listStates[draggedListIndex!].itemStates[i].context.findRenderObject() as RenderBox; 260 | Offset pos = box.localToGlobal(Offset.zero); 261 | var temp = (pos.dy - dy! + (box.size.height / 2)).abs(); 262 | if (temp < closestValue) { 263 | closestValue = temp; 264 | draggedItemIndex = i; 265 | dyInit = dy; 266 | } 267 | } 268 | } 269 | widget.lists![draggedListIndex!].items!.insert(draggedItemIndex!, item); 270 | listStates[draggedListIndex!].itemStates.insert(draggedItemIndex!, itemState); 271 | canDrag = false; 272 | if(listStates[draggedListIndex!].mounted) { 273 | listStates[draggedListIndex!].setState(() {}); 274 | } 275 | if (boardViewController != null && boardViewController.hasClients) { 276 | int? tempListIndex = draggedListIndex; 277 | int? tempItemIndex = draggedItemIndex; 278 | boardViewController 279 | .animateTo(draggedListIndex! * widget.width, duration: new Duration(milliseconds: 400), curve: Curves.ease) 280 | .whenComplete(() { 281 | RenderBox object = listStates[tempListIndex!].context.findRenderObject() as RenderBox; 282 | Offset pos = object.localToGlobal(Offset.zero); 283 | leftListX = pos.dx; 284 | rightListX = pos.dx + object.size.width; 285 | RenderBox box = listStates[tempListIndex].itemStates[tempItemIndex!].context.findRenderObject() as RenderBox; 286 | Offset itemPos = box.localToGlobal(Offset.zero); 287 | topItemY = itemPos.dy; 288 | bottomItemY = itemPos.dy + box.size.height; 289 | Future.delayed(new Duration(milliseconds: widget.dragDelay), () { 290 | canDrag = true; 291 | }); 292 | }); 293 | } 294 | if(mounted) { 295 | setState(() {}); 296 | } 297 | } 298 | 299 | bool shown = true; 300 | 301 | @override 302 | Widget build(BuildContext context) { 303 | print("dy:${dy}"); 304 | print("topListY:${topListY}"); 305 | print("bottomListY:${bottomListY}"); 306 | if(boardViewController.hasClients) { 307 | WidgetsBinding.instance!.addPostFrameCallback((Duration duration) { 308 | try { 309 | boardViewController.position.didUpdateScrollPositionBy(0); 310 | }catch(e){} 311 | bool _shown = boardViewController.position.maxScrollExtent!=0; 312 | if(_shown != shown){ 313 | setState(() { 314 | shown = _shown; 315 | }); 316 | } 317 | }); 318 | } 319 | Widget listWidget = ListView.builder( 320 | physics: ClampingScrollPhysics(), 321 | itemCount: widget.lists!.length, 322 | scrollDirection: Axis.horizontal, 323 | controller: boardViewController, 324 | itemBuilder: (BuildContext context, int index) { 325 | if (widget.lists![index].boardView == null) { 326 | widget.lists![index] = BoardList( 327 | items: widget.lists![index].items, 328 | headerBackgroundColor: widget.lists![index].headerBackgroundColor, 329 | backgroundColor: widget.lists![index].backgroundColor, 330 | footer: widget.lists![index].footer, 331 | header: widget.lists![index].header, 332 | boardView: this, 333 | draggable: widget.lists![index].draggable, 334 | onDropList: widget.lists![index].onDropList, 335 | onTapList: widget.lists![index].onTapList, 336 | onStartDragList: widget.lists![index].onStartDragList, 337 | ); 338 | } 339 | if (widget.lists![index].index != index) { 340 | widget.lists![index] = BoardList( 341 | items: widget.lists![index].items, 342 | headerBackgroundColor: widget.lists![index].headerBackgroundColor, 343 | backgroundColor: widget.lists![index].backgroundColor, 344 | footer: widget.lists![index].footer, 345 | header: widget.lists![index].header, 346 | boardView: this, 347 | draggable: widget.lists![index].draggable, 348 | index: index, 349 | onDropList: widget.lists![index].onDropList, 350 | onTapList: widget.lists![index].onTapList, 351 | onStartDragList: widget.lists![index].onStartDragList, 352 | ); 353 | } 354 | 355 | var temp = Container( 356 | width: widget.width, 357 | padding: EdgeInsets.fromLTRB(0, 0, 0, widget.bottomPadding ?? 0), 358 | child: Row( 359 | crossAxisAlignment: CrossAxisAlignment.start, 360 | mainAxisAlignment: MainAxisAlignment.start, 361 | children: [Expanded(child: widget.lists![index])], 362 | )); 363 | if (draggedListIndex == index && draggedItemIndex == null) { 364 | return Opacity( 365 | opacity: 0.0, 366 | child: temp, 367 | ); 368 | } else { 369 | return temp; 370 | } 371 | }, 372 | ); 373 | if(widget.scrollbar == true){ 374 | listWidget = VsScrollbar( 375 | controller: boardViewController, 376 | showTrackOnHover: true,// default false 377 | isAlwaysShown: shown&&widget.lists!.length>1, // default false 378 | scrollbarFadeDuration: Duration(milliseconds: 500), // default : Duration(milliseconds: 300) 379 | scrollbarTimeToFade: Duration(milliseconds: 800),// default : Duration(milliseconds: 600) 380 | style: widget.scrollbarStyle!=null?VsScrollbarStyle( 381 | hoverThickness: widget.scrollbarStyle!.hoverThickness, 382 | radius: widget.scrollbarStyle!.radius, 383 | thickness: widget.scrollbarStyle!.thickness, 384 | color: widget.scrollbarStyle!.color 385 | ):VsScrollbarStyle(), 386 | child:listWidget); 387 | } 388 | List stackWidgets = [ 389 | listWidget 390 | ]; 391 | bool isInBottomWidget = false; 392 | if (dy != null) { 393 | if (MediaQuery.of(context).size.height - dy! < 80) { 394 | isInBottomWidget = true; 395 | } 396 | } 397 | if(widget.itemInMiddleWidget != null && _isInWidget != isInBottomWidget) { 398 | widget.itemInMiddleWidget!(isInBottomWidget); 399 | _isInWidget = isInBottomWidget; 400 | } 401 | if (initialX != null && 402 | initialY != null && 403 | offsetX != null && 404 | offsetY != null && 405 | dx != null && 406 | dy != null && 407 | height != null && 408 | widget.width != null) { 409 | if (canDrag && dxInit != null && dyInit != null && !isInBottomWidget) { 410 | if (draggedItemIndex != null && draggedItem != null && topItemY != null && bottomItemY != null) { 411 | //dragging item 412 | if (0 <= draggedListIndex! - 1 && dx! < leftListX! + 45) { 413 | //scroll left 414 | if (boardViewController != null && boardViewController.hasClients) { 415 | boardViewController.animateTo(boardViewController.position.pixels - 5, 416 | duration: new Duration(milliseconds: 10), curve: Curves.ease); 417 | if(listStates[draggedListIndex!].mounted) { 418 | RenderBox object = listStates[draggedListIndex!].context 419 | .findRenderObject() as RenderBox; 420 | Offset pos = object.localToGlobal(Offset.zero); 421 | leftListX = pos.dx; 422 | rightListX = pos.dx + object.size.width; 423 | } 424 | } 425 | } 426 | if (widget.lists!.length > draggedListIndex! + 1 && dx! > rightListX! - 45) { 427 | //scroll right 428 | if (boardViewController != null && boardViewController.hasClients) { 429 | boardViewController.animateTo(boardViewController.position.pixels + 5, 430 | duration: new Duration(milliseconds: 10), curve: Curves.ease); 431 | if(listStates[draggedListIndex!].mounted) { 432 | RenderBox object = listStates[draggedListIndex!].context 433 | .findRenderObject() as RenderBox; 434 | Offset pos = object.localToGlobal(Offset.zero); 435 | leftListX = pos.dx; 436 | rightListX = pos.dx + object.size.width; 437 | } 438 | } 439 | } 440 | if (0 <= draggedListIndex! - 1 && dx! < leftListX!) { 441 | //move left 442 | moveLeft(); 443 | } 444 | if (widget.lists!.length > draggedListIndex! + 1 && dx! > rightListX!) { 445 | //move right 446 | moveRight(); 447 | } 448 | if (dy! < topListY! + 70) { 449 | //scroll up 450 | if (listStates[draggedListIndex!].boardListController != null && 451 | listStates[draggedListIndex!].boardListController.hasClients && !isScrolling) { 452 | isScrolling = true; 453 | double pos = listStates[draggedListIndex!].boardListController.position.pixels; 454 | listStates[draggedListIndex!].boardListController.animateTo( 455 | listStates[draggedListIndex!].boardListController.position.pixels - 5, 456 | duration: new Duration(milliseconds: 10), 457 | curve: Curves.ease).whenComplete((){ 458 | 459 | pos -= listStates[draggedListIndex!].boardListController.position.pixels; 460 | if(initialY == null) 461 | initialY = 0; 462 | // if(widget.boardViewController != null) { 463 | // initialY -= pos; 464 | // } 465 | isScrolling = false; 466 | if(topItemY != null) { 467 | topItemY = topItemY! + pos; 468 | } 469 | if(bottomItemY != null) { 470 | bottomItemY = bottomItemY! + pos; 471 | } 472 | if(mounted){ 473 | setState(() { }); 474 | } 475 | }); 476 | } 477 | } 478 | if (0 <= draggedItemIndex! - 1 && 479 | dy! < topItemY! - listStates[draggedListIndex!].itemStates[draggedItemIndex! - 1].height / 2) { 480 | //move up 481 | moveUp(); 482 | } 483 | double? tempBottom = bottomListY; 484 | if(widget.middleWidget != null){ 485 | if(_middleWidgetKey.currentContext != null) { 486 | RenderBox _box = _middleWidgetKey.currentContext! 487 | .findRenderObject() as RenderBox; 488 | tempBottom = _box.size.height; 489 | print("tempBottom:${tempBottom}"); 490 | } 491 | } 492 | if (dy! > tempBottom! - 70) { 493 | //scroll down 494 | 495 | if (listStates[draggedListIndex!].boardListController != null && 496 | listStates[draggedListIndex!].boardListController.hasClients) { 497 | isScrolling = true; 498 | double pos = listStates[draggedListIndex!].boardListController.position.pixels; 499 | listStates[draggedListIndex!].boardListController.animateTo( 500 | listStates[draggedListIndex!].boardListController.position.pixels + 5, 501 | duration: new Duration(milliseconds: 10), 502 | curve: Curves.ease).whenComplete((){ 503 | pos -= listStates[draggedListIndex!].boardListController.position.pixels; 504 | if(initialY == null) 505 | initialY = 0; 506 | // if(widget.boardViewController != null) { 507 | // initialY -= pos; 508 | // } 509 | isScrolling = false; 510 | if(topItemY != null) { 511 | topItemY = topItemY! + pos; 512 | } 513 | if(bottomItemY != null) { 514 | bottomItemY = bottomItemY! + pos; 515 | } 516 | if(mounted){ 517 | setState(() {}); 518 | } 519 | }); 520 | } 521 | } 522 | if (widget.lists![draggedListIndex!].items!.length > draggedItemIndex! + 1 && 523 | dy! > bottomItemY! + listStates[draggedListIndex!].itemStates[draggedItemIndex! + 1].height / 2) { 524 | //move down 525 | moveDown(); 526 | } 527 | } else { 528 | //dragging list 529 | if (0 <= draggedListIndex! - 1 && dx! < leftListX! + 45) { 530 | //scroll left 531 | if (boardViewController != null && boardViewController.hasClients) { 532 | boardViewController.animateTo(boardViewController.position.pixels - 5, 533 | duration: new Duration(milliseconds: 10), curve: Curves.ease); 534 | if(leftListX != null){ 535 | leftListX = leftListX! + 5; 536 | } 537 | if(rightListX != null){ 538 | rightListX = rightListX! + 5; 539 | } 540 | } 541 | } 542 | 543 | if (widget.lists!.length > draggedListIndex! + 1 && dx! > rightListX! - 45) { 544 | //scroll right 545 | if (boardViewController != null && boardViewController.hasClients) { 546 | boardViewController.animateTo(boardViewController.position.pixels + 5, 547 | duration: new Duration(milliseconds: 10), curve: Curves.ease); 548 | if(leftListX != null){ 549 | leftListX = leftListX! - 5; 550 | } 551 | if(rightListX != null){ 552 | rightListX = rightListX! - 5; 553 | } 554 | } 555 | } 556 | if (widget.lists!.length > draggedListIndex! + 1 && dx! > rightListX!) { 557 | //move right 558 | moveListRight(); 559 | } 560 | if (0 <= draggedListIndex! - 1 && dx! < leftListX!) { 561 | //move left 562 | moveListLeft(); 563 | } 564 | } 565 | } 566 | if (widget.middleWidget != null) { 567 | stackWidgets.add(Container(key:_middleWidgetKey,child:widget.middleWidget)); 568 | } 569 | WidgetsBinding.instance!.addPostFrameCallback((timeStamp) { 570 | if(mounted){ 571 | setState(() {}); 572 | } 573 | }); 574 | stackWidgets.add(Positioned( 575 | width: widget.width, 576 | height: height, 577 | child: Opacity(opacity: .7, child: draggedItem), 578 | left: (dx! - offsetX!) + initialX!, 579 | top: (dy! - offsetY!) + initialY!, 580 | )); 581 | } 582 | 583 | return Container( 584 | child: Listener( 585 | onPointerMove: (opm) { 586 | if (draggedItem != null) { 587 | if (dxInit == null) { 588 | dxInit = opm.position.dx; 589 | } 590 | if (dyInit == null) { 591 | dyInit = opm.position.dy; 592 | } 593 | dx = opm.position.dx; 594 | dy = opm.position.dy; 595 | if(mounted) { 596 | setState(() {}); 597 | } 598 | } 599 | }, 600 | onPointerDown: (opd) { 601 | RenderBox box = context.findRenderObject() as RenderBox; 602 | Offset pos = box.localToGlobal(opd.position); 603 | offsetX = pos.dx; 604 | offsetY = pos.dy; 605 | pointer = opd; 606 | if(mounted) { 607 | setState(() {}); 608 | } 609 | }, 610 | onPointerUp: (opu) { 611 | if (onDropItem != null) { 612 | int? tempDraggedItemIndex = draggedItemIndex; 613 | int? tempDraggedListIndex = draggedListIndex; 614 | int? startDraggedItemIndex = startItemIndex; 615 | int? startDraggedListIndex = startListIndex; 616 | 617 | if(_isInWidget && widget.onDropItemInMiddleWidget != null){ 618 | onDropItem!(startDraggedListIndex, startDraggedItemIndex); 619 | widget.onDropItemInMiddleWidget!(startDraggedListIndex, startDraggedItemIndex,opu.position.dx/MediaQuery.of(context).size.width); 620 | }else{ 621 | onDropItem!(tempDraggedListIndex, tempDraggedItemIndex); 622 | } 623 | } 624 | if (onDropList != null) { 625 | int? tempDraggedListIndex = draggedListIndex; 626 | if(_isInWidget && widget.onDropItemInMiddleWidget != null){ 627 | onDropList!(tempDraggedListIndex); 628 | widget.onDropItemInMiddleWidget!(tempDraggedListIndex,null,opu.position.dx/MediaQuery.of(context).size.width); 629 | }else{ 630 | onDropList!(tempDraggedListIndex); 631 | } 632 | } 633 | draggedItem = null; 634 | offsetX = null; 635 | offsetY = null; 636 | initialX = null; 637 | initialY = null; 638 | dx = null; 639 | dy = null; 640 | draggedItemIndex = null; 641 | draggedListIndex = null; 642 | onDropItem = null; 643 | onDropList = null; 644 | dxInit = null; 645 | dyInit = null; 646 | leftListX = null; 647 | rightListX = null; 648 | topListY = null; 649 | bottomListY = null; 650 | topItemY = null; 651 | bottomItemY = null; 652 | startListIndex = null; 653 | startItemIndex = null; 654 | if(mounted) { 655 | setState(() {}); 656 | } 657 | }, 658 | child: new Stack( 659 | children: stackWidgets, 660 | ))); 661 | } 662 | 663 | void run() { 664 | if (pointer != null) { 665 | dx = pointer.position.dx; 666 | dy = pointer.position.dy; 667 | if(mounted) { 668 | setState(() {}); 669 | } 670 | } 671 | } 672 | } 673 | 674 | class ScrollbarStyle{ 675 | double hoverThickness; 676 | double thickness; 677 | Radius radius; 678 | Color color; 679 | ScrollbarStyle({this.radius = const Radius.circular(10),this.hoverThickness = 10,this.thickness = 10,this.color = Colors.black}); 680 | } 681 | -------------------------------------------------------------------------------- /lib/boardview_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/animation.dart'; 2 | 3 | import 'boardview.dart'; 4 | 5 | class BoardViewController{ 6 | 7 | BoardViewController(); 8 | 9 | late BoardViewState state; 10 | 11 | Future animateTo(int index,{Duration? duration,Curve? curve})async{ 12 | double offset = index * state.widget.width; 13 | if (state.boardViewController != null && state.boardViewController.hasClients) { 14 | await state.boardViewController.animateTo( 15 | offset, duration: duration!, curve: curve!); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.5.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.2.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "0.12.10" 70 | meta: 71 | dependency: transitive 72 | description: 73 | name: meta 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "1.3.0" 77 | path: 78 | dependency: transitive 79 | description: 80 | name: path 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.8.0" 84 | sky_engine: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.99" 89 | source_span: 90 | dependency: transitive 91 | description: 92 | name: source_span 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "1.8.1" 96 | stack_trace: 97 | dependency: transitive 98 | description: 99 | name: stack_trace 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.10.0" 103 | stream_channel: 104 | dependency: transitive 105 | description: 106 | name: stream_channel 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "2.1.0" 110 | string_scanner: 111 | dependency: transitive 112 | description: 113 | name: string_scanner 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.1.0" 117 | term_glyph: 118 | dependency: transitive 119 | description: 120 | name: term_glyph 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.2.0" 124 | test_api: 125 | dependency: transitive 126 | description: 127 | name: test_api 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.3.0" 131 | typed_data: 132 | dependency: transitive 133 | description: 134 | name: typed_data 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.3.0" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "2.1.0" 145 | vs_scrollbar: 146 | dependency: "direct main" 147 | description: 148 | name: vs_scrollbar 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "0.2.0" 152 | sdks: 153 | dart: ">=2.12.0 <3.0.0" 154 | flutter: ">=1.17.0" 155 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: boardview 2 | description: This is a custom Flutter widget that can create a draggable BoardView or also known as a kanban. The view can be reordered with drag and drop. 3 | version: 0.2.1 4 | homepage: https://github.com/jakebonk/FlutterBoardView 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | vs_scrollbar: ^0.2.0 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | 18 | flutter: 19 | -------------------------------------------------------------------------------- /test/boardview_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'package:boardview/boardview.dart'; 4 | 5 | void main() { 6 | test('adds one to input values', () { 7 | final boardview = BoardView(); 8 | }); 9 | } 10 | --------------------------------------------------------------------------------