fluttercandies/flutter_scrollview_observer

关于ReorderableListView的定位问题

Closed this issue · 12 comments

您好,请问现在支持ReorderableListView这个组件的定位么
我的页面是有一个switch按钮可以切换显示的列表,一个是listview.builder,一个是ReorderableListView,当我单独在listview.builder中使用时,时正常的,但时加上ReorderableListView的就会失效并且滚动列表的时候会报错。错误如下

======== Exception caught by gesture ===============================================================
The following assertion was thrown while handling a gesture:
Cannot get renderObject of inactive element.

In order for an element to have a valid renderObject, it must be active, which means it is part of the tree.
Instead, this element is in the _ElementLifecycle.defunct state.
If you called this method from a State object, consider guarding it with State.mounted.
The findRenderObject() method was called for the following element: SliverMultiBoxAdaptorElement#006f4(DEFUNCT)
no widget
When the exception was thrown, this was the stack:
#0 Element.findRenderObject. (package:flutter/src/widgets/framework.dart:4006:9)
#1 Element.findRenderObject (package:flutter/src/widgets/framework.dart:4019:6)
#2 ListObserverMix.handleListObserve (package:scrollview_observer/src/listview/list_observer_mix.dart:23:22)
#3 ListViewObserverState.handleObserve (package:scrollview_observer/src/listview/list_observer_view.dart:63:12)
#4 ObserverWidgetState._handleContexts (package:scrollview_observer/src/common/observer_widget.dart:163:34)
#5 ObserverWidgetState.build. (package:scrollview_observer/src/common/observer_widget.dart:93:11)
#6 NotificationListener._dispatch (package:flutter/src/widgets/notification_listener.dart:151:42)
#7 Notification.visitAncestor (package:flutter/src/widgets/notification_listener.dart:67:20)
#8 ViewportNotificationMixin.visitAncestor (package:flutter/src/widgets/scroll_notification.dart:31:18)
#9 Element.visitAncestorElements (package:flutter/src/widgets/framework.dart:4254:39)
#10 Notification.dispatch (package:flutter/src/widgets/notification_listener.dart:83:13)
#11 DragScrollActivity.dispatchScrollStartNotification (package:flutter/src/widgets/scroll_activity.dart:458:111)
#12 ScrollPosition.didStartScroll (package:flutter/src/widgets/scroll_position.dart:895:15)
#13 ScrollPosition.beginActivity (package:flutter/src/widgets/scroll_position.dart:887:7)
#14 ScrollPositionWithSingleContext.beginActivity (package:flutter/src/widgets/scroll_position_with_single_context.dart:114:11)
#15 ScrollPositionWithSingleContext.drag (package:flutter/src/widgets/scroll_position_with_single_context.dart:264:5)
#16 ScrollableState._handleDragStart (package:flutter/src/widgets/scrollable.dart:638:22)
#17 DragGestureRecognizer._checkStart. (package:flutter/src/gestures/monodrag.dart:429:53)
#18 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:198:24)
#19 DragGestureRecognizer._checkStart (package:flutter/src/gestures/monodrag.dart:429:7)
#20 DragGestureRecognizer.acceptGesture (package:flutter/src/gestures/monodrag.dart:349:7)
#21 GestureArenaManager._resolveInFavorOf (package:flutter/src/gestures/arena.dart:264:12)
#22 GestureArenaManager._resolve (package:flutter/src/gestures/arena.dart:223:9)
#23 GestureArenaEntry.resolve (package:flutter/src/gestures/arena.dart:53:12)
#24 OneSequenceGestureRecognizer.resolve (package:flutter/src/gestures/recognizer.dart:306:13)
#25 DragGestureRecognizer.handleEvent (package:flutter/src/gestures/monodrag.dart:317:11)
#26 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:94:12)
#27 PointerRouter._dispatchEventToRoutes. (package:flutter/src/gestures/pointer_router.dart:139:9)
#28 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:539:8)
#29 PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:137:18)
#30 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:123:7)
#31 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:439:19)
#32 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:419:22)
#33 RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:322:11)
#34 GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:374:7)
#35 GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:338:5)
#36 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:296:7)
#37 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:279:7)
#41 _invoke1 (dart:ui/hooks.dart:170:10)
#42 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:331:7)
#43 _dispatchPointerDataPacket (dart:ui/hooks.dart:94:31)
(elided 3 frames from dart:async)
Handler: "onStart"
Recognizer: VerticalDragGestureRecognizer#c6d11
start behavior: start

你好,麻烦提供一下可复现问题的 demo,感谢

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:scrollview_observer/scrollview_observer.dart';
import 'package:flustars/flustars.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

// This widget is the root of your application.
@OverRide
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);

// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.

// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".

final String title;

@OverRide
State createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
int _counter = 0;
bool switchFlagDelete0 = false;
GlobalKey anchorKey = GlobalKey();
late ListObserverController observerController;
var _controllerScroll = new ScrollController(keepScrollOffset:true);

var controller = new ScrollController(keepScrollOffset:true);
int firstChildIndex = 0;
List<Map<String, dynamic>> taskcreateList = [];

@OverRide
void initState() {
super.initState();

observerController = ListObserverController(controller: _controllerScroll);
listInit();

}

listInit(){
for(int index=0;index<100;index++){
String dateTimeStr = DateFormat("yyyy-MM-dd HH:mm").format(DateTime.now().add(Duration(hours: index)));
taskcreateList.add(
{"projectcontent":"任务$index","id":index,"year":0,"month":0,
"day":0,"hour":0,"min":0,"projectid":1,
"childtime":dateTimeStr,"colorA":255,"colorR":234,"colorG":187,"colorB":53,
}
);
}
}

@OverRide
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
body: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
children: [
SafeArea(
child: Container(
height: 40,
width: double.infinity,
padding: const EdgeInsets.only(left: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkWell(
child: Container(
child: const Icon(
Icons.chevron_left,
size: 50,
color: Colors.black,
),
padding: const EdgeInsets.only(top: 2, left: 10),
),
onTap: () async {
print('返回');
},
), //返回按钮
Container(
height: 30,
width: 320,
child: Row(
children: [
Expanded(
flex: 1,
child: Container(
padding: EdgeInsets.only(left: 10),
alignment: Alignment.centerLeft,
// color: Colors.blue,
child: InkWell(
onTap: () async {
listJumpByIndex();
},
child: const Text(
'点击滚动',
style: TextStyle(
fontSize: 16,
color: Colors.black,
),
),
),
),
),
Row(
children: [
switchFlagDelete0 ?
const Text("模式二", style: TextStyle(color: Color.fromARGB(255, 80, 189, 61),),) : //排序模式
const Text("模式一", style: TextStyle(color: Colors.indigo),), //日期模式
Switch(
value: switchFlagDelete0,
activeColor: const Color.fromARGB(255, 80, 150, 61),
activeTrackColor: const Color.fromARGB(255, 80, 189, 61),
inactiveThumbColor: Colors.indigo,
inactiveTrackColor: Colors.blueAccent,
onChanged: (value) {
switchFlagDelete0 = value;
if(switchFlagDelete0){
observerController = ListObserverController(controller: controller);
}else{
observerController = ListObserverController(controller: _controllerScroll);
}
setState(() {

                              });
                            },
                          ), //模式切换开关 true=排序模式 false=日期模式
                        ],
                      )
                    ],
                  ),
                ), //模式切换开关
              ]),
          color: Colors.white,
        ),
      ), //功能栏
      Flexible(
        child: switchFlagDelete0?longPressDragFun(context: context):buildListSchedule(context),
      )//新计划列表
    ],
  ),
);

}

/// 跳转到当天第一条未过时任务方法
Future listJumpByIndex() async {

if(mounted){
  int heighten = -1;
  for(int i =0; i<taskcreateList.length;i++){
    if(taskcreateList[i]['id']==13){
      heighten = i;
      i = taskcreateList.length;
    }
  }
  if(heighten>=0){
    print('heighten>=0:$heighten');

    Future.delayed(Duration(milliseconds: 500)).then((e) {

      observerController.jumpTo(index: heighten);
    });
  }else{
    observerController.jumpTo(index: 0);
  }
}

}

Widget buildListSchedule(content){
return ListViewObserver(
controller: observerController,
onObserve: (resultModel){
if(resultModel.firstChild?.index!=null){
firstChildIndex = (resultModel.firstChild?.index)!;
}
},
child: ListView.builder(
controller: _controllerScroll,
physics:const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: taskcreateList.length,
itemBuilder: (content,i){
var txt_body = taskcreateList[i]['projectcontent'].toString();
var id = taskcreateList[i]['id'].toString();
var year = taskcreateList[i]['year'].toString();
var month = taskcreateList[i]['month'].toString();
var day = taskcreateList[i]['day'].toString();
var hour = taskcreateList[i]['hour'].toString();
var min = taskcreateList[i]['min'].toString();
var projectId = taskcreateList[i]['projectid'].toString();
var childtime = taskcreateList[i]['childtime'].toString();
var index = i;
int colorA = taskcreateList[i]['colorA'];
int colorR = taskcreateList[i]['colorR'];
int colorG = taskcreateList[i]['colorG'];
int colorB = taskcreateList[i]['colorB'];

      return Padding(
          padding: EdgeInsets.all(1.0),
          child: Column(
            children: [
              scheduleDisplayTemplate(
                  id: id,
                  txt_body: txt_body,
                  year: year,
                  month: month,
                  day: day,
                  hour: hour,
                  min: min,
                  projectId: projectId,
                  childtime: childtime,
                  context: context,
                  index: index,
                  colorA: colorA,colorR: colorR,
                  colorG: colorG,colorB: colorB
              ),
            ],
          )
      );
    },
  ),
);

}

Widget longPressDragFun({required BuildContext context}) {

return ReorderableListView(
    shrinkWrap: true,
    scrollController: controller,
    children: _getListDataLongPress(context), onReorder: _onReorder
);

}

void _onReorder(int oldIndex, int newIndex) {
print('oldIndex:$oldIndex');
}

Widget scheduleDisplayTemplate({required String id,
required String txt_body,
required String year,
required String month,
required String day,
required String hour,
required String min,
required String projectId,
required String childtime,
required int index,
required int colorA,
required int colorR,
required int colorG,
required int colorB,
required BuildContext context}) {
String scheduleDateTimeStr = '';
DateTime timeBak = DateTime.now();
Color decorationColor = Colors.white;
if(childtime!=''){
scheduleDateTimeStr = DateFormat("yyyy-MM-dd HH:mm").parse(childtime).year.toString() +'年'+
DateFormat("yyyy-MM-dd HH:mm").parse(childtime).month.toString().padLeft(2,'0') +'月'+
DateFormat("yyyy-MM-dd HH:mm").parse(childtime).day.toString().padLeft(2,'0') +'日'+' '+
DateFormat("yyyy-MM-dd HH:mm").parse(childtime).hour.toString().padLeft(2,'0') +':'+
DateFormat("yyyy-MM-dd HH:mm").parse(childtime).minute.toString().padLeft(2,'0') + ' '+
(DateFormat("yyyy-MM-dd HH:mm").parse(childtime).hour>=12?'PM':'AM');
timeBak = DateFormat("yyyy-MM-dd HH:mm").parse(childtime);
}
String cardText = '';
late Color textColor ;
if(DateTime(timeBak.year,timeBak.month,timeBak.day).isAtSameMomentAs(DateTime(DateTime.now().year,DateTime.now().month,DateTime.now().day))){
cardText = timeBak.hour.toString().padLeft(2,'0')+':'+timeBak.minute.toString().padLeft(2,'0');
textColor = Color.fromARGB(255, 255, 155, 6);
}else{
if(DateTime(timeBak.year,timeBak.month).isAtSameMomentAs(DateTime(DateTime.now().year,DateTime.now().month))){
cardText = '${timeBak.day}日';
textColor = Color.fromARGB(255, 255, 155, 6);
}else{
if(DateTime(timeBak.year).isAtSameMomentAs(DateTime(DateTime.now().year))){
cardText = '${timeBak.month}月';
textColor = Color.fromARGB(255, 15, 121, 242);
}else{
cardText = '${timeBak.year}';
textColor = Color.fromARGB(255, 171, 221, 22);
}
}
}
if(childtime==''){
cardText = '--:--';
}

return Column(
  children: [
    Container(
      key: ValueKey(id),
        constraints: BoxConstraints(
          minWidth: ScreenUtil.getInstance().getAdapterSize(360.0),
          maxWidth: ScreenUtil.getInstance().getAdapterSize(360.0),
        ),
        margin: EdgeInsets.only(top: 1, bottom: 1),
        padding: EdgeInsets.only(top: 5, bottom: 5),
        alignment: Alignment.centerRight,
        child:
        Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              constraints: BoxConstraints(maxWidth: ScreenUtil.getInstance().getAdapterSize(360.0) * 0.7,),
              decoration: BoxDecoration(
                color: Colors.white,
              ),
              alignment: Alignment.centerLeft,
              child:InkWell(
                onTap: (){

                },
                onLongPress: ()  {

                },
                child: Container(
                  // decoration: BoxDecoration(color: Colors.grey,),
                  constraints: BoxConstraints(maxWidth: ScreenUtil.getInstance().getAdapterSize(360.0) * 0.7,
                    maxHeight: double.infinity, minHeight: ScreenUtil.getInstance().getAdapterSize(360.0) * 0.135,
                  ),
                  padding: EdgeInsets.only(left: 3),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text(
                        txt_body,
                        style: TextStyle(
                          color: Colors.black,
                          fontFamily: 'webfont',
                          fontSize: 14,
                        ),
                        //style: TextStyles.listExtra,
                      ),
                      Container(
                        constraints: BoxConstraints(
                          maxWidth:ScreenUtil.getInstance().getAdapterSize(360.0) * 0.7,
                        ),
                        child: RichText(
                          text: TextSpan(
                              style: TextStyle(
                                color: Color.fromARGB(255, 103, 103, 103),
                                fontFamily: 'webfont',
                                fontSize: ScreenUtil.getInstance().getAdapterSize(11.0),
                              ),
                              children: [
                                TextSpan(
                                  text: scheduleDateTimeStr,
                                  style: TextStyle(color: Color.fromARGB(255, 103, 103, 103),),
                                ),
                                TextSpan(
                                  text: ' ',
                                  style: TextStyle(color: Color.fromARGB(255, 103, 103, 103)),
                                ),
                              ]
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ), //新计划页 列表内容
          ],
        ),
        decoration:
        new BoxDecoration(
            color: Colors.white,
            border: Border.all(color: decorationColor),
        )
    ),
  ],
)
;

}

List _getListDataLongPress(BuildContext context) {
List widgets = [
];
for (int i = 0; i < this.taskcreateList.length; i++) {
if (i == 0) {
widgets.clear();
}
var txt_body = taskcreateList[i]['projectcontent'].toString();
var id = taskcreateList[i]['id'].toString();
var year = taskcreateList[i]['year'].toString();
var month = taskcreateList[i]['month'].toString();
var day = taskcreateList[i]['day'].toString();
var hour = taskcreateList[i]['hour'].toString();
var min = taskcreateList[i]['min'].toString();
var projectId = taskcreateList[i]['projectid'].toString();
var childtime = taskcreateList[i]['childtime'].toString();
var childTimeLastTask = taskcreateList[i]['childtime'].toString();
if(i>=1){
childTimeLastTask = taskcreateList[i-1]['childtime'].toString();
}
var idNum = this.taskcreateList[i]['idNum'].toString();
var index = i;
int colorA = this.taskcreateList[i]['colorA'];
int colorR = this.taskcreateList[i]['colorR'];
int colorG = this.taskcreateList[i]['colorG'];
int colorB = this.taskcreateList[i]['colorB'];

  Color decorationColor = Colors.white;

  widgets.add(
      Column(
        key: Key(id),
        children: [
          Container(
              constraints: BoxConstraints(
                minWidth: ScreenUtil.getInstance().getAdapterSize(360.0),
                maxWidth: ScreenUtil.getInstance().getAdapterSize(360.0),
              ),
              margin: EdgeInsets.only(top: 1, bottom: 1),
              padding: EdgeInsets.only(top: 5, bottom: 5),
              alignment: Alignment.centerRight,
              child:
              Row(
                // mainAxisAlignment: MainAxisAlignment.spaceAround,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  InkWell(
                    onTap: (){

                    },
                    child: Container(
                      constraints: BoxConstraints(
                        maxWidth: ScreenUtil.getInstance().getAdapterSize(360.0) * 0.7,
                        maxHeight: double.infinity, minHeight: ScreenUtil.getInstance().getAdapterSize(360.0) * 0.135,
                      ),
                      padding: EdgeInsets.only(left: 3),
                      decoration: BoxDecoration(
                        color: Colors.white,
                      ),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Container(
                            margin: EdgeInsets.only(right: 4),
                            child: Text(
                              txt_body,
                              style: TextStyle(
                                  color: Colors.black,
                                  fontSize: 14),
                              //style: TextStyles.listExtra,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ), //排序模式 卡片 内容
                ],
              ),
              decoration:
              new BoxDecoration(
                  color: Colors.white,
                  border: Border.all(color: decorationColor),
              )
          ),
        ],
      )
  );
}
return widgets;

}

}
================分割线===================
pubspec.yaml:
name: untitled14
description: A new Flutter project.

publish_to: 'none' # Remove this line if you wish to publish to pub.dev

version: 1.0.0+1

environment:
sdk: ">=2.16.2 <3.0.0"

dependencies:
flutter:
sdk: flutter

cupertino_icons: ^1.0.2
scrollview_observer: 1.6.0
intl: ^0.17.0
flustars: ^2.0.0

dev_dependencies:
flutter_test:
sdk: flutter

flutter_lints: ^1.0.0

flutter:

uses-material-design: true

我的电脑不允许安装git只能复制粘贴了 @LinXunFeng
这个demo报错虽然跟我第一次发的不完全一致,但是我swtich切换以后再想跳转确实不好用

调整

有以下几点需要做调整

1、longPressDragFun 方法

原代码:

Widget longPressDragFun({required BuildContext context}) {
  return ReorderableListView(
      shrinkWrap: true,
      scrollController: controller,
      children: _getListDataLongPress(context), onReorder: _onReorder
  );
}

ReorderableListView 需要 ListViewObserver 包裹,且 ListViewObserver 使用 key

ListViewObserver 需要使用 key 以区别不同的视图

Widget longPressDragFun({required BuildContext context}) {
  Widget resultWidget = ReorderableListView(
      shrinkWrap: true,
      scrollController: controller,
      children: _getListDataLongPress(context),
      onReorder: _onReorder);
  return ListViewObserver(
    key: const ValueKey('ReorderableListView'),
    controller: observerController,
    onObserve: (resultModel) {
      if (resultModel.firstChild?.index != null) {
        firstChildIndex = (resultModel.firstChild?.index)!;
      }
    },
    child: resultWidget,
  );
}

2、buildListSchedule 方法

ListViewObserver 使用 key

Widget buildListSchedule(content) {
  return ListViewObserver(
    key: const ValueKey('ListView'),
    ...
    child: ListView.builder(
      ...
    ),
  );
}
  1. 你说的【嵌套 RecorderableListView 滚动还是不好用】是怎么个嵌套法?希望能有示例代码
  2. RecorderableListView 底层也是 RenderSliverList,所以是支持的

shrinkWrap1.6.1 开始也是支持的,最好不要写死版本号

我找到为啥不好用了,是因为有 shrinkWrap: true, 属性的原因