Related
I have a page where I crop and trim the video.
When I initialize a VideoEditorController and for example edit the video, or just navigate back to previous page. And then I get method channel error and all CachedVideoPlayer widgets show a black screen. if we rebuild the widget with CachedVideoPlayer it'll play. And if we try to repeat the same process again for some strange reason CachedVideoPlayers won't crash.
I'm testing on a real device(Iphone 12 mini).
Error:
MissingPluginException(No implementation found for method cancel on channel flutter.io/videoPlayer/videoEvents171)
How I initialize VideoEditorController
#override
void initState() {
super.initState();
_controller = VideoEditorController.file(widget.file,
maxDuration: const Duration(seconds: 30))
..initialize().then((_) => setState(() {
_controller.preferredCropAspectRatio = widget.aspectRatio;
showTrimmer = true;
}));
}
CropScreen
class CropScreen extends StatefulWidget {
const CropScreen({
Key? key,
required this.file,
required this.exportedFile,
required this.aspectRatio,
}) : super(key: key);
final File file;
final double aspectRatio;
final Function(File) exportedFile;
#override
State<CropScreen> createState() => _CropScreenState();
}
class _CropScreenState extends State<CropScreen> {
late VideoEditorController _controller;
final double height = 60;
bool showTrimmer = false;
final ValueNotifier<bool> _isLoadingNotifier = ValueNotifier(false);
void show() {
_isLoadingNotifier.value = true;
}
void hide() {
_isLoadingNotifier.value = false;
}
#override
void initState() {
super.initState();
_controller = VideoEditorController.file(widget.file,
maxDuration: const Duration(seconds: 30))
..initialize().then((_) => setState(() {
_controller.preferredCropAspectRatio = widget.aspectRatio;
showTrimmer = true;
}));
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: _isLoadingNotifier,
builder: (context, value, child) {
return Stack(
children: [
Stack(
children: [
Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(30),
child: Column(children: [
Row(children: [
Expanded(
child: IconButton(
onPressed: () => _controller
.rotate90Degrees(RotateDirection.left),
icon: const Icon(
Icons.rotate_left,
color: white,
),
),
),
Expanded(
child: IconButton(
onPressed: () => _controller
.rotate90Degrees(RotateDirection.right),
icon: const Icon(
Icons.rotate_right,
color: white,
),
),
)
]),
const SizedBox(height: 15),
Expanded(
child: CropGridViewer(
controller: _controller, horizontalMargin: 60),
),
const SizedBox(
height: 15,
),
showTrimmer
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: _trimSlider(context))
: SizedBox(height: 100),
const SizedBox(height: 15),
Row(children: [
Expanded(
child: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Center(
child: Text(
"Cancel",
style: MainTheme.of(context)
.bodyText1
.copyWith(fontWeight: FontWeight.w600),
),
),
),
),
buildSplashTap("21:9", 21 / 9,
padding:
const EdgeInsets.symmetric(horizontal: 10)),
buildSplashTap("1:1", 1 / 1),
buildSplashTap("4:5", 4 / 5,
padding:
const EdgeInsets.symmetric(horizontal: 10)),
buildSplashTap("NO", null,
padding: const EdgeInsets.only(right: 10)),
Expanded(
child: IconButton(
onPressed: () async {
//2 WAYS TO UPDATE CROP
//WAY 1:
show();
_controller.updateCrop();
await _controller.exportVideo(
onCompleted: (file) {
print(file!.path);
widget.exportedFile(file);
hide();
Navigator.pop(context);
});
/*WAY 2:
controller.minCrop = controller.cacheMinCrop;
controller.maxCrop = controller.cacheMaxCrop;
*/
},
icon: Center(
child: Text(
"Crop",
style: MainTheme.of(context)
.bodyText1
.copyWith(fontWeight: FontWeight.w600),
),
),
),
),
]),
]),
),
),
),
],
),
if (value)
BackdropFilter(
filter: ImageFilter.blur(sigmaX: 4.0, sigmaY: 4.0),
child: const Opacity(
opacity: 0.8,
child:
ModalBarrier(dismissible: false, color: Colors.black),
),
),
if (value)
Center(
child: FutureBuilder(
future: Future.delayed(Duration(milliseconds: 500)),
builder: (context, snapshot) {
return const SpinKitFadingCircle(
size: 60, color: primary);
},
),
),
],
);
});
}
Widget buildSplashTap(
String title,
double? aspectRatio, {
EdgeInsetsGeometry? padding,
}) {
return InkWell(
onTap: () => _controller.preferredCropAspectRatio = aspectRatio,
child: Padding(
padding: padding ?? EdgeInsets.zero,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.aspect_ratio, color: Colors.white),
Text(
title,
style: const TextStyle(fontWeight: FontWeight.bold, color: white),
),
],
),
),
);
}
List<Widget> _trimSlider(BuildContext context) {
return [
AnimatedBuilder(
animation: _controller.video,
builder: (_, __) {
final duration = _controller.video.value.duration.inSeconds;
final pos = _controller.trimPosition * duration;
final start = _controller.minTrim * duration;
final end = _controller.maxTrim * duration;
return Padding(
padding: EdgeInsets.symmetric(horizontal: height / 4),
child: Row(children: [
Text(
formatter(Duration(seconds: pos.toInt())),
style: MainTheme.of(context).bodyText1,
),
const Expanded(child: SizedBox()),
OpacityTransition(
visible: _controller.isTrimming,
child: Row(mainAxisSize: MainAxisSize.min, children: [
Text(
formatter(Duration(seconds: start.toInt())),
style: MainTheme.of(context).bodyText1,
),
const SizedBox(width: 10),
Text(
formatter(Duration(seconds: end.toInt())),
style: MainTheme.of(context).bodyText1,
),
]),
)
]),
);
},
),
Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(vertical: height / 4),
child: TrimSlider(
controller: _controller,
height: height,
horizontalMargin: height / 4,
child: TrimTimeline(
controller: _controller,
margin: const EdgeInsets.only(top: 10))),
)
];
}
String formatter(Duration duration) => [
duration.inMinutes.remainder(60).toString().padLeft(2, '0'),
duration.inSeconds.remainder(60).toString().padLeft(2, '0')
].join(":");
}
To use VideoEditorController you need to add this package:
video_editor: ^1.4.1
Dart SDK:
sdk: ">=2.16.1 <3.0.0"
Trying to implement showBottomSheet into my app, but it is throwing an error: Scaffold.of() called with a context that does not contain a Scaffold.
After searching the web a bit, I added a GlobalKey but that seems like it is not doing the trick. Does anyone have a suggestion?
class _DashboardState extends State<Dashboard> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
int _bottomNavBarCurrentIndex = 0;
dashboardViews.DashboardView dView = new dashboardViews.DashboardView();
List<Widget> _listOffers = new List<Widget>();
Widget _currentView;
void loadDashboardView() {
_currentView = (_bottomNavBarCurrentIndex == 0
? dView.getOfferView(_listOffers, _getOfferData)
: dView.getOrdersView());
}
_showInfoSheet() {
showBottomSheet(
context: _scaffoldKey.currentContext,
builder: (context) {
return Text('Hello');
});
}
Future _getOfferData() async {
loadDashboardView();
List<Widget> _resultsOffers = new List<Widget>();
SharedPreferences prefs = await SharedPreferences.getInstance();
String _token = prefs.getString('token');
final responseOffers =
await http.get(globals.apiConnString + 'GetActiveOffers?token=$_token');
if (responseOffers.statusCode == 200) {
List data = json.decode(responseOffers.body);
for (var i = 0; i < data.length; i++) {
_resultsOffers.add(GestureDetector(
onTap: () => _showInfoSheet(),
child: Card(
child: Padding(
padding: EdgeInsets.all(15),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(children: <Widget>[
Expanded(
flex: 5,
child: Text('${data[i]['Title']}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: globals.themeColor4))),
Expanded(
flex: 3,
child: Row(children: <Widget>[
Icon(Icons.access_time,
size: 15, color: Colors.grey),
Padding(
padding: EdgeInsets.fromLTRB(5, 0, 10, 0),
child: Text('11:30 PM',
style:
TextStyle(color: Colors.black))),
])),
Expanded(
flex: 1,
child: Row(children: <Widget>[
Icon(Icons.local_dining,
size: 15, color: Colors.grey),
Padding(
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
child: Text('${i.toString()}',
style:
TextStyle(color: Colors.black))),
])),
]),
Padding(padding: EdgeInsets.all(10)),
Row(children: <Widget>[
Text(
'Created May 2, 2019 at 2:31 PM',
style: TextStyle(color: Colors.grey[600]),
textAlign: TextAlign.start,
)
])
])))));
}
}
setState(() {
_listOffers = _resultsOffers;
});
loadDashboardView();
}
}
void _bottomNavBarTap(int index) {
setState(() {
_bottomNavBarCurrentIndex = index;
loadDashboardView();
});
}
void pullRefresh() {
_getOfferData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
backgroundColor: Colors.grey[200],
body: _currentView,
bottomNavigationBar: BottomNavigationBar(
onTap: _bottomNavBarTap,
currentIndex: _bottomNavBarCurrentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.near_me), title: Text('OFFERS')),
BottomNavigationBarItem(
icon: Icon(Icons.broken_image), title: Text('ORDERS'))
],
),
);
}
}
Hope someone can point me in the right direction, thanks!
EDIT: Here is one of the links I looked at with the same problem, and I tried setting the builder context to c as suggested but didn't work: https://github.com/flutter/flutter/issues/23234
EDIT 2: Adding screenshot that shows the variable contents when I'm getting the Scaffold.of() called with a context that does not contain a Scaffold error.
The reason you're getting this error is because you're calling Scaffold during its build process, and thus your showBottomSheet function can't see it. A workaround this problem is to provide a stateful widget with a Scaffold, assign the Scaffold a key , and then pass it to your Dashboard (I assumed it is the name of your stateful widget from your state class). You don't need to assign a key to the Scaffold inside the build of _DashboardState.
This is the class where you provide a Scaffold with key:
class DashboardPage extends StatefulWidget {
#override
_DashboardPageState createState() => _DashboardPageState() ;
}
class _DashboardPageState extends State<DashboardPage> {
final GlobalKey<ScaffoldState> scaffoldStateKey ;
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldStateKey,
body: Dashboard(scaffoldKey: scaffoldStateKey),
);
}
Your original Dashboard altered :
class Dashboard extends StatefulWidget{
Dashboard({Key key, this.scaffoldKey}):
super(key: key);
final GlobalKey<ScaffoldState> scaffoldKey ;
#override
_DashboardState createState() => new _DashboardState();
}
Your _DashboardState with changes outlined :
class _DashboardState extends State<Dashboard> {
int _bottomNavBarCurrentIndex = 0;
dashboardViews.DashboardView dView = new dashboardViews.DashboardView();
List<Widget> _listOffers = new List<Widget>();
Widget _currentView;
void loadDashboardView() {
_currentView = (_bottomNavBarCurrentIndex == 0
? dView.getOfferView(_listOffers, _getOfferData)
: dView.getOrdersView());
}
_showInfoSheet() {
showBottomSheet(
context: widget.scaffoldKey.currentContext, // referencing the key passed from [DashboardPage] to use its Scaffold.
builder: (context) {
return Text('Hello');
});
}
Future _getOfferData() async {
loadDashboardView();
List<Widget> _resultsOffers = new List<Widget>();
SharedPreferences prefs = await SharedPreferences.getInstance();
String _token = prefs.getString('token');
final responseOffers =
await http.get(globals.apiConnString + 'GetActiveOffers?token=$_token');
if (responseOffers.statusCode == 200) {
List data = json.decode(responseOffers.body);
for (var i = 0; i < data.length; i++) {
_resultsOffers.add(GestureDetector(
onTap: () => _showInfoSheet(),
child: Card(
child: Padding(
padding: EdgeInsets.all(15),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(children: <Widget>[
Expanded(
flex: 5,
child: Text('${data[i]['Title']}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: globals.themeColor4))),
Expanded(
flex: 3,
child: Row(children: <Widget>[
Icon(Icons.access_time,
size: 15, color: Colors.grey),
Padding(
padding: EdgeInsets.fromLTRB(5, 0, 10, 0),
child: Text('11:30 PM',
style:
TextStyle(color: Colors.black))),
])),
Expanded(
flex: 1,
child: Row(children: <Widget>[
Icon(Icons.local_dining,
size: 15, color: Colors.grey),
Padding(
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
child: Text('${i.toString()}',
style:
TextStyle(color: Colors.black))),
])),
]),
Padding(padding: EdgeInsets.all(10)),
Row(children: <Widget>[
Text(
'Created May 2, 2019 at 2:31 PM',
style: TextStyle(color: Colors.grey[600]),
textAlign: TextAlign.start,
)
])
])))));
}
}
setState(() {
_listOffers = _resultsOffers;
});
loadDashboardView();
}
void _bottomNavBarTap(int index) {
setState(() {
_bottomNavBarCurrentIndex = index;
loadDashboardView();
});
}
void pullRefresh() {
_getOfferData();
}
#override
Widget build(BuildContext context) {
// Scaffold key has been removed as there is no further need to it.
return Scaffold(
backgroundColor: Colors.grey[200],
body: _currentView,
bottomNavigationBar: BottomNavigationBar(
onTap: _bottomNavBarTap,
currentIndex: _bottomNavBarCurrentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.near_me), title: Text('OFFERS')),
BottomNavigationBarItem(
icon: Icon(Icons.broken_image), title: Text('ORDERS'))
],
),
);
}
}
}
I want to change color of every icon after pressing. But all of icons in a ExpandableContainerchange after pressing one of them.
class _ExpandableListViewState extends State<ExpandableListView> {
bool expandFlag = false;
Color _iconColor = Colors.white;
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 1.0),
child: new Column(
children: <Widget>[
new Container(
.
.
.
),
new ExpandableContainer(
expanded: expandFlag,
expandedHeight: ...
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Container(
decoration:
new BoxDecoration(border: new Border.all(width: 1.0, color: Colors.grey), color: Colors.black),
child: new ListTile(
title: ...
leading: new IconButton(
icon: Icon(Icons.star, color: _iconColor),
onPressed: () {
setState(() {
_iconColor = _iconColor == Colors.white ? Colors.yellow : Colors.white;
});
},
),
subtitle: ...
),
);
},
itemCount: ...,
))
],
),
);
}
}
class ExpandableContainer extends StatelessWidget {
final bool expanded;
final double expandedHeight;
final Widget child;
ExpandableContainer({
#required this.child,
this.expandedHeight,
this.expanded = true,
});
#override
Widget build(BuildContext context) {
.
.
.
}
Whole code:
import 'package:flutter/material.dart';
import 'data.dart';
void main() {
runApp(new MaterialApp(home: new Home()));
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.grey,
appBar: new AppBar(
title: new Text("Expandable List", style: TextStyle(color: Colors.black)),
backgroundColor: Colors.lightGreen,
),
body: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new ExpandableListView(title: broadcast[index].title, ind: index);
},
itemCount: broadcast.length,
),
);
}
}
class ExpandableListView extends StatefulWidget {
final String title;
final int ind;
const ExpandableListView({this.title, this.ind});
#override
_ExpandableListViewState createState() => new _ExpandableListViewState();
}
class _ExpandableListViewState extends State<ExpandableListView> {
bool expandFlag = false;
Color _iconColor = Colors.white;
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 1.0),
child: new Column(
children: <Widget>[
new Container(
color: Colors.blue[300],
padding: new EdgeInsets.symmetric(horizontal: 5.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new IconButton(
icon: new Container(
height: 50.0,
width: 50.0,
decoration: new BoxDecoration(
color: Colors.deepOrange,
shape: BoxShape.circle,
),
child: new Center(
child: new Icon(
expandFlag ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
color: Colors.white,
size: 30.0,
),
),
),
onPressed: () {
setState(() {
expandFlag = !expandFlag;
});
}),
new Text(
widget.title,
style: new TextStyle(fontWeight: FontWeight.bold,fontSize: 20.0, color: Colors.black87),
)
],
),
),
new ExpandableContainer(
expanded: expandFlag,
expandedHeight: 90.0 * (broadcast[widget.ind].contents.length < 4 ? broadcast[widget.ind].contents.length : 4), // + (0.0 ?: 29.0),
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Container(
decoration:
new BoxDecoration(border: new Border.all(width: 1.0, color: Colors.grey), color: Colors.black),
child: new ListTile(
title: new Text(
broadcast[widget.ind].contents[index],
style: new TextStyle(fontWeight: FontWeight.bold, color: Colors.lightGreen),
textAlign: TextAlign.right,
),
leading: new IconButton(
icon: Icon(Icons.star, color: _iconColor),
onPressed: () {
setState(() {
_iconColor = _iconColor == Colors.white ? Colors.yellow : Colors.white;
});
},
),
subtitle: new Text ('${broadcast[widget.ind].team[index]}\n${broadcast[widget.ind].time[index]} ${broadcast[widget.ind].channel[index]}',
textAlign: TextAlign.right, style:TextStyle(color: Colors.white)),
isThreeLine: true,
),
);
},
itemCount: broadcast[widget.ind].contents.length,
))
],
),
);
}
}
class ExpandableContainer extends StatelessWidget {
final bool expanded;
final double expandedHeight;
final Widget child;
//final Color iconColor;
ExpandableContainer({
#required this.child,
this.expandedHeight,
this.expanded = true,
//this.iconColor,
});
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return new AnimatedContainer(
duration: new Duration(milliseconds: 100),
curve: Curves.easeInOut,
width: screenWidth,
height: expanded ? expandedHeight : 0.0,
child: new Container(
child: child,
decoration: new BoxDecoration(border: new Border.all(width: 1.0, color: Colors.blue)),
),
);
}
}
You need to make the list item a StatefulWidget in which you have the state _iconColor
Stateful List Tile
class StatefulListTile extends StatefulWidget {
const StatefulListTile({this.subtitle, this.title});
final String subtitle, title;
#override
_StatefulListTileState createState() => _StatefulListTileState();
}
class _StatefulListTileState extends State<StatefulListTile> {
Color _iconColor = Colors.white;
#override
Widget build(BuildContext context) {
return new Container(
decoration: new BoxDecoration(
border: new Border.all(width: 1.0, color: Colors.grey),
color: Colors.black),
child: new ListTile(
title: new Text(
widget?.title ?? "",
style: new TextStyle(
fontWeight: FontWeight.bold, color: Colors.lightGreen),
textAlign: TextAlign.right,
),
leading: new IconButton(
icon: Icon(Icons.star, color: _iconColor),
onPressed: () {
setState(() {
_iconColor =
_iconColor == Colors.white ? Colors.yellow : Colors.white;
});
},
),
subtitle: new Text(widget?.subtitle ?? "",
textAlign: TextAlign.right, style: TextStyle(color: Colors.white)),
isThreeLine: true,
),
);
}
}
Usage
class ExpandableListView extends StatefulWidget {
final String title;
final int ind;
const ExpandableListView({this.title, this.ind});
#override
_ExpandableListViewState createState() => new _ExpandableListViewState();
}
class _ExpandableListViewState extends State<ExpandableListView> {
bool expandFlag = false;
Color _iconColor = Colors.white;
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 1.0),
child: new Column(
children: <Widget>[
new Container(
color: Colors.blue[300],
padding: new EdgeInsets.symmetric(horizontal: 5.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new IconButton(
icon: new Container(
height: 50.0,
width: 50.0,
decoration: new BoxDecoration(
color: Colors.deepOrange,
shape: BoxShape.circle,
),
child: new Center(
child: new Icon(
expandFlag
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
color: Colors.white,
size: 30.0,
),
),
),
onPressed: () {
setState(() {
expandFlag = !expandFlag;
});
}),
new Text(
widget.title,
style: new TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20.0,
color: Colors.black87),
)
],
),
),
new ExpandableContainer(
expanded: expandFlag,
expandedHeight: 90.0 *
(broadcast[widget.ind].contents.length < 4
? broadcast[widget.ind].contents.length
: 4), // + (0.0 ?: 29.0),
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return StatefulListTile(
title: broadcast[widget.ind].contents[index],
subtitle:
'${broadcast[widget.ind].team[index]}\n${broadcast[widget.ind].time[index]} ${broadcast[widget.ind].channel[index]}',
);
},
itemCount: broadcast[widget.ind].contents.length,
))
],
),
);
}
}
You should make the color property distinct for each element in the ListView, what you are doing is that the color is global and shared among all the icons in the ListView, for this reason all icons are changing their color when one icon is pressed.
class Broadcast {
final String title;
List<String> contents;
List<String> team = [];
List<String> time = [];
List<String> channel = [];
Color iconColor = Colors.white; //initialize at the beginning
Broadcast(this.title, this.contents, this.team, this.time, this.channel); //, this.icon);
}
edit your ExpandableListView
class ExpandableListView extends StatefulWidget {
final int ind;
final Broadcast broadcast;
const ExpandableListView({this.broadcast,this.ind});
#override
_ExpandableListViewState createState() => new _ExpandableListViewState();
}
edit your Home class
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.grey,
appBar: new AppBar(
title: new Text("Expandable List", style: TextStyle(color: Colors.black)),
backgroundColor: Colors.lightGreen,
),
body: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new ExpandableListView(title: broadcast[index], ind: index);
},
itemCount: broadcast.length,
),
);
}
}
edit your _ExpandableListViewState
class _ExpandableListViewState extends State<ExpandableListView> {
bool expandFlag = false;
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 1.0),
child: new Column(
children: <Widget>[
new Container(
color: Colors.blue[300],
padding: new EdgeInsets.symmetric(horizontal: 5.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new IconButton(
icon: new Container(
height: 50.0,
width: 50.0,
decoration: new BoxDecoration(
color: Colors.deepOrange,
shape: BoxShape.circle,
),
child: new Center(
child: new Icon(
expandFlag ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
color: Colors.white,
size: 30.0,
),
),
),
onPressed: () {
setState(() {
expandFlag = !expandFlag;
});
}),
new Text(
widget.broadcast.title,
style: new TextStyle(fontWeight: FontWeight.bold,fontSize: 20.0, color: Colors.black87),
)
],
),
),
new ExpandableContainer(
expanded: expandFlag,
expandedHeight: 90.0 * (broadcast[widget.ind].contents.length < 4 ? broadcast[widget.ind].contents.length : 4), // + (0.0 ?: 29.0),
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Container(
decoration:
new BoxDecoration(border: new Border.all(width: 1.0, color: Colors.grey), color: Colors.black),
child: new ListTile(
title: new Text(
broadcast[widget.ind].contents[index],
style: new TextStyle(fontWeight: FontWeight.bold, color: Colors.lightGreen),
textAlign: TextAlign.right,
),
leading: new IconButton(
icon: Icon(Icons.star, color: widget.broadcast.iconColor),
onPressed: () {
setState(() {
widget.broadcast.iconColor = widget.broadcast.iconColor == Colors.white ? Colors.yellow : Colors.white;
});
},
),
subtitle: new Text ('${broadcast[widget.ind].team[index]}\n${broadcast[widget.ind].time[index]} ${broadcast[widget.ind].channel[index]}',
textAlign: TextAlign.right, style:TextStyle(color: Colors.white)),
isThreeLine: true,
),
);
},
itemCount: broadcast[widget.ind].contents.length,
))
],
),
);
}
}
I've made a GridView, but now I have some ListTiles in this GridView that I need to be selected Upon selection, I want the background color to change.
And I am also facing one more issue, I want to custom the heights of these ListTile, but I am not getting any success to do so. I am gonna attach the picture the output that I am getting and the output that I want.
What I am getting:
What I want:
Here is the code:
class _SelectLanguageNewState extends State<SelectLanguageNew> {
List<Results> listResponseData;
bool _color;
#override
void initState() {
// TODO: implement initState
super.initState();
getLang();
_color = false;
}
Future getLang() async {
await getLanguage().then((GetLanguageResponse getlanguage)
{
if(getlanguage.isSuccess)
{
setState(() {
listResponseData = getlanguage.responseData.listResults;
});
}
});
}
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
final double itemHeight = (size.height - kToolbarHeight - 24) / 2;
final double itemWidth = size.width / 2;
void _onTileClicked(int index){
debugPrint("You tapped on item $index");
}
// Get grid tiles
List<Widget> _getTiles() {
final List<Widget> tiles = <Widget>[];
for (int i = 0; i < listResponseData.length; i++) {
tiles.add(ListTileTheme(
selectedColor: Colors.blue,
child: new InkWell(
child: new Card(
elevation: 5.0,
child: new Container(
color: _color ? Colors.black : Colors.grey,
alignment: Alignment.center,
child: new Text(listResponseData[i].nativeText,
style: TextStyle(color: Colors.white),),
),
),
onTap: () {
setState(() {
_color = !_color;
});
/* showDialog(
barrierDismissible: false,
context: context,
child: new CupertinoAlertDialog(
content: new Container(
width: 40.0,
child: new CircularProgressIndicator()),
actions: <Widget>[
new FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: new Text("OK"))
],
),
);*/
},
),
));
}
return tiles;
}
return Scaffold(
bottomNavigationBar: BottomAppBar(
child: Container(
height: 50.0,
child: InkWell(
onTap: ()=> print('pressed'),
child: Center(
child: Text('Next', style: TextStyle(color: Colors.white,
),
),
),
),
),
color: Colors.blue,
),
body: new Container(
child: listResponseData == null || listResponseData.isEmpty ? new Container(
child: new Text('Loading data......'),
) : new GridView.count(
shrinkWrap: true,
scrollDirection: Axis.vertical,
childAspectRatio:1.0,
crossAxisCount: MediaQuery.of(context).size.width <= 400.0 ? 3 : MediaQuery.of(context).size.width >= 1000.0 ? 5 : 2,
// Create a grid with 2 columns. If you change the scrollDirection to
// horizontal, this would produce 2 rows.
crossAxisSpacing: 5.0,
// Generate 100 Widgets that display their index in the List
children: _getTiles() ,
),
),
);
}
}
Screenshot
Complete solution:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(scaffoldBackgroundColor: Colors.black, brightness: Brightness.dark),
home: Scaffold(
appBar: AppBar(title: Text("Choose Language")),
body: HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _languageIndex = -1;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 2.4,
children: <Widget>[
GestureDetector(
child: _buildWidget("English", 0),
onTap: () => setState(() => _languageIndex = 0),
),
GestureDetector(
child: _buildWidget("Española", 1),
onTap: () => setState(() => _languageIndex = 1),
),
GestureDetector(
child: _buildWidget("Française", 2),
onTap: () => setState(() => _languageIndex = 2),
),
GestureDetector(
child: _buildWidget("Gaeilge", 3),
onTap: () => setState(() => _languageIndex = 3),
),
GestureDetector(
child: _buildWidget("العربية", 4),
onTap: () => setState(() => _languageIndex = 4),
),
GestureDetector(
child: _buildWidget("جغتای", 5),
onTap: () => setState(() => _languageIndex = 5),
),
GestureDetector(
child: _buildWidget("ગુજરાતી", 6),
onTap: () => setState(() => _languageIndex = 6),
),
GestureDetector(
child: _buildWidget("हिन्दी", 7),
onTap: () => setState(() => _languageIndex = 7),
),
GestureDetector(
child: _buildWidget("日本語", 8),
onTap: () => setState(() => _languageIndex = 8),
),
GestureDetector(
child: _buildWidget("коми", 9),
onTap: () => setState(() => _languageIndex = 9),
),
GestureDetector(
child: _buildWidget("ਲਹਿੰਦੀ", 10),
onTap: () => setState(() => _languageIndex = 10),
),
GestureDetector(
child: _buildWidget("Schwyzerdütsch", 11),
onTap: () => setState(() => _languageIndex = 11),
),
],
),
SizedBox(
width: double.maxFinite,
height: 44,
child: RaisedButton(
child: Text("SUBMIT"),
onPressed: () {
print("languageIndex = ${_languageIndex}");
},
),
),
],
),
);
}
Widget _buildWidget(String language, int index) {
bool isSelected = _languageIndex == index;
return Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(color: isSelected ? Colors.blue[300].withOpacity(0.8) : Colors.grey[700]),
color: Colors.grey[900],
),
child: Text(
language,
style: TextStyle(fontSize: 16, color: isSelected ? Colors.blue[400] : null),
),
);
}
}
You can watch which language user selected by using the _languageIndex.
Here is a material design of Expanded panel that looks like:
I'd like to make a similar one with Flutter, not sure if I've to start with something like the below code or know, and how to complete it!
new ExpansionPanelList(
children: <ExpansionPanel>[
new ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
isExpanded = true;
return new ListTile(
// leading: item.iconpic,
title: new Text(
"First",
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w400,
),
));
},
body: new Text("school"),
isExpanded: true,
),
new ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
isExpanded = true;
return new ListTile(
// leading: item.iconpic,
title: new Text(
"Second",
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w400,
),
));
},
isExpanded: false,
body: new Text("hospital"),
),
new ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
isExpanded = true;
return new ListTile(
// leading: item.iconpic,
title: new Text(
"Third",
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w400,
),
));
},
body: new Text("va facility"),
isExpanded: true)
]),
UPDATE
I just need to start and have the empty panels
In case if you particularly need to mimic the images you referenced from the material design. You would want to build your own custom expansion panel.
I have a simple example using AnimatedContainer to show you how to create the expanded and collapsed effects, and it is up to you to populate both the header and the body sections with what you want.
class AnimateExpanded extends StatefulWidget {
#override
_AnimateExpandedState createState() => new _AnimateExpandedState();
}
class _AnimateExpandedState extends State<AnimateExpanded> {
double _bodyHeight = 0.0;
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.grey[500],
body: new SingleChildScrollView(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Card(
child: new Container(
height: 50.0,
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new IconButton(
icon: new Icon(Icons.keyboard_arrow_down),
onPressed: () {
setState(() {
this._bodyHeight = 300.0;
});
},
)
],
),
),
),
new Card(
child: new AnimatedContainer(
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new IconButton(
icon: new Icon(Icons.keyboard_arrow_up),
onPressed: () {
setState(() {
this._bodyHeight = 0.0;
});
},
),
],
),
curve: Curves.easeInOut,
duration: const Duration(milliseconds: 500),
height: _bodyHeight,
// color: Colors.red,
),
),
],
),
),
);
}
}
Here's a working example (including main etc so you can just paste into a file and run)
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class ListItem {
final WidgetBuilder bodyBuilder;
final String title;
final String subtitle;
bool isExpandedInitially;
ListItem({
#required this.bodyBuilder,
#required this.title,
this.subtitle = "",
this.isExpandedInitially = false,
}) : assert(title != null),
assert(bodyBuilder != null);
ExpansionPanelHeaderBuilder get headerBuilder =>
(context, isExpanded) => new Row(children: [
new SizedBox(width: 100.0, child: new Text(title)),
new Text(subtitle)
]);
}
class ExpansionList extends StatefulWidget {
/// The items that the expansion list should display; this can change
/// over the course of the object but probably shouldn't as it won't
/// transition nicely or anything like that.
final List<ListItem> items;
ExpansionList(this.items) {
// quick check to make sure there's no duplicate titles.
assert(new Set.from(items.map((li) => li.title)).length == items.length);
}
#override
State<StatefulWidget> createState() => new ExpansionListState();
}
class ExpansionListState extends State<ExpansionList> {
Map<String, bool> expandedByTitle = new Map();
#override
Widget build(BuildContext context) {
return new ExpansionPanelList(
children: widget.items
.map(
(item) => new ExpansionPanel(
headerBuilder: item.headerBuilder,
body: new Builder(builder: item.bodyBuilder),
isExpanded:
expandedByTitle[item.title] ?? item.isExpandedInitially),
)
.toList(growable: false),
expansionCallback: (int index, bool isExpanded) {
setState(() {
expandedByTitle[widget.items[index].title] = !isExpanded;
});
},
);
}
}
void main() => runApp(
new MaterialApp(
home: new SingleChildScrollView(
child: new SafeArea(
child: new Material(
child: new ExpansionList(
[
new ListItem(
title: "Title 1",
subtitle: "Subtitle 1",
bodyBuilder: (context) => new Text("Body 1")),
new ListItem(
title: "Title 2",
subtitle: "Subtitle 2",
bodyBuilder: (context) => new Text("Body 1"),
isExpandedInitially: true)
],
),
),
),
),
),
);
If I had to guess, you're missing the parts where you pass in expanded into each expansion header, and the part where you keep track of whether each expansion header is expanded or not.
I've done it a particular way here that assumes each title is unique; you could do something similar but rely on different properties. Or you could build everything in the initState method of your ExpansionListState equivalent.
This is a full working example of pretty much the exact UI you have in the picture in your post. You can simply download the flutter gallery from the play store to see the result. They did it a different way than I did (building everything in the initState method), and it's more complicated than what I did, but would be worth understanding as well.
Hope that helps =)
You can use ExpansionTile inside ListView like this
ListView(
shrinkWrap: true,
children: <Widget>[
ExpansionTile(
backgroundColor: Colors.amber,
leading: Icon(Icons.event),
title: Text('Test1'),
children: <Widget>[
ListTile(title: Text('Title of the item')),
ListTile(
title: Text('Title of the item2'),
)
],
),
ExpansionTile(
title: Text('Test2'),
children: <Widget>[
ListTile(title: Text('Title of the item')),
ListTile(
title: Text('Title of the item2'),
)
],
)
],
)