How to implement overlapping scroll and gesture regions in Flutter mobile app - flutter

I am developing a mobile app that includes a graph of data on a page. I want to be able to scroll sections of the graph based on user input and am unsure about how to go about this. I am new to Flutter and still learning a lot so am looking for a design steer, i.e. what components to use and how to structure those to achieve the effect that I want.
I currently have the whole page scrolling with a ScrollerView and ScrollControllers, but this means that the top row (the heading and axis), plus the left column (the row labels), scroll off the page. It also means that the user has to scroll the application using scroll bars rather than being able to use gestures anywhere on the screen.
I want to implement a solution where the user can scroll up/down, but the heading and axis stay in place, or can scroll left/right and the heading and row labels remain in place. The image below hopefully shows what I want to achieve.
What are the best components to use to achieve this effect? I am thinking I might have to implement RawGestureDetectors and CustomScrollViews, but wondering if there are some simpler out-of-the-box components to achieve this effect? I am happy to read/learn about whatever is recommended and how to implement these myself, but can someone steer me in a direction in terms of which out-of-the-box components might be best to do this and how these might need to be structured (i.e. parent and child relationships).
I have done a search on stackoverflow and not found anything that specifically covers this, other than an unanswered question here: How to implement a whatsapp mobile scroll effect in flutter. If there are other questions with answers or articles that someone can point me to then I am happy to read these too.

The exact thing you're looking for isn't available yet but you can see the preview for two-dimensional scrolling here.
For now, I would implement the yellow box using a CustomScrollView with a SliverAppBar.
You can also give the DataTable widget a try.

I have managed to achieve the effect I want using GestureDetector and AnimatedBuilder widgets. It needs some tidying up to set limits about how far the user can slide the contents, but I have managed to get the top row axis and chart content to slide left and right in sync, and the left-hand labels and chart content to slide up and down in sync.
I have made use of DecoratedBox with a colour to mask areas of the screen where I want to hide the scrolling text, and Stack to ensure that the components are drawn in the right order so that the scrolling text moves under the masks. I haven't bothered with the edges of the app, because my chart is full screen in my mobile app. However, if you wanted to use this effect on a subsection of the screen then you would need to implement masking around the rest of the screen so that the text doesn't show up.
If anyone knows a better way to do this, e.g. only painting the area of the screen where it is visible to the user, then please post an example because that might be a better way to do things.
Here is my code if anyone wants to see how I managed this:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
double _left = 0.0;
double _top = 0.0;
late Offset _leftOnly = Offset(_left, 0.0);
late Offset _leftTop = Offset(_left, _top);
late Offset _topOnly = Offset(0.0, _top);
void _setLeft(DragUpdateDetails details) {
setState(() {
_left += details.primaryDelta!;
_leftTop = Offset(_left, _top);
_leftOnly = Offset(_left, 0);
});
}
void _setTop(DragUpdateDetails details) {
setState(() {
_top += details.primaryDelta!;
_leftTop = Offset(_left, _top);
_topOnly = Offset(0.0, _top);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Padding(
padding: const EdgeInsets.only(top: 200, left: 200),
child: Column(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(children: [
Row(children: const [
//
// Top-left box blank to push bottom row into correct space
SizedBox(
height: 60,
width: 100,
),
//
// Top-right box blank to push bottom row into correct space
SizedBox(
height: 60,
width: 400,
),
]),
//
// Draw bottom row of boxes
//
Row(children: [
//
// Bottom-left box blank to push right-hand box into correct space
//
const SizedBox(
height: 200,
width: 100,
),
//
// Bottom-right box with chart content
//
GestureDetector(
onHorizontalDragUpdate: _setLeft,
onVerticalDragUpdate: _setTop,
child: ConstrainedBox(
key: _bottomRight,
constraints:
const BoxConstraints(maxHeight: 200, maxWidth: 500),
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(),
),
child: OverflowBox(
maxHeight: double.infinity,
maxWidth: double.infinity,
alignment: Alignment.topLeft,
child: Slide(
offset: _leftTop,
child: Column(
children: const [
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
Text(
'chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--chart-- chart--'),
],
),
),
),
),
),
),
]),
//
// Bottom-left box drawn with content and mask for scrolling text
//
GestureDetector(
onVerticalDragUpdate: _setTop,
child: SizedBox(
height: 200,
width: 100,
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(),
color: Colors.white,
),
child: OverflowBox(
alignment: Alignment.topLeft,
maxHeight: double.infinity,
child: Slide(
offset: _topOnly,
child: Column(
children: const [
Text('Label'),
Text('Label'),
Text('Label'),
Text('Label'),
Text('Label'),
Text('Label'),
Text('Label'),
Text('Label'),
Text('Label'),
Text('Label'),
Text('Label'),
Text('Label'),
Text('Label'),
Text('Label'),
],
),
),
),
),
),
),
//
// Draw top row on boxes with masks to cover scrolling text
Row(children: [
//
// Top-left box blank to draw right hand axis box first
//
const SizedBox(
height: 60,
width: 100,
),
//
// Top-right box draw axis with mask
//
GestureDetector(
onHorizontalDragUpdate: _setLeft,
child: SizedBox(
height: 60,
width: 400,
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(),
color: Colors.white,
),
child: OverflowBox(
maxWidth: double.infinity,
alignment: Alignment.centerLeft,
child: Slide(
offset: _leftOnly,
child: const Text(
'axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--axis--'),
),
),
),
),
),
]),
//
// Mask to hide text of axis on left
SizedBox(
height: 60,
width: 100,
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(),
),
child: const Text('Heading'),
),
),
]),
]),
),
),
);
}
}
class Slide extends StatefulWidget {
Slide({
Key? key,
required Widget child,
required Offset offset,
}) : super(key: key) {
_child = child;
_offset = offset;
}
late final Widget _child;
late final Offset _offset;
#override
State<Slide> createState() => _SlideState();
}
class _SlideState extends State<Slide> with TickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget? child) {
return Transform.translate(
offset: widget._offset,
child: child,
);
},
child: widget._child,
);
}
}

Related

how to dynamically build and display widgets based off a string value?

I've created an app with a large amount of buttons that update a string value.
there is a container in the middle that needs to display different widgets for each button chosen. and when a new button is pressed, the middle container will need to pop the old displaying widgets and build a new one in it's place.
I thought about layering the middle container with all the widgets to display and use the visibility widget connected to the string value using if statements to show/hide them but I don't think this is the best way to do it.
I've put together sample code below
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
#override
State<TestPage> createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
String displayText = 'Choose option';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
Expanded(
child: Column(
children: [
SizedBox(
height: 50,
),
Button(
text: 'option A',
onTap: () {
displayText = 'Option A';
},
),
//what would I put in the onTap property to get the app to build the OneOfTheWidgetsToBuild widget in the Display Container below based off the updated string value?
SizedBox(
height: 5,
),
Button(text: 'option B', onTap: null),
],
),
),
Expanded(
child: Column(
children: [
SizedBox(
height: 50,
),
Center(
child: Container(
//DISPLAY CONTAINER
height: 40,
width: 100,
color: Colors.yellow,
child: Center(
child: Text(
'$displayText',
style: TextStyle(color: Colors.black),
),
),
),
)
],
),
),
Expanded(
child: Column(
children: [
SizedBox(
height: 50,
),
Button(text: 'option C', onTap: null),
SizedBox(
height: 5,
),
Button(text: 'option D', onTap: null),
],
),
),
],
),
);
}
}
and here is the widget to display in the middle box:
class OneOfTheWidgetsToBuild extends StatelessWidget {
const OneOfTheWidgetsToBuild({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 40,
width: 100,
color: Colors.green,
);
}
}
thanks so much for your help
If I understand you correctly: You want to show a bunch of different widgets based on the string's value.
One way to go about it, instead of using a bunch of if/else statements is to use a switch case and a function to correctly render the correct widgets.
For example:
Create a widget:
var _displayWidget = Container();
then, create a function to update that widget
void updateWidget(String option) {
switch (option) {
case 'Option A':
_displayWidget = widgetOne();
break;
case 'Option B':
_displayWidget = widgetTwo();
break;
default:
_displayWidget = Container();
break;
}
}
and then, in your Build method:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Test'),
),
body: Column(
children: [
DropdownButton<String>(
items: const [
DropdownMenuItem(
child: Text('Option A'),
value: 'Option A',
),
DropdownMenuItem(
child: Text('Option B'),
value: 'Option B',
),
],
onChanged: (value) {
setState(() {
updateWidget(value!);
});
},
),
_displayWidget,
],
),
);
}

how to add two posts per screen flutter

I'm trying to create a video screen like the picture. on the right side picture showing my implementation so far. how can I create video screen like half of the screen and one after the other as left UI below. (two videos per screen). appriciate your help on this. I haveadded my code for your refernce
post_template.dart
import 'package:flutter/material.dart';
import '../constants/button.dart';
class PostTemplate extends StatelessWidget {
final String username;
final String videoDescription;
final String numberOfLikes;
final String numberOfComments;
final String numberOfShares;
final userPost;
PostTemplate({
required this.username,
required this.videoDescription,
required this.numberOfLikes,
required this.numberOfComments,
required this.numberOfShares,
required this.userPost,
});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
// user post (at the very back)
userPost,
// user name and caption
Padding(
padding: const EdgeInsets.all(20.0),
child: Container(
alignment: Alignment(-1, 1),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text('#' + username,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
)),
SizedBox(
height: 10,
),
RichText(
text: TextSpan(
children: [
TextSpan(
text: videoDescription,
style: TextStyle(color: Colors.white)),
TextSpan(
text: ' #live #lalaive',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white)),
],
),
)
],
),
),
),
// buttons
Padding(
padding: const EdgeInsets.all(20.0),
child: Container(
alignment: Alignment(1, 1),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
MyButton(
icon: Icons.people,
number: numberOfComments,
),
MyButton(
icon: Icons.thumb_up,
number: numberOfLikes,
),
MyButton(
icon: Icons.share,
number: numberOfShares,
),
],
),
),
)
],
),
);
}
}
video_screen.dart
import 'package:flutter/material.dart';
import 'package:lala_live/screens/post_template.dart';
class VideoScreen extends StatefulWidget {
const VideoScreen({Key? key}) : super(key: key);
#override
_VideoScreenState createState() => _VideoScreenState();
}
class _VideoScreenState extends State<VideoScreen> {
final _controller = PageController(initialPage: 0);
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _controller,
scrollDirection: Axis.vertical,
children: [
MyPost1(),
MyPost2(),
MyPost3(),
],
),
);
}
}
class MyPost1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return PostTemplate(
username: 'amandasharma',
videoDescription: 'Free your mind',
numberOfLikes: '1.2M',
numberOfComments: '1232',
numberOfShares: '122',
userPost: Container(
//color: Colors.deepPurple[300],
decoration: new BoxDecoration(
image: new DecorationImage(
image: new AssetImage("asset/images/girl.jpeg"),
fit: BoxFit.fill,
)
)
)
);
}
}
class MyPost2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return PostTemplate(
username: 'zuckerberg',
videoDescription: 'reels for days',
numberOfLikes: '1.2M',
numberOfComments: '232',
numberOfShares: '122',
userPost: Container(
decoration: new BoxDecoration(
image: new DecorationImage(
image: new AssetImage("asset/images/nature.jpg"),
fit: BoxFit.fill,
)
)
),
);
}
}
class MyPost3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return PostTemplate(
username: 'randomUser',
videoDescription: 'Free your mind',
numberOfLikes: '1.2B',
numberOfComments: '232',
numberOfShares: '122',
userPost: Container(
color: Colors.blue[300],
),
);
}
}
for MyPost3() you can wrap the containers in a column and use the Expanded widget.
class MyPost3 extends StatelessWidget {
const MyPost3({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return PostTemplate(
username: 'randomUser',
videoDescription: 'Free your mind',
numberOfLikes: '1.2B',
numberOfComments: '232',
numberOfShares: '122',
userPost: Column(
children: [
Expanded(
child: Container(
color: Colors.blue[300],
),
),
Expanded(
child: Container(
color: Colors.red[300],
),
),
],
),
);
}
}
I believe this is where you want the video widgets to appear ,
// user post (at the very back)
userPost,
You can put the widgets in a column and use Expandable to give them size, i.e flex 2 for each to split screen halfway.
Column -
- Expanded( flex:2, child:video1
- Expanded( flex:2, child:video2
On MyPost3():
return PostTemplate(
username: 'randomUser',
videoDescription: 'Free your mind',
numberOfLikes: '1.2B',
numberOfComments: '232',
numberOfShares: '122',
userPost: SafeArea(
child: Column(children: [
Expanded(
child: Container(
color: Colors.blue,
)),
Expanded(
child: Container(
color: Colors.red,
))
]),
));
You can use an Expanded() to take as much space you can, surrounded by a SafeArea() to ensure your screens don't overlap with your status bar leaving the possibility of an overflow.

Best way to define a bespoke Card in Flutter

I've been attempting to define a bespoke Card in Flutter using row and column and cannot seem to get a fixed format layout similar to the image above (the red lines denote the areas of the card and are just there to show the areas).
e.g.
return Card(child: Column(
children: [
Row(
children: [
Column( children: [
Text('Riverside cafe...'),
Ratingbar(),
],),
ImageWidget(),
],
),
Container(child: Text('Pubs & restaurants'), color : Colors.purple)
],
The resulting cards are to be displayed in a listview and using rows and columns above results in the areas being different sized depending on the data.
It seems to me that using row and column may not be the best way to achieve this. Is there a better way?
As for the best, I suppose that's for you and your client to decide.
For as long as I've been working with Flutter, I haven't come across anything like CSS grid which is great for situations like this. The closest comparison is StaggeredGrid (https://pub.dev/packages/flutter_staggered_grid_view) but that doesn't offer as much control as CSS grid and doesn't seem to quite fit your use case.
Rows, Columns (and other layout widgets) can get the job done:
Here's the main.dart that produced the above example. Code quality isn't perfect, but hopefully you can follow it well enough and it helps you get done what you need to get done.
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
static const String _title = 'Bespoke card example';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Bespoke card example')),
body: Center(
child: Wrap(runSpacing: 10.0, children: [
BespokeCard(title: 'Short name', width: 350),
BespokeCard(
title: 'Riverside Cafe with a really long name', width: 350)
]),
),
);
}
}
class BespokeCard extends StatelessWidget {
final String title;
final double width;
BespokeCard({this.title, this.width});
#override
Widget build(BuildContext context) {
Widget _restaurantNameContainer = Container(
constraints: BoxConstraints(
minHeight: 0,
maxHeight: 120,
maxWidth: (500.0 - 40 - 175 + 1),
minWidth: (500.0 - 40 - 175 + 1),
),
child: AutoSizeText(
title,
style: TextStyle(fontSize: 60),
maxLines: 2,
minFontSize: 10,
stepGranularity: 0.1,
overflow: TextOverflow.ellipsis,
),
);
Widget _rightSideSection = Container(
width: 175,
height: Size.infinite.height,
child: Center(
child: Icon(
Icons.umbrella,
size: 70,
),
),
);
Widget _topSection = Flexible(
flex: 1,
child: Row(
children: [
Flexible(
fit: FlexFit.tight,
flex: 3,
child: Padding(
padding: EdgeInsets.only(left: 40.0, top: 25.0),
child: Column(
children: [
Flexible(child: Container(), flex: 1),
_restaurantNameContainer,
Text('* * * * *', style: TextStyle(fontSize: 70)),
],
),
),
),
_rightSideSection
],
),
);
Widget _bottomSection = Container(
height: 70,
width: Size.infinite.width,
child: Center(
child: Text('Pubs & Restaurants',
style: TextStyle(color: Colors.white, fontSize: 40)),
),
color: Colors.purple);
Widget unfittedCard = Card(
child: SizedBox(
width: 500,
height: 300,
child: Column(
mainAxisSize: MainAxisSize.max,
children: [_topSection, _bottomSection],
),
));
return Container(
width: this.width,
child: FittedBox(fit: BoxFit.fitWidth, child: unfittedCard));
}
}
NOTES:
Be aware of flexFit (tight or loose) property: https://api.flutter.dev/flutter/widgets/Flexible/fit.html
You can either define fixed ratios with all flexibles, or you can mix Flexibles with Containers / SizedBoxes what have you
The package auto_size_text is great for situations like this. (Add auto_size_text: ^2.1.0 to your dependencies)
Be aware of box constraints. I needed them to make the title autosizing text be able to grow tall without also sitting in a large container.
Fitted box is really handy and makes scaling very easy in flutter.

Let wrap take full width

How can I make the red box to use full width and put the button to the very right of the screen when wrapping?
Also I want spaceBetween if the button is not wrapping, but it does not work:
This is what I have sofar:
Column(
children: [
Align(
alignment: Alignment.topLeft,
child: Container(
color: Colors.red,
child: Wrap(
alignment: WrapAlignment.end,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Please wrap on small screens",
style: Theme.of(context).textTheme.bodyText1,
),
),
),
TextButton(
onPressed: () {},
child: Text("Bearbeiten"))
],
),
),
),
// ...
]
)
Updated
Finally, I manage to solve your problem. Probably is not a canonic way, but it's working.
You need to compute the total width of your content in order to compare it with the width of the screen (the width is the width of the widgets, plus space you want between widgets, plus padding from both sides).
With that, you are able to know when you need to change the layout, and you can apply the layout you want to each case.
Here I leave the code:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final GlobalKey textKey = GlobalKey();
final GlobalKey buttonKey = GlobalKey();
double widthLimit = double.infinity;
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
final textBox = textKey.currentContext.findRenderObject() as RenderBox;
final buttonBox = buttonKey.currentContext.findRenderObject() as RenderBox;
widthLimit = textBox.size.width + buttonBox.size.width + 8 + 16 * 2;
});
}
Widget _wrap(bool spaceBetween, Widget child) => spaceBetween
? child
: Align(
alignment: Alignment.centerRight,
child: child,
);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Container(
width: double.infinity,
padding: EdgeInsets.all(16.0),
color: Colors.red,
child: Builder(builder: (context) {
final screenWidth = MediaQuery.of(context).size.width;
final spaceBetween = screenWidth >= widthLimit;
return Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
alignment: WrapAlignment.spaceBetween,
children: [
Text("Please wrap on small screen small screen", key: textKey),
_wrap(
spaceBetween,
TextButton(
key: buttonKey,
onPressed: () {},
child: Text("Bearbeiten"),
)),
],
);
}),
),
),
);
}
}
Old Answer
I am going to take a couple of premises from your screenshots:
The Wrap doesn't have to take the full width by default
You only want to go to two lines when there is not enough space in the device screen.
With that, you can make what you want with this code:
Container(
padding: EdgeInsets.all(16.0),
color: Colors.red,
child: Wrap(
spacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
alignment: WrapAlignment.end,
children: [
Text("Please wrap on small screen"),
TextButton(onPressed: () {}, child: Text("Bearbeiten")),
]),
),
I added the spacing property to the Wrap to provide space as you wanted when both widgets are at the same line. Also, I set the alignment of the Wrap to end to align as you want.
When there is not enough space on the screen, the widget is going to move to a new line and align to the end. As the first widget is longer than the wrapped one and, as I said in the premises, we don't need to take the full width of the screen, Wrap will adapt to keep things aligned properly.

Flutter DataTable cell text not wrapping inside of a row

I'm trying to put a dataTable in a row widget, but I lose the text wrapping in the individual cells when I do that.
This code works correctly:
class Testing extends StatefulWidget {
Testing({Key key}) : super(key: key);
#override
_MyDataTableState createState() => _MyDataTableState();
}
class _MyDataTableState extends State<Testing> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("DataTable"),
),
body: SingleChildScrollView( <-- Text wrapping goes away when wrapped in a Row()
padding: const EdgeInsets.all(8.0),
child: DataTable(
// columnSpacing: 100,
columns: [
DataColumn(
label: Container(
width: 100,
child: Text('Item Code'),
),
),
DataColumn(
label: Text('Stock Item'),
),
],
rows: [
DataRow(
cells: [
DataCell(
Text('Yup. text.'),
),
DataCell(
Text(
'This is a really long text. It\'s supposed to be long so that I can figure out what in the hell is happening to the ability to have the text wrap in this datacell. So far, I haven\'t been able to figure it out.'),
)
],
),
],
),
),
);
}
}
Is there a way to preserve the text wrapping inherent in a DataTable when wrapped in a row?
Wrap SingleChildScrollView with Expanded too.
class Testing extends StatefulWidget {
Testing({Key key}) : super(key: key);
#override
_MyDataTableState createState() => _MyDataTableState();
}
class _MyDataTableState extends State<Testing> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("DataTable"),
),
body: Row(
children: <Widget>[
//TODO: use Expanded here
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
child: DataTable(
// columnSpacing: 100,
columns: [
DataColumn(
label: Container(
width: 100,
child: Text('Item Code'),
),
),
DataColumn(
label: Text('Stock Item'),
),
],
rows: [
DataRow(
cells: [
DataCell(
Text('Yup. text.'),
),
DataCell(
Text(
'This is a really long text. It\'s supposed to be long so that I can figure out what in the hell is happening to the ability to have the text wrap in this datacell. So far, I haven\'t been able to figure it out.'),
)
],
),
],
),
),
),
],
),
);
}
}
I have been stuck on a similar problem since this morning.
For me, the DataColumn "label" Text wasn't wrapping. Although the text in the DataRow cell was wrapping just fine
After trying multiple variations, this is what worked for me.
Put the label text inside Expanded and set softWrap text property to true
DataTable(
columns: [
DataColumn(
label: Expanded(
child: Text(
"Very long text goes here",
softWrap: true,
),
)),
],
rows: [],
);
I guess it happens because of how Row naturally works. It takes as much as available horizontal axis. Although the mainAxisSize can be configured(min or max) according to the children, DataTable doesn't have initial width therefore resulting Text not being wrapped.
For quick fix you can wrap the Data Table in Container and give it a const width.
Quick test on DartPad: https://dartpad.dev/a664a29160b6e0443e9ca3bf28d5ec69
Snippet:
class Testing extends StatefulWidget {
Testing({Key key}) : super(key: key);
#override
_MyDataTableState createState() => _MyDataTableState();
}
class _MyDataTableState extends State<Testing> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("DataTable"),
),
body: Row(
children: [
Container(
width: 300, // Give the DataTable a const width.
child: DataTable(
columns: [
DataColumn(
label: Container(
width: 100,
child: Text('Item Code'),
),
),
DataColumn(
label: Text('Stock Item'),
),
],
rows: [
DataRow(
cells: [
DataCell(
Text('Yup. text.'),
),
DataCell(
Wrap(
children: [
Text(
'This is a really long text. It\'s supposed to be long so that I can figure out what in the hell is happening to the ability to have the text wrap in this datacell. So far, I haven\'t been able to figure it out.')
],
),
)
],
),
],
),
)
],
),
);
}
}