Hi I have to translate two of my widgets based on the DraggableScrollableSheet drag position (you can think this bottom sheet as in GMaps). The widgets I have to move/animates are:
Text - It needs to translate & scale back-and-forth and in-out respectively following an arc path simultaneously. (See expected video)
Row - It needs to translate a bit and then out. Again back-and-forth.
Trails -
Text widget scaling in-out properly but translating in some weird way. (See actual video)
Row widget is fading in-out fine, but having a hard time translating it.
Expected Vid -
https://drive.google.com/file/d/1wg3q8KJc6zMe3b6mw-LgPN62FxK4dMMj/view?usp=sharing
actual vid -
https://drive.google.com/file/d/1umjDVj6PXqiatF6JVt8EeKTCxU2LyM7E/view?usp=sharing
Code -
final Duration _animationDuration = const Duration(milliseconds: 300);
bool _isNavShown = true;
double yValue = -35.0;
void _applyEffectsOnDrag(double factor) {
print(factor);
if (factor > 0.7) {
_isNavShown = false;
} else {
_isNavShown = true;
}
}
Widget _getNavigationButtons(BuildContext ctx, bool isHidden) {
return Padding(
padding: verticalAndHorizontailPadding,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
...[1, 2, 3]
.map(
(e) => Container(
decoration: CircleStyles.navButton,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: padding
decoration: decoration,
child: Icon(Icons.verified_user),
),
SizedBox(height: AppMediaQuery(ctx).appHeight(2)),
Text('block heading')
],
),
),
)
.toList()
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(gradient: Variables.circleGradient),
child: Stack(
children: [
Column(
children: [
Padding(
padding: EdgeInsets.symmetric(
horizontal: AppMediaQuery(context).appHorizontalPadding(4),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
// TODO: Goto home screen
},
child: Container(
alignment: Alignment.center,
width: 32,
height: 32,
decoration: backButtonStyle,
child: const Icon(
Icons.chevron_left,
),
),
),
IconButton(icon: const Icon(Icons.more_horiz), onPressed: null),
],
),
),
TweenAnimationBuilder(
duration: _animationDuration,
curve: Curves.easeInOut,
tween: Tween<double>(begin: 0, end: !_isNavShown ? yValue : 0),
child: Padding(
padding: somePadding,
child: const Align(
alignment: Alignment.centerLeft,
child: const Text('Circles'),
),
),
builder: (_, double dy, Widget ch) {
return Transform.scale(
scale: _isNavShown ? 1 : 0.8,
child: Transform.translate(
offset: Offset(0, dy),
child: ch,
),
);
},
),
AnimatedOpacity(
duration: _animationDuration,
opacity: _isNavShown ? 1 : 0,
child: _getNavigationButtons(context, true),
),
],
),
NotificationListener<DraggableScrollableNotification>(
onNotification: (obj) {
setState(() {
return _applyEffectsOnDrag(obj.extent);
});
},
child: DraggableScrollableSheet(
initialChildSize: 0.6,
minChildSize: 0.6,
maxChildSize: 0.9,
builder: (context, ctrl) {
return Container(
decoration: CircleStyles.sheetBase,
child: ConversationList(ctrl),
);
},
),
),
],
),
),
);
}
Related
my video call output
when logged another person to this video call then shows like this.But I want when someone logged to video call then my video should be small.
like this.
and also when click my video then my video should be big and another person's video should be small. how to do that ? I couldn't find the any documentation how to do that
Code video call ui
// video view
Widget _viewRows() {
final views = _getRenderViews();
switch (views.length) {
case 1:
return Column(
children: <Widget>[_videoView(views[0])],
);
case 2:
return Column(
children: <Widget>[
_expandedVideoRow([views[0]]),
_expandedVideoRow([views[1]])
],
);
case 3:
return Column(
children: <Widget>[
_expandedVideoRow(views.sublist(0, 2)),
_expandedVideoRow(views.sublist(2, 3))
],
);
case 4:
return Column(
children: <Widget>[
_expandedVideoRow(views.sublist(0, 2)),
_expandedVideoRow(views.sublist(2, 4))
],
);
default:
}
return Container();
}
How customize the video UI like as I mentioned?
error
To create the layout which you want, edit _viewRow(CallNotifier notifier) and _expandedVideoRow(List views) function with following code : -
Widget _viewRows(CallNotifier notifier) : -
case 2:
return Container(
margin: EdgeInsets.only(top: 100, bottom: 100),
child: Stack(
children: [
_expandedVideoRow([views[secondScreen]]),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(right: 10, bottom: 10),
child: GestureDetector(
onTap: () {
tempSwap = firstScreen;
firstScreen = secondScreen;
secondScreen = tempSwap;
setState(() {});
},
child: SizedBox(
height: 200,
width: 100,
child: _expandedVideoRow([views[firstScreen]])),
),
),
),
],
));
Above code contains _expandedVideoRow([views[secondScreen]]), which is just a simple Expandable Container and we are passing the index of the screen as a parameter. In our case, there are 2 screens hence 2 index that is 0 and 1. I have declared three integer variables here, int firstScreen = 0, int secondScreen = 1 and int tempSwap = 0. The second _expandedVideoRow([views[firstScreen]]) is wrapped by GesutreDector, so when the user taps on that screen the indexes of the variable are swapped which results in swapping the screens, SizedBox to reduce its width and height and Align widget to give the desired position to the second screen.
Widget _expandedVideoRow(List views) : -
Widget _expandedVideoRow(List<Widget> views) {
final wrappedViews = views.map<Widget>(_videoView).toList();
return Row(
children: wrappedViews,
);
}
Remove the Expanded widget that wraps the row like the above code because we can't use the Expanded under the Stack widget.
If you wish to change the bottom icons, then change _toolbar(CallNotifier notifier) function according to your need.
Widget _toolbar(CallNotifier notifier) {
return Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.symmetric(vertical: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RawMaterialButton(
onPressed: () {
_onToggleMute(notifier);
setState(() {
isMute = !isMute;
});
},
child: Icon(
isMute ? Icons.mic_off : Icons.mic,
color: isMute ? Colors.white : Colors.teal,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: isMute ? Colors.teal : Colors.white,
padding: const EdgeInsets.all(12.0),
),
RawMaterialButton(
onPressed: () => _onCallEnd(context),
child: Icon(
Icons.call_end,
color: Colors.white,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.redAccent,
padding: const EdgeInsets.all(15.0),
),
],
),
);
}
Code which I use in my app, full code : -
class _CallScreenState extends State<CallScreen> {
double globalHeight;
int firstScreen = 0;
int secondScreen = 1;
int tempSwap = 0;
bool isMute = false;
void initState() {
super.initState();
}
List<Widget> _getRenderViews(CallNotifier model) {
final List<StatefulWidget> list = [];
list.add(RtcLocalView.SurfaceView());
model.users
.forEach((int uid) => list.add(RtcRemoteView.SurfaceView(uid: uid)));
return list;
}
Widget _videoView(view) {
return Expanded(child: Container(child: view));
}
Widget _expandedVideoRow(List<Widget> views) {
final wrappedViews = views.map<Widget>(_videoView).toList();
return Row(
children: wrappedViews,
);
}
Widget _viewRows(CallNotifier notifier) {
final views = _getRenderViews(notifier);
switch (views.length) {
case 1:
return Container(
margin: EdgeInsets.only(top: 100, bottom: 100),
child: Column(
children: <Widget>[_videoView(views[0])],
));
case 2:
return Container(
margin: EdgeInsets.only(top: 100, bottom: 100),
child: Stack(
children: [
_expandedVideoRow([views[secondScreen]]),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(right: 10, bottom: 10),
child: GestureDetector(
onTap: () {
tempSwap = firstScreen;
firstScreen = secondScreen;
secondScreen = tempSwap;
setState(() {});
},
child: SizedBox(
height: 200,
width: 100,
child: _expandedVideoRow([views[firstScreen]])),
),
),
),
],
));
default:
}
return Container();
}
Widget _toolbar(CallNotifier notifier) {
return Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.symmetric(vertical: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RawMaterialButton(
onPressed: () {
_onToggleMute(notifier);
setState(() {
isMute = !isMute;
});
},
child: Icon(
isMute ? Icons.mic_off : Icons.mic,
color: isMute ? Colors.white : Colors.teal,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: isMute ? Colors.teal : Colors.white,
padding: const EdgeInsets.all(12.0),
),
RawMaterialButton(
onPressed: () => _onCallEnd(context),
child: Icon(
Icons.call_end,
color: Colors.white,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.redAccent,
padding: const EdgeInsets.all(15.0),
),
],
),
);
}
void _onCallEnd(BuildContext context) {
Navigator.pop(context);
}
void _onToggleMute(CallNotifier notifier) {
notifier.isMuted = notifier.isMuted;
notifier.engine.muteLocalAudioStream(notifier.isMuted);
}
#override
Widget build(BuildContext context) {
return BaseWidget<CallNotifier>(
model: CallNotifier(),
onModelReady: (model) => model.init(widget.channelName, widget.token),
builder: (context, notifier, child) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Stack(
children: <Widget>[
_viewRows(notifier),
Align(
alignment: Alignment.bottomCenter,
child: _toolbar(notifier)),
],
),
),
);
});
}
}
As you may know, the Stepper widget puts step items into a row until it overflows the screen, with no way to make it scroll.
However, I found a way found in this link - https://github.com/flutter/flutter/issues/40601
Here is the code:
Widget _buildHorizontal() {
final List children = [
for (int i = 0; i < widget.steps.length; i += 1) ...[
InkResponse(
onTap: widget.steps[i].state != StepState.disabled
? () {
if (widget.onStepTapped != null) widget.onStepTapped(i);
}
: null,
canRequestFocus: widget.steps[i].state != StepState.disabled,
child: Row(
children: [
Container(
height: 72.0,
child: Center(
child: _buildIcon(i),
),
),
Container(
margin: const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(i),
),
],
),
),
if (!_isLast(i))
Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
height: 1.0,
color: Colors.grey.shade400,
),
],
];
return Column(
children: <Widget>[
Material(
elevation: 2.0,
child: Container(
height: 65,
margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: ListView(
scrollDirection: Axis.horizontal,
children: children,
),
),
),
Expanded(
child: ListView(
padding: const EdgeInsets.all(24.0),
children: <Widget>[
AnimatedSize(
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
vsync: this,
child: widget.steps[widget.currentStep].content,
),
_buildVerticalControls(),
],
),
),
],
);
}`
My issue right now is that I cannot make the ListView autoscrollable.
I have tried to use _scrollController = ItemScrollController but did not help.
I want lazy scrolling when my all page is scrolled so i have to use Custom Scroll View.
now CustomScrollView can have just silvers children, my problem is have many lists and widgets,
expanded widget that has his own list.
to achieve the best lazy scrolling i have to some how get to top level widget all my lists and that can become very nasty.
LeagueGamesList widget contain list of expanded widgets that every expanded widget is a list.
how i can do it and still keep order and maintain widgets?
SliverList(
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
return Container(
padding: EdgeInsets.only(left: 15),
child: Column(
children: <Widget>[
if (_highlights.length > 0)
Column(
children: [
Container(
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Row(
children: [
Text(
'Highlights',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
Image.asset(
AppIcons.rightArrow,
alignment: Alignment.center,
width: 20,
height: 20,
)
],
),
),
),
),
Container(
child: Align(
alignment: Alignment.centerLeft,
child: HighlightRow(highlights: _highlights),
),
),
],
),
Visibility(
visible: _advertisements.length != 0,
child: Padding(
padding: EdgeInsets.only(right: 15),
child: HomepageBanner(
advertisement: _homepageAdvertisement,
),
),
),
if (Provider.of<ArticlesProvider>(context).get().length > 0)
HomepageArticles(
articles: Provider.of<ArticlesProvider>(context).get(),
),
],
),
);
}, childCount: 1),
),
SliverList(
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
return Column(
children: [
Visibility(
visible: isLoading,
child: SpinKitFadingCircle(
color: Colors.black,
size: 20,
),
replacement: SizedBox(height: 20),
),
DateButton(
dateString: getDateString(),
dayForwards: dayForwards,
dayBackwards: dayBackwards,
),
SizedBox(height: 20),
],
);
}, childCount: 1),
),
SliverList(
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
print(index);
return LeagueGamesList(
leagueFixture: leagueFixtures[index],
closeExpanders: false,
openExpanders: isDateChanged,
rowHeight: rowHeight,
);
}, childCount: leagueFixtures.length),
),
],
)
my expanded container that will contain a list
class ExpandableContainer extends StatelessWidget {
final bool expanded;
final double collapsedHeight;
final double expandedHeight;
final Widget child;
ExpandableContainer({
#required this.child,
this.collapsedHeight = 0.0,
this.expandedHeight = 0,
this.expanded = true,
});
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return new AnimatedContainer(
duration: new Duration(milliseconds: 500),
curve: Curves.easeInOut,
width: screenWidth,
height: expanded ? expandedHeight : collapsedHeight,
padding: EdgeInsets.all(0),
margin: EdgeInsets.all(0),
alignment: Alignment.topLeft,
child: child,
);
}
Instead of nesting lists in lists make only the deepest list be actual SliverLists and combine them together using MultiSlivers from my package sliver_tools
For screen a Column with a row of two Containers, each showing an image, and an undo FlatButton beneath the row, when I use the InteractiveViewer for the second one in the row it works perfectly but for the first when it is expanded it has the Undo button on top of the image.
Would like for when either of the Containers are used with the Interactive Viewer that they be at the top of the view.
Would also like for Interactive Viewer containers to go full screen on a long press and exit full screen when long press is released.
final TransformationController _transformationController =
TransformationController();
Animation<Matrix4> _animationReset;
AnimationController _controllerReset;
void _onAnimateReset() {
_transformationController.value = _animationReset.value;
if (!_controllerReset.isAnimating) {
_animationReset?.removeListener(_onAnimateReset);
_animationReset = null;
_controllerReset.reset();
}
}
void _animateResetInitialize() {
_controllerReset.reset();
_animationReset = Matrix4Tween(
begin: _transformationController.value,
end: Matrix4.identity(),
).animate(_controllerReset);
_animationReset.addListener(_onAnimateReset);
_controllerReset.forward();
}
void _animateResetStop() {
_controllerReset.stop();
_animationReset?.removeListener(_onAnimateReset);
_animationReset = null;
_controllerReset.reset();
}
void _onInteractionStart(ScaleStartDetails details) {
// If the user tries to cause a transformation while the reset animation is
// running, cancel the reset animation.
if (_controllerReset.status == AnimationStatus.forward) {
_animateResetStop();
}
}
void _onInteractionEnd(ScaleEndDetails details) {
_animateResetInitialize();
}
final TransformationController _transformationController2 =
TransformationController();
Animation<Matrix4> _animationReset2;
AnimationController _controllerReset2;
void _onAnimateReset2() {
_transformationController2.value = _animationReset2.value;
if (!_controllerReset2.isAnimating) {
_animationReset2?.removeListener(_onAnimateReset2);
_animationReset2 = null;
_controllerReset2.reset();
}
}
void _animateResetInitialize2() {
_controllerReset2.reset();
_animationReset2 = Matrix4Tween(
begin: _transformationController2.value,
end: Matrix4.identity(),
).animate(_controllerReset2);
_animationReset2.addListener(_onAnimateReset2);
_controllerReset2.forward();
}
void _animateResetStop2() {
_controllerReset2.stop();
_animationReset2?.removeListener(_onAnimateReset2);
_animationReset2 = null;
_controllerReset2.reset();
}
void _onInteractionStart2(ScaleStartDetails details) {
// If the user tries to cause a transformation while the reset animation is
// running, cancel the reset animation.
if (_controllerReset2.status == AnimationStatus.forward) {
_animateResetStop2();
}
}
void _onInteractionEnd2(ScaleEndDetails details) {
_animateResetInitialize2();
}
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: BGColor,
appBar: AppBar(
backgroundColor: PrimaryColor,
title: Text('App\'name',
style: TextStyle(
color: Colors.white
),
),
leading: GestureDetector(
onTap: () {
Navigator.of(context).pop();},
child: Padding(
padding: EdgeInsets.only(top: 19.0, left: 12.0),
child: Text('Quit',
style: TextStyle(
color: Colors.white54,
fontSize: 18.0,
),
),
),
),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
flex: 1,
child: tapInstruct(),
),
SizedBox(height: 5.0),
Flexible(
flex: 9,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
flex: 1,
child: InteractiveViewer(
boundaryMargin: EdgeInsets.all(double.infinity),
transformationController: _transformationController,
minScale: 1.0,
maxScale: 5,
onInteractionStart: _onInteractionStart,
onInteractionEnd: _onInteractionEnd,
panEnabled: false,
child: Container(
//margin: EdgeInsets.all(10.0),
child: GestureDetector(
onTap: () => _leftCardTapped(),
child: Padding(
padding: const EdgeInsets.all(2.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(15.0),
child: Image.file(
favpicz[lcn]
// fit: BoxFit.fitHeight,
),
),
),
// ),
),
),
),
),
Divider(
thickness: 2.0,
),
Expanded(
flex: 1,
child: InteractiveViewer(
boundaryMargin: EdgeInsets.all(double.infinity),
transformationController: _transformationController2,
minScale: 1.0,
maxScale: 5,
onInteractionStart: _onInteractionStart2,
onInteractionEnd: _onInteractionEnd2,
panEnabled: false,
child: Container(
//margin: EdgeInsets.all(10.0),
child: GestureDetector(
onTap: () => _rightCardTapped(),
//onLongPress: //TODO full screen image,
// onLongPressEnd: ,//TODO exit full screen
//child: Card(
child: Padding(
padding: const EdgeInsets.all(2.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(15.0),
child: Image.file(
favpicz[rcn],
),
),
),
//),
),
),
),
),
],
),
),
SizedBox(height: 5.0),
Flexible(
flex: 1,
child:
GestureDetector(
child: FlatButton(
onPressed: () => undo(),
child: Text('Undo',
style: TextStyle(
color: BGColor
),),
color: undoButtonColor,
),
),
)
],
),
),
);
}
I was able to get this to look the way I wanted by wrapping the two Containers and the FlatButton in Visibility widgets and creating functions to set the visibility of the other container and flat button to false when the InteractiveViewer animation began and true when it ended.
Using the Visibility widget also worked for the desired full screen effect on long hold.
I have a function that returns a Widget into my builder.
I want it to return a ListView, but everything below the Appbar just gets white with this piece of code in my return:
#override
Widget details(BuildContext context) {
var cell = (Widget widget) => Padding(
padding: EdgeInsets.all(5),
child: widget,
);
var locale = Localizations.localeOf(context);
return ListView(
children: <Widget>[
ListTile(title: Text("Test")),
],
);
}
The calling builder has this as body:
return Container(
//padding: EdgeInsets.all(32),
child: Column(
children: [
Container(
color: Color.fromRGBO(49, 161, 181, 0.4),
child:
Hero(
tag: collectible.heroTag,
child: Container(
width: double.infinity,
height: 250,
child: FadeInImage(
image: collectible.detailImage,
placeholder: collectible.icon,
fadeInDuration: Duration(milliseconds: 100),
fadeOutDuration: Duration(milliseconds: 100),
fadeInCurve: Curves.linear,
fadeOutCurve: Curves.linear,
// errorBuilder: (context, obj, satckTrace) =>
// Image.asset('images/bildfehlt.png'),
),
alignment: Alignment.center,
),
),
),
CheckboxListTile(
title: Text("Got it"),
controlAffinity: ListTileControlAffinity.leading,
value: collectible.isObtained(settings),
onChanged: (value) {
collectible.setObtained(settings, value);
},
),
collectible.details(context), // <-------- function
],
),
);
Did I implement sth wrong?
Any other widget, Container or Column for Example just work fine.
Error-Log:
https://pastebin.com/geSBXcB6