How to drag elements inside a zoomable content widget? - flutter

I'm trying to create an editable node diagram in a draggable/zoomable viewport (kind of like the node system in Blender). A user should be able to edit and drag the nodes.
I can't get the PhotoView (which I use as a viewport) to stay still when I actually want to drag a node within it. All of the PhotoView's children are moving when only one widget - the node - should do so.
I've tried placing boxes listening to pointer events (to make them draggable) inside a PhotoView, but somehow, anything outside a centered area the size of the screen doesn't receive any touches.
Minimal code so far:
Creating the PhotoViewController (inside a State):
double scale = 1;
PhotoViewController controller;
#override
void initState() {
super.initState();
controller = PhotoViewController()
..outputStateStream.listen(listener);
}
void listener(PhotoViewControllerValue value) {
setState(() {
// store the scale in a local variable to drag widgets in relation to the zoom
scale = value.scale;
});
}
Building the viewport (also part of the State):
Offset position = Offset(0, 0);
#override
Widget build(BuildContext context) {
return Center(
child: PhotoView.customChild(
child: Stack(
children: <Widget>[
// vertical line
Center(
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.black
),
child: Container(
width: 1,
height: 1000,
),
),
),
// horizontal line
Center(
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.black
),
child: Container(
width: 1000,
height: 1,
),
),
),
// box to debug the initial screen size
Center(
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.black12
),
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
),
),
),
// stack containing all nodes (only one in this case)
Stack(
children: <Widget>[
// build a node...
Center(
child: Transform.translate( // offset the node
offset: position,
child: Listener( // make it a listener
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.red
),
child: Container(
width: 130,
height: 100,
child: Column(
children: <Widget>[
Text("Node",
style: TextStyle(
color: Colors.white,
fontSize: 18.0
),
)
],
),
),
),
// make the node listen to touch movements and eventually change its position
onPointerMove: (event) =>
{
setState(() =>
{
position +=
event.delta.scale(1 / scale, 1 / scale)
})
},
)
)
),
]
),
],
),
childSize: Size(10000, 10000),
backgroundDecoration: BoxDecoration(color: Colors.white),
initialScale: 1.0,
controller: controller,
),
);
}
At the moment, I kind of fixed the unintentional viewport drag by toggling a bool (whenever a node is touched/released) and overwriting the PhotoView's value.

Switching from the photo_view package to pskink's matrix_gesture_detector solved my issues. Though I now follow a different system: for every node, a new matrix is created (deriving from a main matrix, translated by the node's position). Matrices may be used inside a Transform widget to transform its child.
I achieved building a translatable viewport in which there is a draggable node carrying a clickable checkbox.
Some working minimal code:
import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';
import 'package:vector_math/vector_math_64.dart' as vector;
...
// following code is executed inside a State
Matrix4 matrix = Matrix4.identity();
ValueNotifier<int> notifier = ValueNotifier(0);
vector.Vector3 nodePosition = vector.Vector3(50, 0, 0);
bool check = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Node Diagram Demo'),
),
body: LayoutBuilder(
builder: (ctx, constraints) {
return MatrixGestureDetector(
shouldRotate: false,
onMatrixUpdate: (m, tm, sm, rm) {
matrix = MatrixGestureDetector.compose(matrix, tm, sm, null);
notifier.value++;
},
child: Container(
width: double.infinity,
height: double.infinity,
alignment: Alignment.topLeft,
color: Color(0xff444444),
child: AnimatedBuilder(
animation: notifier,
builder: (ctx, child) {
return Container(
width: double.infinity,
height: double.infinity,
child: Stack( // a stack in which all nodes are built
children: <Widget>[
buildCenter(),
buildNode()
],
)
);
},
),
),
);
},
),
);
}
// I made this into a method so the transformed matrix can be calculated at runtime
Widget buildNode() {
// create a clone of the main matrix and translate it by the node's position
Matrix4 ma = matrix.clone();
ma.translate(nodePosition.x, nodePosition.y);
return Transform(
transform: ma, // transform the node using the new (translated) matrix
child: MatrixGestureDetector(
shouldRotate: false,
shouldScale: false,
onMatrixUpdate: (m, tm, sm, rm) {
Matrix4 change = tm;
// move the node (in relation to the viewport zoom) when it's being dragged
double sc = MatrixGestureDetector.decomposeToValues(matrix).scale;
nodePosition += change.getTranslation() / sc;
notifier.value++; // refresh view
},
// design a node holding a bool variable ('check')...
child: Container(
decoration: BoxDecoration(
color: Colors.blue
),
child: Container(
width: 200,
height: 100,
child: Column(
children: <Widget>[
Text("Node",
style: TextStyle(
color: Colors.white,
fontSize: 18.0
),
),
Checkbox(
onChanged: (v) =>
{
check = v,
notifier.value++ // refresh view
},
value: check,
)
],
),
)
)
)
);
}
// build two lines to indicate a matrix origin
Widget buildCenter() {
return Transform(
transform: matrix,
child: Stack(
children: <Widget>[
// vertical line
Center(
child: Container(
width: 1,
height: 250,
decoration: BoxDecoration(color: Colors.white)
)
),
// horizontal line
Center(
child: Container(
width: 250,
height: 1,
decoration: BoxDecoration(color: Colors.white)
)
),
],
)
);
}

Related

Changing the CameraPreview Aspect Ratio (Flutter)

I have an app where I have a Scaffold with an AppBar and a bottom Ads Banner.
In between, there is the CameraPreview from the camera plugin in Flutter.
As the CameraPreview is made to take the aspect ratio of the device/camera, the CameraPreview doesn't take the entire available space, leaving extra space on most devices.
I tried to crop the CameraPreview to show only whatever fits in the available space. It worked, but now the preview is stretched out
LayoutBuilder(
builder: (context, constraints) {
final cameraController = controller.cameraController!;
if(cameraController.value.previewSize != null) {
return ClipRect(
child: OverflowBox(
alignment: Alignment.center,
child: FittedBox(
fit: BoxFit.fitWidth,
child: SizedBox(
width: constraints.maxWidth,
height: constraints.maxWidth,
child: AspectRatio(
aspectRatio: cameraController.value.aspectRatio,
child: CameraPreview(cameraController),
),
),
),
),
);
} else {
return const SizedBox.shrink();
}
},
)
I tried other solutions like Transform.scale, but that only zooms into the preview, it doesn't change the ratio or the stretching.
Looking solutions in the package itself doesn't help either, most similar issues are stalling or already closed for stalling.
What am I supposed to do here? Am I supposed to manually clip the preview's value?
check this below code,
Use get screen size by MediaQuery & calculate scale for aspect ratio widget and add CameraPreview() to it like below
// get screen size
final size = MediaQuery.of(context).size;
// calculate scale for aspect ratio widget
var scale = cameraController.value.aspectRatio / size.aspectRatio;
// check if adjustments are needed...
if (cameraController.value.aspectRatio < size.aspectRatio) {
scale = 1 / scale;
}
return Transform.scale(
scale: scale,
child: Center(
child: AspectRatio(
aspectRatio: cameraController.value.aspectRatio,
child: CameraPreview(cameraController),
),
),
);
Complete code
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
if (controller != null && controller.value.isRecordingVideo) {
//stop video
}
},
child: Scaffold(
resizeToAvoidBottomInset: false,
key: _scaffoldKey,
body: Container(
child: Stack(
children: [
Positioned(
child: Container(
alignment: Alignment.center,
child: cameraScreen(),
),
),
Positioned(
child: Container(
alignment: Alignment.bottomCenter,
child: Container(
height: MediaQuery.of(context).size.height * .1,
color: Colors.black54,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
//can add Controls
],
),
),
),
)
],
),
),
),
);
}
Widget cameraScreen() {
final CameraController cameraController = controller;
if (cameraController == null || !cameraController.value.isInitialized) {
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.black,
child: Center(
child: Text(
"Loading Camera...",
style: CameraTextStyle.cameraUtilLoadingStyle(),
),
),
);
} else {
return cameraWidget(context, cameraController);
}
}
Widget cameraWidget(context, cameraController) {
// get screen size
final size = MediaQuery.of(context).size;
// calculate scale for aspect ratio widget
var scale = cameraController.value.aspectRatio / size.aspectRatio;
// check if adjustments are needed...
if (cameraController.value.aspectRatio < size.aspectRatio) {
scale = 1 / scale;
}
return Transform.scale(
scale: scale,
child: Center(
child: AspectRatio(
aspectRatio: cameraController.value.aspectRatio,
child: CameraPreview(cameraController),
),
),
);
}
Widget cameraSwitch() {
final CameraController cameraController = controller;
return Container(
child: InkWell(
onTap: () {
if (cameraController != null &&
cameraController.value.isInitialized &&
!cameraController.value.isRecordingVideo) {
if (cameras.isNotEmpty) {
if (selectedCamera == cameras[0]) {
selectedCamera = cameras[1];
onNewCameraSelected(selectedCamera);
} else {
selectedCamera = cameras[0];
onNewCameraSelected(selectedCamera);
}
}
}
setState(() {});
},
child: Icon(
Icons.switch_camera,
size: 30,
color: Colors.white,
),
),
);
}

How to programmatically change Z-Index of widget Stack in Flutter

As you can see in this Stack the yellow cube is at the bellow of a purple cube.
when I click, I want to change the index of the yellow cube to transform it from index 0 to 1 and the purple cube from index 1 to 0, vice versa.
I tried IndexedStack but it's only showing a single child from a list of children.
class _FlipIndex extends State<FlipIndex> {
int currentIndex = 0;
#override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: (){
// Change Z-Index of widget
},
child: Stack(
alignment: Alignment.center,
children: [
Transform.translate(
offset: Offset(-30.0, 0.0),
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.yellow,
shape: BoxShape.rectangle,
),
),
),
Transform.translate(
offset: Offset(30.0, 0.0),
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.purple,
shape: BoxShape.rectangle,
),
),
),
],
),
),
);
}
}
Try this package https://pub.dev/packages/indexed
Example image:
This package allows you to order items inside the stack using index like z-index in CSS.
Easily you can change the order of items by change the index property
This is an example of how it works
Indexer(
children: [
Indexed(
index: 100,
child: Positioned(
//...
)
),
Indexed(
index: 1000,
child: Positioned(
//...
)
),
Indexed(
index: 3,
child: Positioned(
//...
)
),
],
);
if you are using bloc of some complex widget you can extands or implement the IndexedInterface class and override index getter:
class IndexedDemo extends IndexedInterface {
int index = 5;
}
or implements
class IndexedDemo extends AnimatedWidget implements IndexedInterface {
int index = 1000;
//...
//...
}
then use it just like Indexed class widget:
Indexer(
children: [
IndexedDemo(
index: 100,
child: Positioned(
//...
)
),
IndexedFoo(
index: 1000,
child: Positioned(
//...
)
),
],
);
Online demo
Video demo
try this:
class _FlipIndex extends State<FlipIndex> {
List<Widget> _stackChildren = [];
int currentIndex = 0;
#override
void initState() {
super.initState();
_stackChildren.add(_stackChild(Colors.yellow, 30));
_stackChildren.add(_stackChild(Colors.green, -30));
}
//call this function for swapping items
void _swapOrder() {
Widget _first = _stackChildren[0];
_stackChildren.removeAt(0);
_stackChildren.add(_first);
setState(() {});
}
Widget _stackChild(Color childColor, double xOffset) {
return Transform.translate(
key: UniqueKey(),
offset: Offset(xOffset, 0.0),
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: childColor,
shape: BoxShape.rectangle,
),
),
);
}
#override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: () {
_swapOrder();
},
child: Stack(
alignment: Alignment.center,
children: _stackChildren,
),
),
);
}
}

How to create a widget can swipe up/down/left/right in flutter?

How to create a card can swipe up/down/left/right in flutter?
I see can use PageView but it is just for one orientation up-down or left-right.
So how to combine all direction to detect swipe a Wigdet in flutter?
Thanks!
You can use 'PageView' as child of another 'PageView':
class _TrainigState extends State<TrainingPage> {
PageController hPagerController = PageController(keepPage: true);
PageController vPagerController = PageController(keepPage: true);
double mWidth;
double mHeight;
#override
Widget build(BuildContext context) {
mWidth = MediaQuery.of(context).size.width;
mHeight = MediaQuery.of(context).size.height;
return PageView(
controller: hPagerController,
children: [
_verticalPageView([Colors.blue, Colors.purpleAccent, Colors.pinkAccent, Colors.orangeAccent]),
_verticalPageView([Colors.yellow, Colors.orange, Colors.deepOrange, Colors.red]),
_verticalPageView([Colors.green, Colors.lightGreenAccent, Colors.greenAccent, Colors.lightBlueAccent]),
],
);
}
Widget _verticalPageView(List colors) {
return PageView(
controller: vPagerController,
scrollDirection: Axis.vertical,
children: [
Container(
width: mWidth,
height: mHeight,
color: colors[0],
),
Container(
width: mWidth,
height: mHeight,
color: colors[1],
),
Container(
width: mWidth,
height: mHeight,
color: colors[2],
),
Container(
width: mWidth,
height: mHeight,
color: colors[3],
),
],
);
}
}
I hope it is useful for you.

Flutter overflowed positioned Button is not clickable

I have a stack widget parenting a Positioned widget like this:
Stack(
overflow: Overflow.visible,
children: [
Container(
width: 150,
height: 150,
),
Positioned(
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('FAB tapped!');
},
backgroundColor: Colors.blueGrey,
),
right: 0,
left: 0,
bottom: -26,
),
],
),
That part of the fab which is placed outside the container is not clickable, what is the solution?
and here is a screenshot:
try this :
Stack(
overflow: Overflow.visible,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>
[
Container(width: 150, height: 150, color: Colors.yellow),
Container(width: 150, height: 28, color: Colors.transparent),
],
),
Positioned(
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('FAB tapped!');
},
backgroundColor: Colors.blueGrey,
),
right: 0,
left: 0,
bottom: 0,
),
],
)
you should keep button inside of stack if you want it to stay clickable
Providing an updated answer since overflow specification is deprecated after v1.22.0-12.0.pre. clipBehavior is the replacing property:
Stack(
clipBehavior: Clip.none,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>
[
Container(width: 150, height: 150, color: Colors.yellow),
Container(width: 150, height: 28, color: Colors.transparent),
],
),
Positioned(
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('FAB tapped!');
},
backgroundColor: Colors.blueGrey,
),
right: 0,
left: 0,
bottom: 0,
),
],
)
Note: credits to #Amir's answer
The problem is when a child overflows on Stack that has Clip.none behavior, the part that is outside of Stack would not be recognized to be clicked.
Solution :
Wrap the Stack with Column and add the space you want to be outside of Stack :
final _clipSpace = 30;
Stack(
clipBehavior: Clip.none,
children: [
Column(
children: [
DecoratedBox(
decoration: const BoxDecoration(// decorate the box //
),
child: Column(
children: [
// column's children
],
)
],
),
),
// clip space
const SizedBox(height: _clipSpace,)
],
),
const Positioned(
child: _ActionButton(),
left: 0,
right: 0,
bottom: 0,
),
],
);
Container(
width: 150,
height: 180,
child: Stack(
children: [
Container(
width: double.infinity,
height: 150,
child: Image.asset('assets/images/image.jpg', fit: BoxFit.cover,)
),
Container(
alignment: Alignment.bottomCenter,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('FAB tapped!');
},
backgroundColor: Colors.blueGrey,
),
),
],
),
),
Fab button is not clickable because it renders outside of stack as you have given -ve bottom, Ideally, you should have parent container and inside it has all stack widget you should render it.
Here I have used hardcoded values, but you should use media query as per your requirement
Like:
Container(
width: MediaQuery.of(context).size.width * 0.3,
height: MediaQuery.of(context).size.height * 0.3,
child: Stack(
children: [
Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * 0.26,
child: Image.asset('assets/images/jitesh.jpg', fit: BoxFit.cover,)
),
Container(
alignment: Alignment.bottomCenter,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('FAB tapped!');
},
backgroundColor: Colors.blueGrey,
),
),
],
),
),
up until now, there is now solution from Flutter, I should make a simple trick to solve this issue, I need to make a layout like this
the workaround is by adding a SizedBox below your background widget, the height of the SizedBox should be the same as the height of the overlaping widget.
like this
Stack(
clipBehavior: Clip.none,
children: [
Column( // wrap the background in a column
children: [
const _HeaderBackground(),
SizedBox(height: 100), // add the SizedBox with height = 100.0
],
),
Positioned(
bottom: 16,
left: 4,
right: 4,
child: _ReferralCodeSection(customer), // the height of this widget is 100
),
],
),
You have to put the button in the last place of the Stack's children
Stack(children: [...., buttonWidget ])
Flutter does not officially plan to solve this problem, so we can only use some hacking methods.
Here is my resolution with an example, you can use the following OverflowWithHitTest Widget directlly:
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
/// Creates a widget that can check its' overflow children's hitTest
///
/// [overflowKeys] is must, and there should be used on overflow widget's outermost widget those' sizes cover the overflow child, because it will [hitTest] its' children, but not [hitTest] its' parents. And i cannot found a way to check RenderBox's parent in flutter.
///
/// The [OverflowWithHitTest]'s size must contains the overflow widgets, so you can use it as outer as possible.
///
/// This will not reduce rendering performance, because it only overcheck the given widgets marked by [overflowKeys].
///
/// Demo:
///
/// class _MyPageStore extends State<MyPage> {
///
/// var overflowKeys = <GlobalKey>[GlobalKey()];
///
/// Widget build(BuildContext context) {
/// return Scaffold(
/// body: OverflowWithHitTest(
///
/// overflowKeys: overflowKeys,
///
/// child: Container(
/// height: 50,
/// child: UnconstrainedBox(
/// child: Container(
/// width: 200,
/// height: 50,
/// color: Colors.red,
/// child: OverflowBox(
/// alignment: Alignment.topLeft,
/// minWidth: 100,
/// maxWidth: 200,
/// minHeight: 100,
/// maxHeight: 200,
/// child: GestureDetector(
/// key: overflowKeys[0],
/// behavior: HitTestBehavior.translucent,
/// onTap: () {
/// print('==== onTap;');
/// },
/// child: Container(
/// color: Colors.blue,
/// height: 200,
/// child: Text('aaaa'),
/// ),
/// ),
/// ),
/// ),
/// ),
/// ),
/// ),
/// );
/// }
/// }
///
///
class OverflowWithHitTest extends SingleChildRenderObjectWidget {
const OverflowWithHitTest({
required this.overflowKeys,
Widget? child,
Key? key,
}) : super(key: key, child: child);
final List<GlobalKey> overflowKeys;
#override
_OverflowWithHitTestBox createRenderObject(BuildContext context) {
return _OverflowWithHitTestBox(overflowKeys: overflowKeys);
}
#override
void updateRenderObject(
BuildContext context, _OverflowWithHitTestBox renderObject) {
renderObject.overflowKeys = overflowKeys;
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
DiagnosticsProperty<List<GlobalKey>>('overflowKeys', overflowKeys));
}
}
class _OverflowWithHitTestBox extends RenderProxyBoxWithHitTestBehavior {
_OverflowWithHitTestBox({required List<GlobalKey> overflowKeys})
: _overflowKeys = overflowKeys,
super(behavior: HitTestBehavior.translucent);
/// Global keys of overflow children
List<GlobalKey> get overflowKeys => _overflowKeys;
List<GlobalKey> _overflowKeys;
set overflowKeys(List<GlobalKey> value) {
var changed = false;
if (value.length != _overflowKeys.length) {
changed = true;
} else {
for (var ind = 0; ind < value.length; ind++) {
if (value[ind] != _overflowKeys[ind]) {
changed = true;
}
}
}
if (!changed) {
return;
}
_overflowKeys = value;
markNeedsPaint();
}
#override
bool hitTest(BoxHitTestResult result, {required Offset position}) {
if (hitTestOverflowChildren(result, position: position)) {
result.add(BoxHitTestEntry(this, position));
return true;
}
bool hitTarget = false;
if (size.contains(position)) {
hitTarget =
hitTestChildren(result, position: position) || hitTestSelf(position);
if (hitTarget || behavior == HitTestBehavior.translucent)
result.add(BoxHitTestEntry(this, position));
}
return hitTarget;
}
bool hitTestOverflowChildren(BoxHitTestResult result,
{required Offset position}) {
if (overflowKeys.length == 0) {
return false;
}
var hitGlobalPosition = this.localToGlobal(position);
for (var child in overflowKeys) {
if (child.currentContext == null) {
continue;
}
var renderObj = child.currentContext!.findRenderObject();
if (renderObj == null || renderObj is! RenderBox) {
continue;
}
var localPosition = renderObj.globalToLocal(hitGlobalPosition);
if (renderObj.hitTest(result, position: localPosition)) {
return true;
}
}
return false;
}
}

Flutter: How to create bidirectional scrolling ListView with fixed portion of on left and bottom

How can I make a scrolling view in Flutter in which a left and bottom portion of the screen is fixed (axes), then the rest of the screen can be scrolled horizontally to the right, or vertically upward. (imagine scrolling a graph with two axes .. see image)
Very interesting question. After looking through the docs I couldn't find a widget that would fit this scenario. So I decided to search a bit on pub.dev for a plugin that could make this happen.
Found it: https://pub.dev/packages/bidirectional_scroll_view
The plugin does a fairly good job of scrolling content on both axis, but to get what you are looking for ("fixed portion of on left and bottom") you are gonna have to structure your page accordingly. I decided to go with Stack and Align widgets, here is what it looks like:
See the full code on a working DartPad: https://dartpad.dev/10573c0e9bfa7f1f8212326b795d8628
Or take a look at the code bellow (don't forget to include bidirectional_scroll_view in your project):
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
BidirectionalScrollViewPlugin _plugin;
double fixedAxisSpace = 100.0;
double biDirectContentWidth = 4096.0;
double biDirectContentHeight = 4096.0;
#override
void initState() {
super.initState();
_plugin = new BidirectionalScrollViewPlugin(
child: _buildWidgets(),
velocityFactor: 0.0,
);
}
void _snapToZeroZero(BuildContext context){
double yOffset = biDirectContentHeight + fixedAxisSpace - context.size.height;
_plugin.offset = new Offset(0, yOffset);
}
#override
Widget build(BuildContext context) {
final btnSnapToZeroZero = Padding(
padding: EdgeInsets.all(10.0),
child:FlatButton(
color: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(12.0),
),
onPressed: () { _snapToZeroZero(context); },
child: Text(
"Snap to 0.0",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
)
);
return new MaterialApp(
debugShowCheckedModeBanner: false,
home: new Scaffold(
body: Stack(
children: <Widget>[
_plugin, // BidirectionalScrollViewPlugin, goes 1st because we want it to sit on the bottom layer
Align( // Y Axis goes over _plugin, it is aligned to topLeft
alignment: Alignment.topLeft,
child: Column(
children: <Widget> [
Expanded(
child: Container(
width: fixedAxisSpace,
decoration: BoxDecoration(
color: Colors.white, // change to Colors.white70 too se what is going on "behind the scene"
border: Border(
right: BorderSide(width: 1.0, color: Colors.black),
),
),
child: Center(child: VerticalTextWidget("FIXED _ Y AXIS", 22))
),
),
SizedBox(height: fixedAxisSpace),
]
),
),
Align( // X Axis goes over _plugin and Y Axis, it is aligned to bottomLeft
alignment: Alignment.bottomLeft,
child: Row(
children: <Widget> [
SizedBox(width: fixedAxisSpace),
Expanded(
child: Container(
height: fixedAxisSpace,
decoration: BoxDecoration(
color: Colors.white, // change to Colors.white70 too se what is going on "behind the scene"
border: Border(
top: BorderSide(width: 1.0, color: Colors.black),
),
),
child: Center(child: Text("FIXED | X AXIS", style: TextStyle(fontSize: 22)))
),
),
]
),
),
Align( // this little square is optional, I use it to put a handy little button over everything else at the bottom left corner.
alignment: Alignment.bottomLeft,
child: Container(
color: Colors.white, // change to Colors.white70 too se what is going on "behind the scene"
height: fixedAxisSpace,
width: fixedAxisSpace,
child: btnSnapToZeroZero
),
),
],
)
)
);
}
// put your large bidirectional content here
Widget _buildWidgets() {
return new Padding(
padding: EdgeInsets.fromLTRB(100, 0, 0, 100),
child: SizedBox(
width: biDirectContentWidth,
height: biDirectContentHeight,
child: Image.network(
'https://i.stack.imgur.com/j1ItQ.png?s=328&g=1',
repeat: ImageRepeat.repeat,
alignment: Alignment.bottomLeft
),
)
);
}
}
VerticalTextWidget:
class VerticalTextWidget extends StatelessWidget {
final String text;
final double size;
const VerticalTextWidget(this.text, this.size);
#override
Widget build(BuildContext context) {
return Wrap(
direction: Axis.vertical,
alignment: WrapAlignment.center,
children: text.split("").map((string) => Text(string, style: TextStyle(fontSize: size))).toList(),
);
}
}