Flutter Web: Crop image without dart:io - flutter-web

I'm writing this at a time when dart:io is not available for Flutter Web. dart:io has the commonly used 'File' type which is required by most Flutter imaging packages.
Attempting to crop an image of unknown encoding in the UInt8List format. I spent a few days building out a simple cropping tool without dart:io
Check below for solution.

I would turn it into a package but I'm rushing through a project and don't have time.
Use This block of code to initialize the cropping route:
Future<Uint8List> cropResult = await Navigator.push(
context,
MaterialPageRoute(
builder: (ctx) => Cropper(
image: _image,
),
),
);
_image = await cropResult;
Here is the route page managing the crop. Very basic.
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as Im;
import 'dart:math';
class Cropper extends StatefulWidget {
final Uint8List image;
const Cropper({Key key, this.image}) : super(key: key);
#override
_CropperState createState() => _CropperState(image: image);
}
class _CropperState extends State<Cropper> {
Uint8List image;
Uint8List resultImg;
double scale = 1.0;
double zeroScale; //Initial scale to fit image in bounding crop box.
Offset offset = Offset(0.0, 0.0); //Used in translation of image.
double cropRatio = 6 / 10; //aspect ratio of desired crop.
Im.Image decoded; //decoded image to get pixel dimensions
double imgWidth; //img pixel width
double imgHeight; //img pixel height
Size cropArea; //Size of crop bonding box
double cropPad; //Aesthetic crop box padding.
double pXa; //Positive X available in translation
double pYa; //Positive Y available in translation
double totalX; //Total X of scaled image
double totalY; //Total Y of scaled image
Completer _decoded = Completer<bool>();
Completer _encoded = Completer<Uint8List>();
_CropperState({this.image});
#override
initState() {
_decodeImg();
super.initState();
}
_decodeImg() {
if (_decoded.isCompleted) return;
decoded = Im.decodeImage(image);
imgWidth = decoded.width.toDouble();
imgHeight = decoded.height.toDouble();
_decoded?.complete(true);
}
_encodeImage(Im.Image cropped) async {
resultImg = Im.encodePng(cropped);
_encoded?.complete(resultImg);
}
void _cropImage() async {
double xPercent = pXa != 0.0 ? 1.0 - (offset.dx + pXa) / (2 * pXa) : 0.0;
double yPercent = pYa != 0.0 ? 1.0 - (offset.dy + pYa) / (2 * pYa) : 0.0;
double cropXpx = imgWidth * cropArea.width / totalX;
double cropYpx = imgHeight * cropArea.height / totalY;
double x0 = (imgWidth - cropXpx) * xPercent;
double y0 = (imgHeight - cropYpx) * yPercent;
Im.Image cropped = Im.copyCrop(
decoded, x0.toInt(), y0.toInt(), cropXpx.toInt(), cropYpx.toInt());
_encodeImage(cropped);
Navigator.pop(context, _encoded.future);
}
computeRelativeDim(double newScale) {
totalX = newScale * cropArea.height * imgWidth / imgHeight;
totalY = newScale * cropArea.height;
pXa = 0.5 * (totalX - cropArea.width);
pYa = 0.5 * (totalY - cropArea.height);
}
bool init = true;
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: Text('Crop Photo'),
centerTitle: true,
leading: IconButton(
onPressed: _cropImage,
tooltip: 'Crop',
icon: Icon(Icons.crop),
),
actions: [
RaisedButton(
onPressed: () => Navigator.pop(context, null),
child: Text('Cancel'),
)
],
),
body: Column(
children: <Widget>[
Expanded(
child: FutureBuilder(
future: _decoded.future,
builder: (ctx, snap) {
if (!snap.hasData)
return Center(
child: Text('Loading...'),
);
return LayoutBuilder(
builder: (ctx, cstr) {
if (init) {
cropPad = cstr.maxHeight * 0.05;
double tmpWidth = cstr.maxWidth - 2 * cropPad;
double tmpHeight = cstr.maxHeight - 2 * cropPad;
cropArea = (tmpWidth / cropRatio > tmpHeight)
? Size(tmpHeight * cropRatio, tmpHeight)
: Size(tmpWidth, tmpWidth / cropRatio);
zeroScale = cropArea.height / imgHeight;
computeRelativeDim(scale);
init = false;
}
return GestureDetector(
onPanUpdate: (pan) {
double dy;
double dx;
if (pan.delta.dy > 0)
dy = min(pan.delta.dy, pYa - offset.dy);
else
dy = max(pan.delta.dy, -pYa - offset.dy);
if (pan.delta.dx > 0)
dx = min(pan.delta.dx, pXa - offset.dx);
else
dx = max(pan.delta.dx, -pXa - offset.dx);
setState(() => offset += Offset(dx, dy));
},
child: Stack(
children: [
Container(
color: Colors.black.withOpacity(0.5),
height: cstr.maxHeight,
width: cstr.maxWidth,
child: ClipRect(
child: Container(
alignment: Alignment.center,
height: cropArea.height,
width: cropArea.width,
child: Transform.translate(
offset: offset,
child: Transform.scale(
scale: scale * zeroScale,
child: OverflowBox(
maxWidth: imgWidth,
maxHeight: imgHeight,
child: Image.memory(
image,
),
),
),
),
),
),
),
IgnorePointer(
child: Center(
child: Container(
height: cropArea.height,
width: cropArea.width,
decoration: BoxDecoration(
border:
Border.all(color: Colors.white, width: 2),
),
),
),
),
],
),
);
},
);
},
),
),
Row(
children: <Widget>[
Text('Scale:'),
Expanded(
child: SliderTheme(
data: theme.sliderTheme,
child: Slider(
divisions: 50,
value: scale,
min: 1,
max: 2,
label: '$scale',
onChanged: (n) {
double dy;
double dx;
computeRelativeDim(n);
dy = (offset.dy > 0)
? min(offset.dy, pYa)
: max(offset.dy, -pYa);
dx = (offset.dx > 0)
? min(offset.dx, pXa)
: max(offset.dx, -pXa);
setState(() {
offset = Offset(dx, dy);
scale = n;
});
},
),
),
),
],
),
],
),
);
}
}

Related

Design arrow in Flutter

I'm trying to design in Flutter. I don't know how to design this arrow in container.
You can use CustomPainer or ClipPath for this. You can also check this answer for this kind of layout.
class PriceTagPaint extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.red
..strokeCap = StrokeCap.round
..style = PaintingStyle.fill;
Path path = Path();
path
..lineTo(size.width * .85, 0) // .85 amount of right gap
..lineTo(size.width, size.height / 2)
..lineTo(size.width * .85, size.height)
..lineTo(0, size.height)
..lineTo(0, 0)
..close();
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
And use
SizedBox(
height: 100,
width: 250,
child: CustomPaint(
painter: PriceTagPaint(),
child: Align(
alignment: Alignment(-.2, 0), //litle left
child: Text(
"-11% Off",
style: TextStyle(
fontSize: 44,
color: Colors.white,
fontWeight: FontWeight.bold),
),
),
),
),
Result
More about CustomPaint

Blank Screen when fetching data from api

I am fetching a data from api and display it in list using ListView builder, it's working properly on emulator but when I run app on my android phone it is displaying blank screen, here it looks.
below Leave History label, I am displaying data from api, but it looks like this now.
It looks like this on emulator
Here is the code:
Fetching api data and inserting it into list
List<leaveHistory> historyList = [];
var loader = 0;
Future<List<leaveHistory>> _getRecord() async {
Dio dio = new Dio();
var data = {
'username': getname,
'token': getaccesstoken,
};
return dio
.post(localhostUrlLeaveHistoryON, data: json.encode(data))
.then((onResponse) async {
Map<String, dynamic> map = onResponse.data;
List<dynamic> data = map["data"];
for (var historyData in data) {
leaveHistory history = leaveHistory(
historyData["Date"],
historyData["description"],
historyData["type"],
historyData['fromdate'],
historyData["todate"],
historyData["noofleavedays"],
historyData["leave"]);
historyList.add(history);
loader = 0;
}
if (historyList.length == 0) {
loader = 1;
}
return historyList;
}).catchError((onerror) {
loader = 1;
print(onerror.toString());
});
}
display the data using ListView.builder
Widget build(BuildContext context) {
return Scaffold(
appBar: new MyAppBar(
title: Text("Leaves Tracker"),
onpressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Profile()));
}),
drawer: NavigationDrawerWidget(),
body: Stack(overflow: Overflow.visible, children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(20, 10, 0, 0),
child: Text(
"Leave History",
style: TextStyle(fontSize: 30, fontFamily: 'RaleWay'),
),
),
Container(
padding: EdgeInsets.fromLTRB(10, 30, 0, 0),
child: FutureBuilder(
future: _getRecord(),
builder: (BuildContext context,
AsyncSnapshot<List<leaveHistory>> snapshot) {
if (snapshot.hasData) {
if (historyList.length != 0) {
return Container(
padding: EdgeInsets.fromLTRB(0, 30, 0, 0),
child: Expanded(
child: ListView.builder(
itemCount: snapshot.data.length,
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
Color c = Colors.red;
//changing the avatar color
if (i == 0)
i++;
else if (i == 1) {
c = Colors.blue;
i++;
} else if (i == 2) {
c = Color(int.parse(annualboxcolor));
i = 0;
}
Color c1 = Colors.green;
if (snapshot.data[index].Status == "Approved") {
c1 = Colors.green;
} else if (snapshot.data[index].Status ==
"Not Approved") {
c1 = Colors.red;
} else if (snapshot.data[index].Status ==
"Pending") {
c1 = Color(int.parse(pendingrequestcolor));
}
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
ListTile(
leading: CircleAvatar(
radius: 25,
backgroundColor: c,
child: Container(
padding: EdgeInsets.fromLTRB(
0, 10, 0, 0),
child:
Column(children: <Widget>[
Text(
snapshot
.data[index].noofdays
.toString(),
style: TextStyle(
fontSize: 15.0,
fontWeight:
FontWeight.bold,
color: Colors.white),
),
Text(
int.parse(snapshot
.data[index]
.noofdays) >
1
? "Days"
: "Day",
style: TextStyle(
fontSize: 10.0,
color: Colors.white),
)
]),
)),
title:
Text(snapshot.data[index].type),
isThreeLine: true,
subtitle: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
snapshot.data[index].fromdate +
" To " +
snapshot.data[index].todate,
),
Text(snapshot.data[index].Status,
style: TextStyle(
color: c1,
fontWeight:
FontWeight.bold)),
Text(
snapshot
.data[index].description,
)
],
)),
Divider(
color: Colors.grey,
height: 10,
),
],
));
},
)));
}
}
if (loader == 1) {
print("run");
return Nodatafound();
} else {
return Center(
child: CircularProgressIndicator(
color: Colors.blue[500],
));
}
}),
),
]));
}
}
history class
class leaveHistory {
final String date;
final String fromdate;
final String todate;
final String description;
final String type;
final String Status;
final String noofdays;
leaveHistory(this.date, this.description, this.type, this.fromdate,
this.todate, this.noofdays, this.Status);
}
When i redirect to this screen from drawer it first display this error
DioError [DioErrorType.response]: Http status error [300]
on this catchError this line print(onerror.toString());
Update:
When i connect my phone with my laptop with datacable, it's working fine, but when i install apk problem occurs.
Please help where i am doing wrong.
If #Huthaifa was not the answer you're looking, have you tried to update the value of: localhostUrlLeaveHistoryON?
If you're using something like: http://localhost
Please try to change this to your computer's ip address.
Your error is caused by incorrect use of parent data widget. And that is from using expanded inside container. Remove your Expanded from here:
return Container(
padding: EdgeInsets.fromLTRB(0, 30, 0, 0),
child: Expanded(
child: ListView.builder(
You will also notice that the error in console is gone. Always read the errors and understand them. The error will 100% say, incorrect use of parent widget. Just remove the expanded.

How to achieve coplanar card layout in flutter

I'm trying to achieve a coplanar/disallined card collection layout in flutter. This is on Card layout on material Design documentation https://imgur.com/miHhpFs
I''ve tried with a GridView.count layout, but can't figure out how to disalline items. I also found out that there are user created libraries like this https://pub.dev/packages/flutter_staggered_grid_view that can help with what I want to do, but I'd prefer an official solution since this layout is on the material design documentation.
I don't know what the "official solution" for creating this is, but I believe it would be something along the lines of "Create three list views with three scroll controllers, one of which is offset from the other two, and then sync up their scroll controllers, accounting for the offset."
I didn't know if this would work or not, so I created this dartpad in order to test it:
https://dartpad.dev/f9c8f00b78899d3c8c4a426d3466a8a3
Just in case the dartpad doesn't work, here is the code that I used:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ScrollSync(),
);
}
}
class ScrollSync extends StatefulWidget {
#override
_ScrollSyncState createState() => _ScrollSyncState();
}
class _ScrollSyncState extends State<ScrollSync> {
CustomScrollController _controller1 =
CustomScrollController(keepScrollOffset: true);
CustomScrollController _controller2 = CustomScrollController(
initialScrollOffset: 150.0, keepScrollOffset: true);
CustomScrollController _controller3 =
CustomScrollController(keepScrollOffset: true);
#override
void initState() {
_controller1.addListener(() =>
_controller2.jumpToWithoutGoingIdleAndKeepingBallistic(
_controller1.offset, "1 listen 2"));
_controller1.addListener(() =>
_controller3.jumpToWithoutGoingIdleAndKeepingBallistic(
_controller1.offset, "1 listen 3"));
_controller2.addListener(() =>
_controller1.jumpToWithoutGoingIdleAndKeepingBallistic(
_controller2.offset, "2 listen 1"));
_controller2.addListener(() =>
_controller3.jumpToWithoutGoingIdleAndKeepingBallistic(
_controller2.offset, "2 listen 3"));
_controller3.addListener(() =>
_controller1.jumpToWithoutGoingIdleAndKeepingBallistic(
_controller3.offset, "3 listen 1"));
_controller3.addListener(() =>
_controller2.jumpToWithoutGoingIdleAndKeepingBallistic(
_controller3.offset, "3 listen 2"));
super.initState();
}
#override
void dispose() {
_controller1.dispose();
_controller2.dispose();
_controller3.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Scroll Sync"),
),
body: Row(
children: [
Flexible(
flex: 1,
child: ListView.builder(
controller: _controller1,
scrollDirection: Axis.vertical,
itemBuilder: (_, index) => Container(
color: Colors.blueGrey,
width: 150,
height: 300,
margin: EdgeInsets.all(10.0),
child: Center(
child: Text(
"$index",
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.display2,
),
),
),
),
),
Flexible(
flex: 1,
child: ListView.builder(
controller: _controller2,
itemBuilder: (_, index) => Container(
height: 300,
color: Colors.blueGrey,
margin: EdgeInsets.all(10.0),
child: Center(
child: Text(
"$index",
style: Theme.of(context)
.textTheme
.display2
.copyWith(color: Colors.white),
),
),
),
),
),
Flexible(
flex: 1,
child: ListView.builder(
controller: _controller3,
itemBuilder: (_, index) => Container(
height: 300,
color: Colors.blueGrey,
margin: EdgeInsets.all(10.0),
child: Center(
child: Text(
"$index",
style: Theme.of(context)
.textTheme
.display2
.copyWith(color: Colors.black),
),
),
),
),
),
],
),
);
}
}
class CustomScrollController extends ScrollController {
CustomScrollController(
{double initialScrollOffset = 0.0,
keepScrollOffset = true,
debugLabel,
String controller})
: super(
initialScrollOffset: initialScrollOffset,
keepScrollOffset: keepScrollOffset,
debugLabel: debugLabel,
);
#override
_SilentScrollPosition createScrollPosition(
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition,
) {
return _SilentScrollPosition(
physics: physics,
context: context,
oldPosition: oldPosition,
initialPixels: initialScrollOffset,
);
}
void jumpToWithoutGoingIdleAndKeepingBallistic(
double value, String controller) {
assert(positions.isNotEmpty, 'ScrollController not attached.');
for (_SilentScrollPosition position
in new List<ScrollPosition>.from(positions))
position.jumpToWithoutGoingIdleAndKeepingBallistic(value, controller);
}
}
class _SilentScrollPosition extends ScrollPositionWithSingleContext {
_SilentScrollPosition({
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition,
double initialPixels,
}) : super(
physics: physics,
context: context,
oldPosition: oldPosition,
initialPixels: initialPixels,
);
void jumpToWithoutGoingIdleAndKeepingBallistic(
double value, String controller) {
print(controller);
print(value);
print(pixels);
if (controller[0] == "2") {
if (pixels + 150.0 != value) {
forcePixels(value - 150.0);
}
} else if (controller[9] == "2") {
if (pixels - 150.0 != value) {
forcePixels(value + 150.0);
}
} else if (pixels != value) {
forcePixels(value);
}
}
}

How get moving text in widget with given width

I am building a radio app. Like in Spotify, there is a bar with the current title and artist, the text should be in one line and in a given width. How can I let the text move from right to left and back?
When using a self-made animation, I want to have a fixed speed of the moving text, so I need the time and the width of the text widget.
Is there a package/built-in option to do this?
Or do I have to use a self-made animation? If so, how can I get the text widget width?
Controller and animation:
AnimationController(duration: Duration(seconds: 10), vsync: this);
animation = Tween<double>(begin: 0, end: 1)
.animate(CurvedAnimation(parent: _controller, curve: Curves.linear));
animation.addListener(() {
setState(() {});
});
_controller.repeat();
build method
double value =
-300 * (animation.value <= 0.5 ? animation.value : 1 - animation.value);
return Container(
child: SizedBox(
width: widget.width,
height: 24,
child: Transform.translate(
offset: Offset(value, 0),
child: widget.text,
),
),
);
You can do something like this:
import 'dart:async';
import 'package:flutter/material.dart';
class ScrollingText extends StatefulWidget {
final String text;
final TextStyle textStyle;
final Axis scrollAxis;
final double ratioOfBlankToScreen;
ScrollingText({
#required this.text,
this.textStyle,
this.scrollAxis: Axis.horizontal,
this.ratioOfBlankToScreen: 0.25,
}) : assert(text != null,);
#override
State<StatefulWidget> createState() {
return ScrollingTextState();
}
}
class ScrollingTextState extends State<ScrollingText>
with SingleTickerProviderStateMixin {
ScrollController scrollController;
double screenWidth;
double screenHeight;
double position = 0.0;
Timer timer;
final double _moveDistance = 3.0;
final int _timerRest = 100;
GlobalKey _key = GlobalKey();
#override
void initState() {
super.initState();
scrollController = ScrollController();
WidgetsBinding.instance.addPostFrameCallback((callback) {
startTimer();
});
}
void startTimer() {
if (_key.currentContext != null) {
double widgetWidth =
_key.currentContext.findRenderObject().paintBounds.size.width;
double widgetHeight =
_key.currentContext.findRenderObject().paintBounds.size.height;
timer = Timer.periodic(Duration(milliseconds: _timerRest), (timer) {
double maxScrollExtent = scrollController.position.maxScrollExtent;
double pixels = scrollController.position.pixels;
if (pixels + _moveDistance >= maxScrollExtent) {
if (widget.scrollAxis == Axis.horizontal) {
position = (maxScrollExtent -
screenWidth * widget.ratioOfBlankToScreen +
widgetWidth) /
2 -
widgetWidth +
pixels -
maxScrollExtent;
} else {
position = (maxScrollExtent -
screenHeight * widget.ratioOfBlankToScreen +
widgetHeight) /
2 -
widgetHeight +
pixels -
maxScrollExtent;
}
scrollController.jumpTo(position);
}
position += _moveDistance;
scrollController.animateTo(position,
duration: Duration(milliseconds: _timerRest), curve: Curves.linear);
});
}
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
screenWidth = MediaQuery.of(context).size.width;
screenHeight = MediaQuery.of(context).size.height;
}
Widget getBothEndsChild() {
if (widget.scrollAxis == Axis.vertical) {
String newString = widget.text.split("").join("\n");
return Center(
child: Text(
newString,
style: widget.textStyle,
textAlign: TextAlign.center,
),
);
}
return Center(
child: Text(
widget.text,
style: widget.textStyle,
));
}
Widget getCenterChild() {
if (widget.scrollAxis == Axis.horizontal) {
return Container(width: screenWidth * widget.ratioOfBlankToScreen);
} else {
return Container(height: screenHeight * widget.ratioOfBlankToScreen);
}
}
#override
void dispose() {
super.dispose();
if (timer != null) {
timer.cancel();
}
}
#override
Widget build(BuildContext context) {
return ListView(
key: _key,
scrollDirection: widget.scrollAxis,
controller: scrollController,
physics: NeverScrollableScrollPhysics(),
children: <Widget>[
getBothEndsChild(),
getCenterChild(),
getBothEndsChild(),
],
);
}
}
And use the widget like this:
ScrollingText(
text: text,
textStyle: TextStyle(fontSize: 12),
)
use this widget from pub.dev marquee.
Marquee(
text: 'There once was a boy who told this story about a boy: "',
)

Show masked area on top of camera widget

I'm trying to implement a credit card reader (using some API). For the user to take a picture of the card, I'm presenting the camera preview widget full screen. I would like to mask out an area so the user centers the card there.
He're an illustration of what I have in mind
I was wondering how this masking could be accomplished in flutter? Currently I'm just drawing a BoxDecoration, but that's missing the semi transparent, greyed out area.
Container(
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.width * 0.8 / 1.55,
decoration: new BoxDecoration(
border: Border.all(
color: Colors.white,
width: 2.0,
style: BorderStyle.solid),
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(4.0))),
)
You can try something like this for the time being but it is a very inefficient solution and I am sure there must be a better solution that I too would like to know.
#override
Widget build(BuildContext context) {
if (!controller.value.isInitialized) {
return Container();
}
return Container(
height: MediaQuery.of(context).size.height,
child: Stack(
children: <Widget>[
CustomPaint(
foregroundPainter: P(),
child: CameraPreview(controller),
),
ClipPath(
clipper: Clip(),
child: CameraPreview(controller)),
],
),
);
}
class P extends CustomPainter{
#override
void paint(Canvas canvas, Size size) {
canvas.drawColor(Colors.grey.withOpacity(0.8), BlendMode.dstOut);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
class Clip extends CustomClipper<Path>{
#override
getClip(Size size) {
print(size);
Path path = Path()
..addRRect(RRect.fromRectAndRadius(Rect.fromLTWH(10, size.height/2-120, size.width-20, 240), Radius.circular(26)));
return path;
}
#override
bool shouldReclip(oldClipper) {
// TODO: implement shouldReclip
return true;
}
}
I now just went with a custom BoxPainter and first draw the background, then the card area and finally blend the layers together to 'cut out' the center part.
import 'package:flutter/widgets.dart';
class CardDecoration extends Decoration {
#override
BoxPainter createBoxPainter([VoidCallback onChanged]) {
return _CardPainter();
}
}
class _CardPainter extends BoxPainter {
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
final clearPaint = new Paint()
..color = Colors.black
..style = PaintingStyle.fill
..blendMode = BlendMode.dstOut;
final bgPaint = new Paint()
..color = Color.fromARGB(150, 0, 0, 0)
..style = PaintingStyle.fill;
final borderPaint = new Paint()
..color = Colors.white.withAlpha(120)
..style = PaintingStyle.stroke
..strokeWidth = 3.0;
final rect = offset & configuration.size;
final cardWidth = 0.8*rect.width;
final cardHeight = cardWidth/1.55;
canvas.saveLayer(Rect.fromLTRB(0, 0, rect.width, rect.height), clearPaint);
canvas.drawPaint(bgPaint);
canvas.saveLayer(Rect.fromLTRB(0, 0, rect.width, rect.height), clearPaint);
canvas.drawRRect(RRect.fromLTRBR(0.1*rect.width, (rect.height-cardHeight)/2, 0.9*rect.width, (rect.height+cardHeight)/2, Radius.circular(8)), bgPaint);
canvas.restore();
canvas.restore();
canvas.drawRRect(RRect.fromLTRBR(0.1*rect.width, (rect.height-cardHeight)/2, 0.9*rect.width, (rect.height+cardHeight)/2, Radius.circular(8)), borderPaint);
}
}
Probably, you can try with columns, rows, and containers :).
Widget getMaskCard(BuildContext context) {
Color _background = Colors.grey.withOpacity(0.2);
return Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Container(
height: MediaQuery.of(context).size.height,
width: 1,
color: _background,
),
),
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width * 0.8,
child: Column(
children: <Widget>[
Expanded(
child: Container(
color: _background,
),
),
Container(
height: 180,
width: MediaQuery.of(context).size.width * 0.8,
color: Colors.transparent,
),
Expanded(
child: Container(
color: _background,
),
),
],
),
),
Expanded(
child: Container(
height: MediaQuery.of(context).size.height,
width: 1,
color: _background,
),
),
],
)
],
);
}
Example

Resources