一步一步完成Flutter應用開發-掘金App沸點拖拽排序,抖動,編輯話題

一天清晨 發表於 2021-03-18

主要完成沸點頁面的話題廣場頁面. 這個頁面涉及到內容包括: 拖拽排序,增加話題動畫效果,先分析效果怎麼實現,然後就是頁面的構建。 最終效果

tutieshi_640x1343_7s.gif

拖拽

首先想到拖拽,flutter提供了ReorderableListView 拖拽效果如下:

tutieshi_640x1343_5s.gif

var data = <Color>[
    Colors.yellow[50],
    Colors.blue[100],
    Colors.red[200],
    Colors.pink[300],
  ];

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 250,
      child: ReorderableListView(
        padding: EdgeInsets.all(10),
        onReorder: _handleReorder,
        scrollDirection: Axis.vertical,
        children: data.map((color) => _buildItem(color)).toList(),
      ),
    );
  }

  void _handleReorder(int oldIndex, int newIndex) {
    if (oldIndex < newIndex) {
      newIndex -= 1;
    }

    setState(() {
      final element = data.removeAt(oldIndex);
      data.insert(newIndex, element);
    });
  }

  Widget _buildItem(Color color) {
    return Container(
      key: ValueKey(color),
      alignment: Alignment.center,
      height: 50,
      width: 200,
      color: color,
    );
  }
複製程式碼

還有利用GridView和LongPressDraggable進行拖拽排序

tutieshi_640x1343_8s.gif

renderItem(index) {
    return Stack(
      children: [
        Container(
          decoration: BoxDecoration(
            color: Colors.white,
            border: Border.all(color: Colors.grey, width: 1),
            borderRadius: BorderRadius.circular(15),
          ),
          alignment: Alignment.center,
          child: Text(widget.dataList[index]['title']),
        ),
        Positioned(
          child: Text(widget.dataList[index]['move'] == 'false' ? 'x' : '+'),
          right: 10,
          top: 2,
        ),
      ],
    );
  }

  Widget _buildItemWidget(int index) {
    return LongPressDraggable(
      data: index,
      child: DragTarget<int>(
        onAccept: (data) {
          widget.onAccept(data, index);
        },
        builder: (context, data, rejects) {
          return renderItem(index);
        },
        onMove: (data) {
          print(data);
        },
        onLeave: (data) {
          print('$data is Leaving item $index');
          widget.onAccept(data, index);
        },
        onWillAccept: (data) {
          return true;
        },
      ),
      onDragStarted: () {
      },
      onDraggableCanceled: (Velocity velocity, Offset offset) {
      },
      onDragCompleted: () {
      },
     
      feedback: Material(
        child: Container(
          width: (Get.width - 20 - 20) / 3.0,
          height: (Get.width - 20 - 20) / 3.0 * 4 / 10,
          child: renderItem(index),
        ),
      ),
      childWhenDragging: Container(),
    );
  }

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            childAspectRatio: 2.5,
            crossAxisSpacing: 10,
            mainAxisSpacing: 10),
        itemCount: widget.dataList.length,
        itemBuilder: (BuildContext context, int index) {
          return _buildItemWidget(index);
        });
  }
複製程式碼

兩種效果都能實現需求,沒有思路就看看大神們都是怎麼寫的。看了看原始碼,有利用GridView加手勢識別配合操作實現。 使用dragablegridview_flutter庫 然後稍加了修改 傳送門。 沒有思路可以去搜搜。有實現的大神,讀讀程式碼。慢慢的自己也可以造輪子了。

tutieshi_640x1343_8s.gif

 List<ItemBin> itemBins = new List();
 var editSwitchController = EditSwitchController();
DragAbleGridView(
                    mainAxisSpacing: 10.0,
                    crossAxisSpacing: 0.0,
                    childAspectRatio: 2.5,
                    crossAxisCount: 3,
                    itemBins: itemBins,
                    editSwitchController: editSwitchController,
                    isOpenDragAble: alert != '點選進入話題',
                    animationDuration: 300, //milliseconds
                    longPressDuration: 800, //milliseconds

                    child: (int position) {
                      return Container(
                        width: (Get.width - 60) / 3.0,
                        height: (Get.width - 40) / 3.0 * 0.4,
                        // margin: EdgeInsets.only(top: 6.0, right: 6.0),
                        decoration: BoxDecoration(
                          color: Colors.white,
                          border: Border.all(color: Colors.grey, width: 1),
                          borderRadius: BorderRadius.circular(15),
                        ),
                        alignment: Alignment.center,
                        child: Text(
                          itemBins[position].data,
                          style: TextStyle(fontSize: 16.0, color: Colors.blue),
                        ),
                      );
                    },
                    editChangeListener: () {},
                  ),
                ),
複製程式碼

抖動起來

直接使用補間動畫就可以了,點選編輯讓可以編輯的動起來

tutieshi_640x1343_3s.gif

controller = AnimationController(
        duration: const Duration(milliseconds: 1000), vsync: this);

    animation = TweenSequence<double>([
      //使用TweenSequence進行多組補間動畫
      TweenSequenceItem<double>(tween: Tween(begin: 0, end: 5), weight: 1),
      TweenSequenceItem<double>(tween: Tween(begin: 5, end: 0), weight: 2),
      TweenSequenceItem<double>(tween: Tween(begin: 0, end: -5), weight: 3),
      TweenSequenceItem<double>(tween: Tween(begin: -5, end: 0), weight: 4),
    ]).animate(controller)
      ..addListener(() {
        setState(() {});
      })
      ..addStatusListener((s) {
        if (s == AnimationStatus.completed) {
          setState(() {});
          // controller.forward();
        }
      });
      
   Transform(
                          transform:
                              Matrix4.rotationZ(animation.value * pi / 180),
                          alignment: Alignment.center,
                          child: Container(
                            width: (Get.width - 60) / 3.0,
                            height: (Get.width - 40) / 3.0 * 0.4,
                            margin: EdgeInsets.only(top: 6.0),
                            decoration: BoxDecoration(
                              color: Colors.white,
                              border: Border.all(color: Colors.grey, width: 1),
                              borderRadius: BorderRadius.circular(15),
                            ),
                            alignment: Alignment.center,
                            child: Text(
                              itemBins[position].data,
                              style:
                                  TextStyle(fontSize: 16.0, color: Colors.blue),
                            ),
                          ));
複製程式碼

新增話題聯動

底部新增話題,我的話題中刪除話題.產生聯動效果,實現起來比較簡單,就是最終效果了。

//預設載入5條資料
CommonListWiget(
            networkApi: (currentPage) async {
              return ['1', '2', '3', '4', '5'];
            },
            itemBuilder: (BuildContext context, int position) {
            //....之前省略程式碼
            // 最後返回:
                Wrap(
                children: futureList
                    .map((e) => Stack(
                          children: [
                            InkWell(
                              onTap: () {
                                futureList.remove(e);
                                currentList.add(e);
                                itemBins.add(new ItemBin(e));
                                // setState(() {});
                              },
                              child: Container(
                                margin: EdgeInsets.only(
                                    bottom: 10, right: 10, left: 10, top: 5),
                                width: (Get.width - 60) / 3.0,
                                height: (Get.width - 40) / 3.0 * 0.4,
                                decoration: BoxDecoration(
                                  color: Colors.white,
                                  border:
                                      Border.all(color: Colors.grey, width: 1),
                                  borderRadius: BorderRadius.circular(15),
                                ),
                                alignment: Alignment.center,
                                child: Text(e,
                                    style: TextStyle(
                                        fontSize: 16.0, color: Colors.blue)),
                              ),
                            ),
                            Positioned(
                              child: Text(
                                '+',
                                style: TextStyle(fontSize: 18),
                              ),
                              top: 5,
                              right: 20,
                            )
                          ],
                        ))
                    .toList(),
              );
            }
複製程式碼