Flutter: Is there any way to get "fix size"/"reduce size" of DataColumn in "DataTable"? - datatable

Is there is any way to reduce the size of DataColumn?
I am Using DataTable Class in Flutter
columns: <DataColumn> [
DataColumn(
label: Text("Column A",
style: Theme.of(context).textTheme.subtitle),
numeric: false,
onSort: (i, b) {},
tooltip: "Perticulars",
),
DataColumn(
label: Text("Column B",
style: Theme.of(context).textTheme.subtitle),
numeric: false,
onSort: (i, b) {},
tooltip: "As per Assessee",
),
DataColumn(
label:
Text("Column C", style: Theme.of(context).textTheme.subtitle),
numeric: false,
onSort: (i, b) {},
tooltip: "As per ITD",
)
],

I removed some code about sort. You can change _tablePadding and _columnSpacing to reduce the size:
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
class CustomDataTable extends DataTable {
CustomDataTable({
Key key,
#required this.columns,
this.sortColumnIndex,
this.sortAscending = true,
this.onSelectAll,
#required this.rows,
}) :
assert(columns != null),
assert(columns.isNotEmpty),
assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
assert(sortAscending != null),
assert(rows != null),
assert(!rows.any((DataRow row) => row.cells.length != columns.length)),
_onlyTextColumn = _initOnlyTextColumn(columns),
super(
key: key,
columns: columns,
sortColumnIndex: sortColumnIndex,
sortAscending: sortAscending,
onSelectAll: onSelectAll,
rows: rows);
final List<DataColumn> columns;
final int sortColumnIndex;
final bool sortAscending;
final ValueSetter<bool> onSelectAll;
final List<DataRow> rows;
static const double _headingRowHeight = 56.0;
static const double _dataRowHeight = 48.0;
static const double _tablePadding = 18.0;
static const double _columnSpacing = 56.0;
static const double _headingFontSize = 12.0;
static const Duration _sortArrowAnimationDuration = Duration(milliseconds: 150);
static const Color _grey100Opacity = Color(0x0A000000); // Grey 100 as opacity instead of solid color
static const Color _grey300Opacity = Color(0x1E000000); // Dark theme variant is just a guess.
final int _onlyTextColumn;
static int _initOnlyTextColumn(List<DataColumn> columns) {
int result;
for (int index = 0; index < columns.length; index += 1) {
final DataColumn column = columns[index];
if (!column.numeric) {
if (result != null)
return null;
result = index;
}
}
return result;
}
bool get _debugInteractive {
return columns.any((DataColumn column) => false)
|| rows.any((DataRow row) => false);
}
static final LocalKey _headingRowKey = UniqueKey();
void _handleSelectAll(bool checked) {
if (onSelectAll != null) {
onSelectAll(checked);
} else {
for (DataRow row in rows) {
if ((row.onSelectChanged != null) && (row.selected != checked))
row.onSelectChanged(checked);
}
}
}
Widget _buildCheckbox({
Color color,
bool checked,
VoidCallback onRowTap,
ValueChanged<bool> onCheckboxChanged,
}) {
Widget contents = Semantics(
container: true,
child: Padding(
padding: const EdgeInsetsDirectional.only(start: _tablePadding, end: _tablePadding / 2.0),
child: Center(
child: Checkbox(
activeColor: color,
value: checked,
onChanged: onCheckboxChanged,
),
),
),
);
if (onRowTap != null) {
contents = TableRowInkWell(
onTap: onRowTap,
child: contents,
);
}
return TableCell(
verticalAlignment: TableCellVerticalAlignment.fill,
child: contents,
);
}
Widget _buildHeadingCell({
BuildContext context,
EdgeInsetsGeometry padding,
Widget label,
String tooltip,
bool numeric,
VoidCallback onSort,
bool sorted,
bool ascending,
}) {
label = Container(
padding: padding,
height: _headingRowHeight,
alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
child: AnimatedDefaultTextStyle(
style: TextStyle(
// TODO(ianh): font family should match Theme; see https://github.com/flutter/flutter/issues/3116
fontWeight: FontWeight.w500,
fontSize: _headingFontSize,
height: math.min(1.0, _headingRowHeight / _headingFontSize),
color: (Theme.of(context).brightness == Brightness.light)
? ((onSort != null && sorted) ? Colors.black87 : Colors.black54)
: ((onSort != null && sorted) ? Colors.white : Colors.white70),
),
softWrap: false,
duration: _sortArrowAnimationDuration,
child: label,
),
);
if (tooltip != null) {
label = Tooltip(
message: tooltip,
child: label,
);
}
if (onSort != null) {
label = InkWell(
onTap: onSort,
child: label,
);
}
return label;
}
Widget _buildDataCell({
BuildContext context,
EdgeInsetsGeometry padding,
Widget label,
bool numeric,
bool placeholder,
bool showEditIcon,
VoidCallback onTap,
VoidCallback onSelectChanged,
}) {
final bool isLightTheme = Theme.of(context).brightness == Brightness.light;
if (showEditIcon) {
const Widget icon = Icon(Icons.edit, size: 18.0);
label = Expanded(child: label);
label = Row(
textDirection: numeric ? TextDirection.rtl : null,
children: <Widget>[ label, icon ],
);
}
label = Container(
padding: padding,
height: _dataRowHeight,
alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
child: DefaultTextStyle(
style: TextStyle(
// TODO(ianh): font family should be Roboto; see https://github.com/flutter/flutter/issues/3116
fontSize: 13.0,
color: isLightTheme
? (placeholder ? Colors.black38 : Colors.black87)
: (placeholder ? Colors.white30 : Colors.white70),
),
child: IconTheme.merge(
data: IconThemeData(
color: isLightTheme ? Colors.black54 : Colors.white70,
),
child: DropdownButtonHideUnderline(child: label),
),
),
);
if (onTap != null) {
label = InkWell(
onTap: onTap,
child: label,
);
} else if (onSelectChanged != null) {
label = TableRowInkWell(
onTap: onSelectChanged,
child: label,
);
}
return label;
}
#override
Widget build(BuildContext context) {
assert(!_debugInteractive || debugCheckHasMaterial(context));
final ThemeData theme = Theme.of(context);
final BoxDecoration _kSelectedDecoration = BoxDecoration(
border: Border(bottom: Divider.createBorderSide(context, width: 1.0)),
// The backgroundColor has to be transparent so you can see the ink on the material
color: (Theme.of(context).brightness == Brightness.light) ? _grey100Opacity : _grey300Opacity,
);
final BoxDecoration _kUnselectedDecoration = BoxDecoration(
border: Border(bottom: Divider.createBorderSide(context, width: 1.0)),
);
final bool showCheckboxColumn = rows.any((DataRow row) => row.onSelectChanged != null);
final bool allChecked = showCheckboxColumn && !rows.any((DataRow row) => row.onSelectChanged != null && !row.selected);
final List<TableColumnWidth> tableColumns = List<TableColumnWidth>(columns.length + (showCheckboxColumn ? 1 : 0));
final List<TableRow> tableRows = List<TableRow>.generate(
rows.length + 1, // the +1 is for the header row
(int index) {
return TableRow(
key: index == 0 ? _headingRowKey : rows[index - 1].key,
decoration: index > 0 && rows[index - 1].selected ? _kSelectedDecoration
: _kUnselectedDecoration,
children: List<Widget>(tableColumns.length),
);
},
);
int rowIndex;
int displayColumnIndex = 0;
if (showCheckboxColumn) {
tableColumns[0] = const FixedColumnWidth(_tablePadding + Checkbox.width + _tablePadding / 2.0);
tableRows[0].children[0] = _buildCheckbox(
color: theme.accentColor,
checked: allChecked,
onCheckboxChanged: _handleSelectAll,
);
rowIndex = 1;
for (DataRow row in rows) {
tableRows[rowIndex].children[0] = _buildCheckbox(
color: theme.accentColor,
checked: row.selected,
onRowTap: () => row.onSelectChanged != null ? row.onSelectChanged(!row.selected) : null ,
onCheckboxChanged: row.onSelectChanged,
);
rowIndex += 1;
}
displayColumnIndex += 1;
}
for (int dataColumnIndex = 0; dataColumnIndex < columns.length; dataColumnIndex += 1) {
final DataColumn column = columns[dataColumnIndex];
final EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(
start: dataColumnIndex == 0 ? showCheckboxColumn ? _tablePadding / 2.0 : _tablePadding : _columnSpacing / 2.0,
end: dataColumnIndex == columns.length - 1 ? _tablePadding : _columnSpacing / 2.0,
);
if (dataColumnIndex == _onlyTextColumn) {
tableColumns[displayColumnIndex] = const IntrinsicColumnWidth(flex: 1.0);
} else {
tableColumns[displayColumnIndex] = const IntrinsicColumnWidth();
}
tableRows[0].children[displayColumnIndex] = _buildHeadingCell(
context: context,
padding: padding,
label: column.label,
tooltip: column.tooltip,
numeric: column.numeric,
onSort: () => column.onSort != null ? column.onSort(dataColumnIndex, sortColumnIndex == dataColumnIndex ? !sortAscending : true) : null,
sorted: dataColumnIndex == sortColumnIndex,
ascending: sortAscending,
);
rowIndex = 1;
for (DataRow row in rows) {
final DataCell cell = row.cells[dataColumnIndex];
tableRows[rowIndex].children[displayColumnIndex] = _buildDataCell(
context: context,
padding: padding,
label: cell.child,
numeric: column.numeric,
placeholder: cell.placeholder,
showEditIcon: cell.showEditIcon,
onTap: cell.onTap,
onSelectChanged: () => row.onSelectChanged != null ? row.onSelectChanged(!row.selected) : null,
);
rowIndex += 1;
}
displayColumnIndex += 1;
}
return Table(
columnWidths: tableColumns.asMap(),
children: tableRows,
);
}
}

Related

Flutter: How do I select and display images

I cannot display selected images from gallery in a grid. In this code, I am displaying images in a list and I want to turn it into small grid type in 1 row but I don't know how. Can you please help?
Here's my code for selecting multiple images using file picker.
FileType fileType;
String imgName, _imgPath;
Map<String, String> imgPaths;
List<File> _imgList = List();
bool isLoadingPath = false;
_openFile() async {
setState(() => isLoadingPath = true);
try {
_imgPath = null;
imgPaths = await FilePicker.getMultiFilePath(
type: fileType != null ? fileType : FileType.custom,
allowedExtensions: ['jpg', 'png']);
_imgList.clear();
imgPaths.forEach((key, val) {
print('{ key: $key, value: $val}');
File file = File(val);
_imgList.add(file);
});
} on PlatformException catch (e) {
print("Unsupported operation" + e.toString());
}
if (!mounted) return;
setState(() {
isLoadingPath = false;
imgName = _imgPath != null
? _imgPath.split('/').last
: imgPaths != null
? imgPaths.keys.toString()
: '...';
});
}
Displaying images in a list. (How to display images as it is?)
Widget _fileBuilder() {
return Builder(
builder: (BuildContext context) => isLoadingPath
? Padding(
padding: const EdgeInsets.only(bottom: 4.0))
: _imgPath != null || imgPaths != null && (imgPaths.length > 1 && imgPaths.length < 5)
? new Container(
height: imgPaths.length > 1
? MediaQuery.of(context).size.height * 0.15
: MediaQuery.of(context).size.height * 0.10,
width: MediaQuery.of(context).size.width,
child: new Scrollbar(
child: new ListView.separated(
itemCount: imgPaths != null && imgPaths.isNotEmpty
? imgPaths.length
: 1,
itemBuilder: (BuildContext context, int index) {
final bool isMultiPath = imgPaths != null && imgPaths.isNotEmpty;
final int fileNo = index + 1;
final String name = 'File $fileNo : ' + (isMultiPath
? imgPaths.keys.toList()[index]
: _imgPath ?? '...');
final filePath = isMultiPath
? imgPaths.values.toList()[index].toString()
: _imgPath;
return new ListTile(
title: Transform.translate(
offset: Offset(-25, 0),
child: new Text(
name,
),
),
leading: Icon(Icons.attach_file_outlined, color: Color(0xFFF3A494),),
dense: true,
);
},
separatorBuilder:
(BuildContext context, int index) =>
new Divider(),
)),
)
: new Container(child: Text('4 photos is the maximum'),),
);
}
Dependencies:
file_picker: ^1.4.2
path:
mime:
async:
what you can do is, use the Image Picker dependency. You can find its documentation on pub.dev. after installing it, try using it and store the image uploaded in a file in the device. and with that file name, you can access the image.
You can try the below code, it worked for me. Also don't forget to import dart:io; for using file.
var _storedImage;
Future<void> _takePictureByCamera() async {
final picker = ImagePicker();
final imageFile =
await picker.getImage(source: ImageSource.camera, maxWidth: 600, imageQuality: 60);
setState(() {
_storedImage = File(imageFile!.path);
});
final appDir = await path_provider.getApplicationDocumentsDirectory();
final fileName = path.basename(imageFile!.path);
final savedImage = File(imageFile.path).copy('${appDir.path}/$fileName');
widget.onSelectImage(savedImage);
}
Future<void> _takePictureByGallery() async {
final picker = ImagePicker();
final imageFile =
await picker.getImage(source: ImageSource.gallery, maxWidth: 600);
if (imageFile == null) {
return;
}
setState(() {
_storedImage = File(imageFile.path);
});
final appDir = await path_provider.getApplicationDocumentsDirectory();
final fileName = path.basename(imageFile.path);
final savedImage = File(imageFile.path).copy('${appDir.path}/$fileName');
widget.onSelectImage(savedImage);
}
and after selecting or clicking the image, you can do this to display the image ->
void getImage() async {
final pickedImage = await showModalBottomSheet(
context: accountTabScaffoldMessengerKey.currentContext!,
backgroundColor: Colors.transparent,
enableDrag: true,
// elevation: 0,
builder: (context) => AccountImageUpdateBottomSheet(_selectImage),
);
_selectImage(pickedImage);
}
void _selectImage(File pickedImage) {
setState(() {
_pickedImage = pickedImage;
});
}
The image you selected is stored in the _pickedImage and you can access it by Image.file(_pickedImage).

Flutter ListView.separated frame drop when loading huge list

I'm trying to display a lazy list in flutter for windows. The list has approximately 2300 elements in it. The list is within a FutureBuilder, whose future is to fetch the 2300 elements from Hive database. Each element in the list is a MaterialButton with some properties. Im not getting smooth scrolling when scrolled fast. Some frames are being dropped. I have tried cacheextend and setting automatickeepalives to true. Still having the same problem. When ItemExtend is set to a large number(say 40), the scrollView works fine without frame drop. In release mode, it has better performance, but still some frames are being dropped. What is the best solution available to this problem?
//this Rawscrollbar is returned if the future is have some data
RawScrollbar(
isAlwaysShown: true,
controller: scrollControllerMLC,
thickness: context.percentWidth * .8,
radius: Radius.zero,
thumbColor:
SearchLeftContainerColors.headPoolListThumbColor,
child: ListView.separated(
padding: EdgeInsets.fromLTRB(
context.percentWidth * .5,
context.percentHeight * 0,
context.percentWidth * 1,
context.percentHeight * 1),
itemCount: lengthOfBoxes,
controller: scrollControllerMLC,
// addAutomaticKeepAlives: true,
// physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, int index) {
return ListButtonMEDLC(data[index], index);
},
separatorBuilder: (BuildContext context, int index) {
return Divider(
color: SearchLeftContainerColors
.headsPoolSeparatorColour,
height: 1,
);
},
));
class ListButtonMEDLC extends StatelessWidget {
final String text;
final int index;
ListButtonMEDLC(this.text, this.index);
#override
Widget build(BuildContext context) {
return MaterialButton(
color:
(context.watch<ListButtonColorChangerMEDLC>().isSelectedList[index])
? SearchLeftContainerColors.headPoolListSelectedColor
: SearchLeftContainerColors.headPoolListColor,
hoverColor:
(context.watch<ListButtonColorChangerMEDLC>().isSelectedList[index])
? SearchLeftContainerColors.headPoolListSelectedColor
: SearchLeftContainerColors.headPoolListHoverColor,
highlightColor: SearchLeftContainerColors.headPoolListHighLightedColor,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
text,
style: TextStyle(
fontSize: context.percentWidth * 1.1,
color: (context
.watch<ListButtonColorChangerMEDLC>()
.isSelectedList[index])
? Colors.white
: Colors.black),
),
),
onPressed: () {
context.read<ListButtonColorChangerMEDLC>().changeIsSelectedList(index);
},
);
}
}
//this it the future of the future builder;
loadDrugBox() async {
Map boxes = await DB.boxes.getBoxAsMap("drugs");
lengthOfBoxes = boxes.length;
return boxes;
}
//Provider
class ListButtonColorChangerMEDLC extends ChangeNotifier {
List<bool> isSelectedList = List.generate(lengthOfBoxes, (index) => false);
changeIsSelectedList(int indexOfSelected) {
for (int i = 0; i < lengthOfBoxes; i++) {
if (i == indexOfSelected) {
isSelectedList[i] = true;
} else
isSelectedList[i] = false;
}
notifyListeners();
}
}
Yes. I solved this issue by replacing material button which was an expensive widget with gestureDetector.

Solve the error or give a Complete working example to upload images with multi image picker as a List<file> with Dio

I am trying to upload images for some days and it's not working so please if you have a working code put it as an answer or just solve the code
Here is the code
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:mazadi/Models/ImageUpload.dart';
import 'package:multi_image_picker/multi_image_picker.dart';
class AddAd3 extends StatefulWidget {
AddAd3({
Key key,
}) : super(key: key);
#override
_AddAd3State createState() => _AddAd3State();
}
class _AddAd3State extends State<AddAd3> {
bool _isUploading = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(12.0),
child: ListView(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
width: 100,
height: 100,
child: RaisedButton(
onPressed: () {
getImage();
},
color: Colors.white,
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(7.0)),
child: SizedBox(
width: 90,
height: 90,
child: Center(
child: Icon(
Icons.add,
color: Colors.deepOrange,
size: 30.0,
),
),
),
),
),
],
),
SizedBox(
height: 40,
),
SizedBox(
width: 500,
height: 500,
child: _isUploading == true
? FutureBuilder(
future: uploadImage(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return new Text('loading...');
default:
if (snapshot.hasError)
return new Text('${snapshot.error}');
else
return createListView(context, snapshot);
}
},
)
: Text(""),
),
],
),
));
}
Future getImage() async {
files.clear();
List<Asset> resultList = List<Asset>();
resultList = await MultiImagePicker.pickImages(
maxImages: 10,
enableCamera: false,
);
for (var asset in resultList) {
int MAX_WIDTH = 500; //keep ratio
int height = ((500 * asset.originalHeight) / asset.originalWidth).round();
ByteData byteData =
await asset.getThumbByteData(MAX_WIDTH, height, quality: 80);
if (byteData != null) {
List<int> imageData = byteData.buffer.asUint8List();
MultipartFile u = MultipartFile.fromBytes(
imageData,
filename: asset.name,
);
files.add(u);
}
}
setState(() {
_isUploading = true;
});
}
List<MultipartFile> files = new List<MultipartFile>();
Future<List<String>> uploadImage() async {
FormData formData = new FormData.fromMap({"thumb": files});
Dio dio = new Dio();
var response = await dio.post(
"https://mazadyy.com/index.php?route=api/customer_product/uploadImages",
data: formData);
UploadImage image = UploadImage.fromJson(response.data);
return image.images;
}
Widget createListView(BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Text("error createListView");
}
if (!snapshot.hasData) {
return Text("");
}
List<String> values = snapshot.data;
return new ListView.builder(
shrinkWrap: true,
itemCount: values.length,
itemBuilder: (BuildContext context, int index) {
return new Column(
children: <Widget>[
Image.network(
values[index],
width: 300,
height: 100,
),
SizedBox(
height: 40,
),
],
);
},
);
}
}
and here it the model
class UploadImage {
List<String> images;
UploadImage({this.images});
factory UploadImage.fromJson(Map<String, dynamic> json) {
return UploadImage(images: parseImage(json['images']));
}
static List<String> parseImage(json) {
return new List<String>.from(json);
}
}
the exception that I get is
type String is not a subtype of 'Map<String, dynamic>'
so if you solved the error I will really appreciate it and give him 50 reputation as a reward because
I saw many struggle with the same problem
any help will be appreciated whatever it's
if (images.isEmpty || images[0] != null) {
for (int i = 0; i < images.length; i++) {
ByteData byteData = await images[i].getByteData();
List<int> imageData = byteData.buffer.asUint8List();
http.MultipartFile multipartFile =
http.MultipartFile.fromBytes('image', imageData,
filename: images[i].name,
contentType: MediaType('image', 'jpg'));
imagestoEdit.add(multipartFile);
print(imagestoEdit.length);
}
}
Dio.post(url,formdata:{images:imagestoEdit})

How do I convert a listView to a GridView flutter?

I have a scrollable list view in a horizontal format that I want to covert into a grid view with the same elements.
Container(
height: MediaQuery.of(context).size.height / 4,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: homeList.length,
itemBuilder: (ctx, i) {
return GestureDetector(
onTap: () {
if (i == 0) {
_interstitialAd.show();
} else if (i == 1) {
} else if (i == 2) {
sendInvite();
}
},
);
},
),
)
This is what the list view looks like:
And this is what I want it to look like:
Flutter has the equivalent to ListView.builder for GridView.
You only need to tell it the number of columns/rows(depending in the orientation) you want.
GridView.builder(
itemCount: homeList.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:2),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
if (i == 0) {
_interstitialAd.show();
} else if (i == 1) {
} else if (i == 2) {
sendInvite();
});
},
)

Animating widget positions as the screen scrolls in flutter (GIF included)

I am trying to animate two Rows of widgets to collapse into 1 Row of these widgets as one scroll. I am trying to achieve this behavior inside a SliverAppBar.
For clarification, I have included a GIF here for reference. I would like the behavior you see in the app bar, but instead of 1 row to 2, I would like 2 rows becoming 1.
Here is a quick snippet of what I have so far. I wrapped 2 Row widgets that contain 3 shrinkableBox widgets each into a Wrap widget. I dynamically adjust the size of these boxes by hooking into _scrollController.offset and doing some calculations. The rows do move around dynamically but they don't animate and move abruptly instead.
double kExpandedHeight = 300.0;
Widget build(BuildContext context) {
double size = !_scrollController.hasClients || _scrollController.offset == 0 ? 75.0 : 75 - math.min(45.0, (45 / kExpandedHeight * math.min(_scrollController.offset, kExpandedHeight) * 1.5));
return Scaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverAppBar(
pinned: true,
expandedHeight: kExpandedHeight,
title: new Text(
"Title!",
),
bottom: PreferredSize(child: Wrap(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ShrinkableBox(
onClick: () {
print("tapped");
},
size: size,
),
ShrinkableBox(
onClick: () {
print("tapped");
},
size: size,
),
ShrinkableBox(
onClick: () {
print("tapped");
},
size: size,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ShrinkableBox(
onClick: () {
print("tapped");
},
size: size,
),
ShrinkableBox(
onClick: () {
print("tapped");
},
size: size,
),
ShrinkableBox(
onClick: () {
print("tapped");
},
size: size,
),
],
),
],
), preferredSize: new Size.fromHeight(55),),
)
// ...
// ...Other sliver list content here...
// ...
You could use a Stack together with Positioned widgets to position the ShrinkableBoxes as you need. Since what controls the animation is the scroll offset, you don't need to use animated widgets or an animation controller or something like it. Here's a working example which calculates the positions by linearly interpolating the initial and final position of the boxes (you can get different animation paths by changing the Curves.linear to other curves):
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: Home()));
}
class Home extends StatefulWidget {
#override
State createState() => HomeState();
}
class HomeState extends State<Home> {
static const double kExpandedHeight = 300.0;
static const double kInitialSize = 75.0;
static const double kFinalSize = 30.0;
static const List<Color> kBoxColors = [
Colors.red,
Colors.green,
Colors.yellow,
Colors.purple,
Colors.orange,
Colors.grey,
];
ScrollController _scrollController = new ScrollController();
#override
void initState() {
_scrollController.addListener(() {
setState(() { /* State being set is the Scroll Controller's offset */ });
});
}
#override
void dispose() {
_scrollController.dispose();
}
Widget build(BuildContext context) {
double size = !_scrollController.hasClients || _scrollController.offset == 0
? 75.0
: 75 -
math.min(45.0,
(45 / kExpandedHeight * math.min(_scrollController.offset, kExpandedHeight) * 1.5));
return Scaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverAppBar(
pinned: true,
expandedHeight: kExpandedHeight,
title: Text("Title!"),
bottom: PreferredSize(
preferredSize: Size.fromHeight(55),
child: buildAppBarBottom(size),
),
),
SliverFixedExtentList(
itemExtent: 50.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
),
],
),
);
}
Widget buildAppBarBottom(double size) {
double t = (size - kInitialSize) / (kFinalSize - kInitialSize);
const double initialContainerHeight = 2 * kInitialSize;
const double finalContainerHeight = kFinalSize;
return Container(
height: lerpDouble(initialContainerHeight, finalContainerHeight, t),
child: LayoutBuilder(
builder: (context, constraints) {
List<Widget> stackChildren = [];
for (int i = 0; i < 6; i++) {
Offset offset = getInterpolatedOffset(i, constraints, t);
stackChildren.add(Positioned(
left: offset.dx,
top: offset.dy,
child: buildSizedBox(size, kBoxColors[i]),
));
}
return Stack(children: stackChildren);
},
),
);
}
Offset getInterpolatedOffset(int index, BoxConstraints constraints, double t) {
Curve curve = Curves.linear;
double curveT = curve.transform(t);
Offset a = getOffset(index, constraints, kInitialSize, 3);
Offset b = getOffset(index, constraints, kFinalSize, 6);
return Offset(
lerpDouble(a.dx, b.dx, curveT),
lerpDouble(a.dy, b.dy, curveT),
);
}
Offset getOffset(int index, BoxConstraints constraints, double size, int columns) {
int x = index % columns;
int y = index ~/ columns;
double horizontalMargin = (constraints.maxWidth - size * columns) / 2;
return Offset(horizontalMargin + x * size, y * size);
}
Widget buildSizedBox(double size, Color color) {
return Container(
height: size,
width: size,
color: color,
);
}
}

Resources