Draggable publisher rebuilds the native view on each drag, can this be improved?
What happens
Once we drag the publisher around it disposes and re-creates the AndroidView
class each time.
Related code
Creation of the publisher:
final localParticipant = _participants.firstWhere((ParticipantModel participant) => !participant.isRemote, orElse: () => null);
if (localParticipant != null) {
children.add(DraggablePublisher(
child: localParticipant.widget,
availableScreenSize: size,
onButtonBarVisible: _onButtonBarVisible.stream,
));
}
localParticipant.wiget
is eventually this widget that gets called:
Widget widget({bool mirror = true}) {
var creationParams = {
'isLocal': true,
'mirror': mirror,
};
return _widget ??= AndroidView(
viewType: 'twilio_unofficial_programmable_video/views',
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
onPlatformViewCreated: (int viewId) {
print('LocalView created => $viewId, creationParams: ${creationParams}');
},
);
}
The DraggablePublisher
looks like this:
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
class DraggablePublisher extends StatefulWidget {
final Size availableScreenSize;
final Widget child;
final double scaleFactor;
final Stream<bool> onButtonBarVisible;
const DraggablePublisher({
Key key,
@required this.availableScreenSize,
this.child,
@required this.onButtonBarVisible,
/// The portion of the screen the DraggableWidget should use.
this.scaleFactor = .25,
}) : assert(scaleFactor != null && scaleFactor > 0 && scaleFactor <= .4),
assert(availableScreenSize != null),
assert(onButtonBarVisible != null),
super(key: key);
@override
_DraggablePublisherState createState() => _DraggablePublisherState();
}
class _DraggablePublisherState extends State<DraggablePublisher> {
bool _isButtonBarVisible = true;
double _bottom = 80;
double _right = 10;
double _width;
double _height;
final Duration _duration300ms = const Duration(milliseconds: 300);
final Duration _duration0ms = const Duration(milliseconds: 0);
Duration _duration;
@override
void initState() {
super.initState();
_duration = _duration300ms;
widget.onButtonBarVisible.listen(_buttonBarVisible);
}
void _buttonBarVisible(bool visible) {
if (!mounted) {
return;
}
setState(() {
_isButtonBarVisible = visible;
if (visible) {
if (_bottom <= 80) {
_bottom = math.min(_bottom += 70, 80);
}
} else {
if (_bottom <= 80) {
_bottom = math.max(_bottom -= 70, 10);
}
}
});
}
@override
Widget build(BuildContext context) {
_width = widget.availableScreenSize.width * widget.scaleFactor;
_height = _width * (widget.availableScreenSize.height / widget.availableScreenSize.width);
Widget clippedVideo = Container(
width: _width,
height: _height,
child: ClipRRect(
child: widget.child,
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
);
return AnimatedPositioned(
right: _right,
bottom: _bottom,
width: _width,
height: _height,
duration: _duration,
child: Draggable(
child: clippedVideo,
feedback: clippedVideo,
childWhenDragging: Container(),
onDraggableCanceled: _onDraggableCanceled,
onDragEnd: _onDragEnd,
onDragStarted: _onDragStarted,
),
);
}
void _onDragStarted() {
// Don't want to animate the position changes whilst dragging
_duration = _duration0ms;
}
void _onDragEnd(DraggableDetails details) {
// Record the current positions as the starting point of the animation
// to it's final corner in the [_onDraggableCanceled] function.
setState(() {
_bottom = (widget.availableScreenSize.height - (details.offset.dy + _height));
_right = widget.availableScreenSize.width - (details.offset.dx + _width);
});
}
void _onDraggableCanceled(Velocity velocity, Offset offset) {
// Determine the center of the object being dragged so we can decide
// in which corner the object should be placed.
var dx = (_width / 2) + offset.dx;
dx = dx < 0 ? 0 : dx >= widget.availableScreenSize.width ? widget.availableScreenSize.width - 1 : dx;
var dy = (_height / 2) + offset.dy;
dy = dy < 0 ? 0 : dy >= widget.availableScreenSize.height ? widget.availableScreenSize.height - 1 : dy;
var draggableCenter = Offset(dx, dy);
// We need a small delay here, because otherwise the property changes
// in the [_onDragEnd] function will also animate, and we don't want that!
Timer(const Duration(milliseconds: 50), () {
setState(() {
_duration = _duration300ms;
if (Rect.fromLTRB(0, 0, widget.availableScreenSize.width / 2, widget.availableScreenSize.height / 2).contains(draggableCenter)) {
_bottom = widget.availableScreenSize.height - (30 + _height);
_right = widget.availableScreenSize.width - (10 + _width);
} else if (Rect.fromLTRB(widget.availableScreenSize.width / 2, 0, widget.availableScreenSize.width, widget.availableScreenSize.height / 2).contains(draggableCenter)) {
_bottom = widget.availableScreenSize.height - (30 + _height);
_right = 10;
} else if (Rect.fromLTRB(0, widget.availableScreenSize.height / 2, widget.availableScreenSize.width / 2, widget.availableScreenSize.height).contains(draggableCenter)) {
_bottom = _isButtonBarVisible ? 70 : 10;
_right = widget.availableScreenSize.width - (10 + _width);
} else if (Rect.fromLTRB(widget.availableScreenSize.width / 2, widget.availableScreenSize.height / 2, widget.availableScreenSize.width, widget.availableScreenSize.height).contains(draggableCenter)) {
_bottom = _isButtonBarVisible ? 70 : 10;
_right = 10;
}
});
});
}
}
What to improve?
Above works, but could we change this so the native view is not always rebuild?