How to update Draggable child when entering DragTarget in Flutter?

后端 未结 1 589
孤城傲影
孤城傲影 2020-12-16 06:43

I have a number of Draggables and DragTargets. On the Draggables I have specified child and feedback, however I also want it to change it\'s look when the

相关标签:
1条回答
  • 2020-12-16 07:43

    The only way I can think of is using a streambuilder and a stream that will carry the information of whether the draggable is on a drag target. This code provides a basic solution.

    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);
                },
              ),
            ],
          ),
        );
      }
    }
    

    Edit: The first example doesn't handle multiple drags at the same time this on does and it's a little cleaner.

    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));
      }
    }
    

    Edit 2: Solution without using streams and streambuilder. The important part is the feedback widget is stateful.

    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);
      }
    }
    
    0 讨论(0)
提交回复
热议问题