【问题标题】:How to update Draggable child when entering DragTarget in Flutter?在 Flutter 中输入 DragTarget 时如何更新 Draggable child?
【发布时间】:2019-06-25 11:36:21
【问题描述】:

我有许多 Draggables 和 DragTargets。在 Draggables 上,我指定了 childfeedback,但是我也希望它在 Draggable 进入 DragTarget 时改变它的外观。我看不出有什么办法。

每当我拖动 Draggable 时,我都会将其颜色更改为红色,但是一旦它进入 DragTarget 我想将 Draggable 颜色更新为绿色。

我知道 DragTarget.OnWillAccept,每当 Draggable 进入 DragTarget 时都会调用此方法,但我只有 数据。我尝试用新颜色更新数据,然后调用 setState,但这似乎不起作用。

关于如何获得这种行为的任何建议?

我想要类似下面的回调 Draggable.onEnteringDragTargetDraggable.onLeavingDragTarget

【问题讨论】:

    标签: drag-and-drop flutter draggable flutter-layout


    【解决方案1】:

    我能想到的唯一方法是使用一个流构建器和一个流,该流将携带有关可拖动对象是否在拖动目标上的信息。这段代码提供了一个基本的解决方案。

    class _MyHomePageState extends State<MyHomePage> {
      BehaviorSubject<bool> willAcceptStream;
    
      @override
      void initState() {
        willAcceptStream = new BehaviorSubject<bool>();
        willAcceptStream.add(false);
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Column(
            children: <Widget>[
              Container(
                height: 400,
                width: double.infinity,
                child: Container(
                  width: 100,
                  height: 100,
                  child: Center(
                    child: Draggable(
                      feedback: StreamBuilder(
                        initialData: false,
                        stream: willAcceptStream,
                        builder: (context, snapshot) {
                          return Container(
                            height: 100,
                            width: 100,
                            color: snapshot.data ? Colors.green : Colors.red,
                          );
                        },
                      ),
                      childWhenDragging: Container(),
                      child: Container(
                        height: 100,
                        width: 100,
                        color: this.willAcceptStream.value ?? false
                            ? Colors.green
                            : Colors.blue,
                      ),
                      onDraggableCanceled: (v, f) => setState(
                            () {
                              this.willAcceptStream.add(false);
                            },
                          ),
                    ),
                  ),
                ),
              ),
              DragTarget(
                builder: (context, list, list2) {
                  return Container(
                    height: 50,
                    width: double.infinity,
                    color: Colors.blueGrey,
                    child: Center(child: Text('TARGET ZONE'),),
                  );
                },
                onWillAccept: (item) {
                  debugPrint('will accept');
                  this.willAcceptStream.add(true);
                  return true;
                },
                onLeave: (item) {
                  debugPrint('left the target');
                  this.willAcceptStream.add(false);
                },
              ),
            ],
          ),
        );
      }
    }
    

    编辑:第一个示例不会同时处理多个拖动,并且它更简洁。

    import 'package:rxdart/rxdart.dart';
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Draggable Test',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key}) : super(key: key);
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      MyDraggableController<String> draggableController;
    
      @override
      void initState() {
        this.draggableController = new MyDraggableController<String>();
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Draggable Test'),
          ),
          body: Column(
            children: <Widget>[
              Container(
                height: 400,
                width: double.infinity,
                child: Container(
                  width: 100,
                  height: 100,
                  child: Center(
                    child: Stack(
                      children: <Widget>[
                        Positioned(
                          left: 30,
                          top: 30,
                          child: MyDraggable<String>(draggableController, 'Test1'),
                        ),
                        Positioned(
                          left: 230,
                          top: 230,
                          child: MyDraggable<String>(draggableController, 'Test2'),
                        )
                      ],
                    ),
                  ),
                ),
              ),
              DragTarget<String>(
                builder: (context, list, list2) {
                  return Container(
                    height: 50,
                    width: double.infinity,
                    color: Colors.blueGrey,
                    child: Center(
                      child: Text('TARGET ZONE'),
                    ),
                  );
                },
                onWillAccept: (item) {
                  debugPrint('draggable is on the target');
                  this.draggableController.onTarget(true, item);
                  return true;
                },
                onLeave: (item) {
                  debugPrint('draggable has left the target');
                  this.draggableController.onTarget(false, item);
                },
              ),
            ],
          ),
        );
      }
    }
    
    class MyDraggable<T> extends StatefulWidget {
      final MyDraggableController<T> controller;
      final T data;
      MyDraggable(this.controller, this.data);
      @override
      _MyDraggableState createState() =>
          _MyDraggableState<T>(this.controller, this.data);
    }
    
    class _MyDraggableState<T> extends State<MyDraggable> {
      BehaviorSubject<DraggableInfo<T>> willAcceptStream;
      MyDraggableController<T> controller;
      T data;
      _MyDraggableState(this.controller, this.data);
    
      @override
      void initState() {
        willAcceptStream = this.controller._isOnTarget;
        willAcceptStream.add(new DraggableInfo<T>(false, this.data));
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Draggable<T>(
          data: this.data,
          feedback: StreamBuilder<DraggableInfo<T>>(
            initialData: DraggableInfo<T>(false, this.data),
            stream: willAcceptStream,
            builder: (context, snapshot) {
              return Container(
                height: 100,
                width: 100,
                color: snapshot.data.isOnTarget && snapshot.data.data == this.data ? Colors.green : Colors.red,
              );
            },
          ),
          childWhenDragging: Container(),
          child: Container(
            height: 100,
            width: 100,
            color: (this.willAcceptStream.value.isOnTarget ?? this.willAcceptStream.value.data == this.data)
                ? Colors.green
                : Colors.blue,
          ),
          onDraggableCanceled: (v, f) => setState(
                () {
                  this.willAcceptStream.add(DraggableInfo(false, null));
                },
              ),
        );
      }
    }
    
    class DraggableInfo<T> {
      bool isOnTarget;
      T data;
      DraggableInfo(this.isOnTarget, this.data);
    }
    
    class MyDraggableController<T> {
      BehaviorSubject<DraggableInfo<T>> _isOnTarget;
    
      MyDraggableController() {
        this._isOnTarget = new BehaviorSubject<DraggableInfo<T>>();
      }
    
      void onTarget(bool onTarget, T data) {
        _isOnTarget.add(new DraggableInfo(onTarget, data));
      }
    }
    

    编辑 2:不使用流和流构建器的解决方案。重要的部分是反馈小部件是有状态的。

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Draggable Test',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key}) : super(key: key);
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      MyDraggableController<String> draggableController;
    
      @override
      void initState() {
        this.draggableController = new MyDraggableController<String>();
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Draggable Test'),
          ),
          body: Column(
            children: <Widget>[
              Container(
                height: 400,
                width: double.infinity,
                child: Container(
                  width: 100,
                  height: 100,
                  child: Center(
                    child: Stack(
                      children: <Widget>[
                        Positioned(
                          left: 30,
                          top: 30,
                          child: MyDraggable<String>(
                            draggableController,
                            'Test1',
                          ),
                        ),
                        Positioned(
                          left: 230,
                          top: 230,
                          child: MyDraggable<String>(
                            draggableController,
                            'Test2',
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
              DragTarget<String>(
                builder: (context, list, list2) {
                  return Container(
                    height: 100,
                    width: double.infinity,
                    color: Colors.blueGrey,
                    child: Center(
                      child: Text('TARGET ZONE'),
                    ),
                  );
                },
                onWillAccept: (item) {
                  debugPrint('draggable is on the target $item');
                  this.draggableController.onTarget(true, item);
                  return true;
                },
                onLeave: (item) {
                  debugPrint('draggable has left the target $item');
                  this.draggableController.onTarget(false, item);
                },
              ),
            ],
          ),
        );
      }
    }
    
    class MyDraggable<T> extends StatefulWidget {
      final MyDraggableController<T> controller;
      final T data;
      MyDraggable(this.controller, this.data, {Key key}) : super(key: key);
      @override
      _MyDraggableState createState() =>
          _MyDraggableState<T>(this.controller, this.data);
    }
    
    class _MyDraggableState<T> extends State<MyDraggable> {
      MyDraggableController<T> controller;
      T data;
      bool isOnTarget;
      _MyDraggableState(this.controller, this.data);
      FeedbackController feedbackController;
      @override
      void initState() {
        feedbackController = new FeedbackController();
    
        this.controller.subscribeToOnTargetCallback(onTargetCallbackHandler);
    
        super.initState();
      }
    
      void onTargetCallbackHandler(bool t, T data) {
        this.isOnTarget = t && data == this.data;
        this.feedbackController.updateFeedback(this.isOnTarget);
      }
    
      @override
      void dispose() {
        this.controller.unSubscribeFromOnTargetCallback(onTargetCallbackHandler);
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Draggable<T>(
          data: this.data,
          feedback: FeedbackWidget(feedbackController),
          childWhenDragging: Container(
            height: 100,
            width: 100,
            color: Colors.blue[50],
          ),
          child: Container(
            height: 100,
            width: 100,
            color: (this.isOnTarget ?? false) ? Colors.green : Colors.blue,
          ),
          onDraggableCanceled: (v, f) => setState(
                () {
                  this.isOnTarget = false;
                  this.feedbackController.updateFeedback(this.isOnTarget);
                },
              ),
        );
      }
    }
    
    class FeedbackController {
      Function(bool) feedbackNeedsUpdateCallback;
    
      void updateFeedback(bool isOnTarget) {
        if (feedbackNeedsUpdateCallback != null) {
          feedbackNeedsUpdateCallback(isOnTarget);
        }
      }
    }
    
    class FeedbackWidget extends StatefulWidget {
      final FeedbackController controller;
      FeedbackWidget(this.controller);
      @override
      _FeedbackWidgetState createState() => _FeedbackWidgetState();
    }
    
    class _FeedbackWidgetState extends State<FeedbackWidget> {
      bool isOnTarget;
    
      @override
      void initState() {
        this.isOnTarget = false;
        this.widget.controller.feedbackNeedsUpdateCallback = feedbackNeedsUpdateCallbackHandler;
        super.initState();
      }
    
      void feedbackNeedsUpdateCallbackHandler(bool t) {
        setState(() {
          this.isOnTarget = t;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Container(
          height: 100,
          width: 100,
          color: this.isOnTarget ?? false ? Colors.green : Colors.red,
        );
      }
    
      @override
      void dispose() {
        this.widget.controller.feedbackNeedsUpdateCallback = null;
        super.dispose();
      }
    }
    
    class DraggableInfo<T> {
      bool isOnTarget;
      T data;
      DraggableInfo(this.isOnTarget, this.data);
    }
    
    class MyDraggableController<T> {
      List<Function(bool, T)> _targetUpdateCallbacks = new List<Function(bool, T)>();
    
      MyDraggableController();
    
      void onTarget(bool onTarget, T data) {
        if (_targetUpdateCallbacks != null) {
          _targetUpdateCallbacks.forEach((f) => f(onTarget, data));
        }
      }
    
      void subscribeToOnTargetCallback(Function(bool, T) f) {
        _targetUpdateCallbacks.add(f);
      }
    
      void unSubscribeFromOnTargetCallback(Function(bool, T) f) {
        _targetUpdateCallbacks.remove(f);
      }
    }
    

    【讨论】:

    • 谢谢。我最初尝试的是在 Data 项上添加一个 VoidCallback,然后从 onWillAccept 我尝试调用该回调。 VoidCallback 被初始化为确保更新 onTarget 属性并在 _MyDraggableState 中调用 setState 的方法。你有什么想法,为什么这不起作用?因为这似乎是比使用流更清洁的解决方案。
    • @dynamokaj 我让它在没有流的情况下工作并更新了我的答案。我需要创建一个单独的有状态小部件以用作反馈并明确更新此反馈小部件的状态。我真的不知道为什么您的方式不起作用,但我想这与构建通过后添加到树中的反馈小部件有关,并且具有不同的 buildContext。但我只是猜测。
    • 知道为什么 ChangeNotifier 在这里不起作用吗?我正在使用 context.watch() 但没有更新
    猜你喜欢
    • 2021-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-07
    • 1970-01-01
    • 2020-02-29
    • 1970-01-01
    相关资源
    最近更新 更多