I'm trying to take square pictures in my app. I'm using the camera package and I'm trying to display a centre square-cropped version of the CameraPreview widget.
My goal is to show the central square of the preview (full width), with an even amount cropped from the top and bottom.
I was struggling to get this to work, so I created a minimal example using a fixed image. (Apologies for the dull picture of me in a chair):
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Example',
theme: ThemeData(),
home: Scaffold(
body: Example(),
),
);
}
}
class Example extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
CroppedCameraPreview(),
// Something to occupy the rest of the space
Expanded(
child: Container(),
)
],
);
}
}
class CroppedCameraPreview extends StatelessWidget {
#override
Widget build(BuildContext context) {
// We will pretend this is a camera preview (to make demo easier)
var cameraImage = Image.network("https://i.imgur.com/gZfg4jm.jpg");
var aspectRatio = 1280 / 720;
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width,
child: ClipRect(
child: new OverflowBox(
alignment: Alignment.center,
child: FittedBox(
fit: BoxFit.fitWidth,
child: cameraImage,
),
),
),
);
}
}
This works fine - I get a full screen width image, centre cropped and pushed to the top of my app.
However, if I drop this code into my existing app and replace cameraImage with a CameraPreview, I get a lot of layout errors:
flutter: ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
flutter: The following assertion was thrown during performResize():
flutter: TextureBox object was given an infinite size during layout.
flutter: This probably means that it is a render object that tries to be as big as possible, but it was put
flutter: inside another render object that allows its children to pick their own size.
flutter: The nearest ancestor providing an unbounded width constraint is:
flutter: RenderFittedBox#0bd54 NEEDS-LAYOUT NEEDS-PAINT
flutter: creator: FittedBox ← OverflowBox ← ClipRect ← ConstrainedBox ← Container ← Stack ← ConstrainedBox
flutter: ← Container ← CameraWidget ← Column ← CameraPage ← MediaQuery ← ⋯
flutter: parentData: offset=Offset(0.0, 0.0) (can use size)
flutter: constraints: BoxConstraints(w=320.0, h=320.0)
flutter: size: MISSING
flutter: fit: fitWidth
flutter: alignment: center
flutter: textDirection: ltr
flutter: The nearest ancestor providing an unbounded height constraint is:
flutter: RenderFittedBox#0bd54 NEEDS-LAYOUT NEEDS-PAINT
flutter: creator: FittedBox ← OverflowBox ← ClipRect ← ConstrainedBox ← Container ← Stack ← ConstrainedBox
flutter: ← Container ← CameraWidget ← Column ← CameraPage ← MediaQuery ← ⋯
flutter: parentData: offset=Offset(0.0, 0.0) (can use size)
flutter: constraints: BoxConstraints(w=320.0, h=320.0)
flutter: size: MISSING
flutter: fit: fitWidth
flutter: alignment: center
flutter: textDirection: ltr
flutter: The constraints that applied to the TextureBox were:
flutter: BoxConstraints(unconstrained)
flutter: The exact size it was given was:
flutter: Size(Infinity, Infinity)
flutter: See https://flutter.io/layout/ for more information.
Can anyone suggest why I'm getting errors with the preview and how to avoid them?
I solved this by giving a specific size to my CameraPreview instance, by wrapping it in a Container:
var size = MediaQuery.of(context).size.width;
// ...
Container(
width: size,
height: size,
child: ClipRect(
child: OverflowBox(
alignment: Alignment.center,
child: FittedBox(
fit: BoxFit.fitWidth,
child: Container(
width: size,
height:
size / widget.cameraController.value.aspectRatio,
child: camera, // this is my CameraPreview
),
),
),
),
);
To respond to Luke's comment, I then used this code to square crop the resulting image. (Because even though the preview is square, the image captured is still standard ratio).
Future<String> _resizePhoto(String filePath) async {
ImageProperties properties =
await FlutterNativeImage.getImageProperties(filePath);
int width = properties.width;
var offset = (properties.height - properties.width) / 2;
File croppedFile = await FlutterNativeImage.cropImage(
filePath, 0, offset.round(), width, width);
return croppedFile.path;
}
This uses https://github.com/btastic/flutter_native_image. It's been a while since I used this code - think it currently just works for portrait images, but should easily be extendable to handle landscape.
I have a code snippet similar to the one used in the answer.
Same as the answer, it supports cases when aspect ratio of the camera is different from aspect ratio of the screen.
Though my version has some difference: it does not require MediaQuery to get the device size, so it will fit the width of any parent (not just full-screen-width)
....
return AspectRatio(
aspectRatio: 1,
child: ClipRect(
child: Transform.scale(
scale: 1 / _cameraController.value.aspectRatio,
child: Center(
child: AspectRatio(
aspectRatio: _cameraController.value.aspectRatio,
child: CameraPreview(_cameraController),
),
),
),
),
);
To center-square-crop the image, see the snippet below.
It works equally fine with images in portrait and landscape orientation. It also allows to optionally mirror the image (it can be useful if you want to retain original mirrored look from selfie camera)
import 'dart:io';
import 'dart:math';
import 'package:flutter/rendering.dart';
import 'package:image/image.dart' as IMG;
class ImageProcessor {
static Future cropSquare(String srcFilePath, String destFilePath, bool flip) async {
var bytes = await File(srcFilePath).readAsBytes();
IMG.Image src = IMG.decodeImage(bytes);
var cropSize = min(src.width, src.height);
int offsetX = (src.width - min(src.width, src.height)) ~/ 2;
int offsetY = (src.height - min(src.width, src.height)) ~/ 2;
IMG.Image destImage =
IMG.copyCrop(src, offsetX, offsetY, cropSize, cropSize);
if (flip) {
destImage = IMG.flipVertical(destImage);
}
var jpg = IMG.encodeJpg(destImage);
await File(destFilePath).writeAsBytes(jpg);
}
}
This code requires image package. Add it into pubspec.yaml:
dependencies:
image: ^2.1.4
After some testing here is what I suggest for cropping the CameraPreview (not the image itself) to any rectangular shape:
Widget buildCameraPreview(CameraController cameraController) {
final double previewAspectRatio = 0.7;
return AspectRatio(
aspectRatio: 1 / previewAspectRatio,
child: ClipRect(
child: Transform.scale(
scale: cameraController.value.aspectRatio / previewAspectRatio,
child: Center(
child: CameraPreview(cameraController),
),
),
),
);
}
The advantage are similar to #VeganHunter's solution but can be extended to any rectangular shape by playing with the previewAspectRatio.
A value of 1 for previewAspectRatio will result in a square shape, a value of 0.5 will be a rectangle half the height of its width, and a value of cameraController.value.aspectRatio will be the full size preview.
Also, here is the explanation why this work at all:
One thing with Flutter is that making a child overflow its parent is usually impossible (because of the way the widgets size is computed during the layout phase). The use of Transform.scale is crucial here, because according to the doc:
This object applies its transformation just prior to painting, which means the transformation is not taken into account when calculating how much space this widget's child (and thus this widget) consumes.
This means the widget will be able to overflow it's container when scaled up, and we can clip (and thus hide) the overflowing parts with CLipRect, limiting the overall size of the preview to its parent size. It would not be possible to achieve the same effect using only Containers, as they would scale themselves based on the available space during the layout phase, and there would be no overflow so nothing to clip.
The scale (cameraController.value.aspectRatio * previewAspectRatio) is chosen to have the width of the preview match the width of its parent.
If this solution does not work for you try to replace all the cameraController.value.aspectRatio by its inverse (1 / cameraController.value.aspectRatio).
This was tested for portraitUp mode only and some tweaking may be needed for Landscape.
Crop Camera Preview using this
screenWidth = MediaQuery.of(context).size.width;
resolutionRatio = 1; // Change this value for custom crop
...
SizedBox(
width: screenWidth,
height: screenWidth * resolutionRatio,
child: SingleChildScrollView(
controller: ScrollController(
initialScrollOffset: (controller!.value.previewSize!.height / 2) - (controller!.value.previewSize!.width * resolutionRatio / 2),
),
physics: const NeverScrollableScrollPhysics(),
child: AspectRatio(
aspectRatio: 1 / controller!.value.aspectRatio,
child: CameraPreview(controller!),
),
),
)
This is how I solved.
SizedBox(
width: width,
height: height,
child: SingleChildScrollView(
child: AspectRatio(
aspectRatio: _cameraController.value.aspectRatio,
child: CameraPreview(_cameraController),
),
),
)
Well, I wanted something more generic where we can put the camera in any container and it will use the BoxFit object to define how to Crop/Position/Fit on the screen.
So, instead of using the CameraPreview widget, I'm using the buildPreview from the CameraController that doesn't have an embedded AspectRatio.
LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
return Container(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: FittedBox(
fit: BoxFit.cover,
child: SizedBox(
width: _cameraController!.value.deviceOrientation == DeviceOrientation.portraitUp
? _cameraController!.value.previewSize!.height
: _cameraController!.value.previewSize!.width,
height: _cameraController!.value.deviceOrientation == DeviceOrientation.portraitUp
? _cameraController!.value.previewSize!.width
: _cameraController!.value.previewSize!.height,
child: _cameraController!.buildPreview())),
);
}),
Explanation:
The main idea is to get the "parent" size using LayoutBuilder + Container with max constraints (it might be optional depending on where you are using it). So, we have a container using the exact size that we have.
Then, inside the container, we use a FittedBox with the BoxFit we want, it will Crop/Resize/Fit its child accordingly. Then, the child, is the exact size of the camera preview component, so the FittedBox will be able to do his work correctly. With the CameraPreview widget, it has an AspectRatio, that confuses the FittedBox.
mix of some solutions, without ration or zoom problems:
var tmp = MediaQuery.of(context).size;
final screenH = max(tmp.height, tmp.width);
final screenW = min(tmp.height, tmp.width);
tmp = cameraController.value.previewSize!;
final previewH = max(tmp.height, tmp.width);
final previewW = min(tmp.height, tmp.width);
final screenRatio = screenH / screenW;
final previewRatio = previewH / previewW;
return Center(
child: Container(
width: screenW,
height: screenW,
color: Colors.black,
child: ClipRRect(
child: OverflowBox(
maxHeight: screenRatio > previewRatio
? screenH
: screenW / previewW * previewH,
maxWidth: screenRatio > previewRatio
? screenH / previewH * previewW
: screenW,
child: CameraPreview(
cameraController,
),
),
),
),
);
Easy cropping method
from memory:
Container(
width: 40.0,
height: 40.0,
decoration: BoxDecoration(
image: DecorationImage(
image: MemoryImage(photo),
fit: BoxFit.cover,
),
),
)
from asset:
Container(
width: 40.0,
height: 40.0,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/photo.jpg'),
fit: BoxFit.cover,
),
),
)
Related
I am new to flutter and stackOverFlow. I would be highly grateful for your help, or even a slightest idea.
Question 1st:
You can see in the pictures attached, that first two cards have successfully loaded the rating (in green background). But in the 3rd and 4th card, it shows "444.1111". It is the default value, i gave statically. Why sometimes http works and sometimes fail to load data from net ?
I am using HTTP flutter packge, and using get data using this function
static var client = my_http_alias.Client() ;
static Future<List<Welcome>> fetchProducts() async {
var response = await my_http_alias.get(Uri.parse('https://makeup-api.herokuapp.com/api/v1/products.json?brand=maybelline'),);
if (response.statusCode == 200){
var jsonString = response.body;
return welcomeFromJson ( jsonString ) ;
}
else{
//error.
return welcomeFromJson('name: Some thing Went Wrong' );
}
}
=======================
I want my images to be displayed full, but when I try to do so (by wrapping with Expanded), it overflows the other widgets. I have tried many things, and came here in hope of finding answers.
The error code is:
Performing hot reload...
Syncing files to device sdk gphone64 x86 64...
======== Exception caught by rendering library =====================================================
The following assertion was thrown during layout:
A RenderFlex overflowed by 55 pixels on the bottom.
The relevant error-causing widget was:
Column Column:file:///C:/Users/qurat/StudioProjects/getx_5_rest_api/lib/views/product_tile.dart:45:16
The overflowing RenderFlex has an orientation of Axis.vertical.
The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and black striped pattern. This is usually caused by the contents being too big for the RenderFlex.
Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the RenderFlex to fit within the available space instead of being sized to their natural size.
This is considered an error condition because it indicates that there is content that cannot be seen. If the content is legitimately bigger than the available space, consider clipping it with a ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex, like a ListView.
The specific RenderFlex in question is: RenderFlex#279b1 OVERFLOWING
... parentData: offset=Offset(8.0, 8.0) (can use size)
... constraints: BoxConstraints(w=152.4, h=152.4)
... size: Size(152.4, 152.4)
... direction: vertical
... mainAxisAlignment: start
... mainAxisSize: max
... crossAxisAlignment: start
... textDirection: ltr
... verticalDirection: down
◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤
====================================================================================================
======== Exception caught by rendering library =====================================================
The following assertion was thrown during layout:
A RenderFlex overflowed by 55 pixels on the bottom.
The relevant error-causing widget was:
Column Column:file:///C:/Users/qurat/StudioProjects/getx_5_rest_api/lib/views/product_tile.dart:45:16
The overflowing RenderFlex has an orientation of Axis.vertical.
The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and black striped pattern. This is usually caused by the contents being too big for the RenderFlex.
Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the RenderFlex to fit within the available space instead of being sized to their natural size.
This is considered an error condition because it indicates that there is content that cannot be seen. If the content is legitimately bigger than the available space, consider clipping it with a ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex, like a ListView.
The specific RenderFlex in question is: RenderFlex#522a9 OVERFLOWING
... parentData: offset=Offset(8.0, 8.0) (can use size)
... constraints: BoxConstraints(w=152.4, h=152.4)
... size: Size(152.4, 152.4)
... direction: vertical
... mainAxisAlignment: start
... mainAxisSize: max
... crossAxisAlignment: start
... textDirection: ltr
... verticalDirection: down
◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤
====================================================================================================
======== Exception caught by rendering library =====================================================
The following assertion was thrown during layout:
A RenderFlex overflowed by 55 pixels on the bottom.
The relevant error-causing widget was:
Column Column:file:///C:/Users/qurat/StudioProjects/getx_5_rest_api/lib/views/product_tile.dart:45:16
The overflowing RenderFlex has an orientation of Axis.vertical.
The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and black striped pattern. This is usually caused by the contents being too big for the RenderFlex.
Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the RenderFlex to fit within the available space instead of being sized to their natural size.
This is considered an error condition because it indicates that there is content that cannot be seen. If the content is legitimately bigger than the available space, consider clipping it with a ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex, like a ListView.
The specific RenderFlex in question is: RenderFlex#064bd OVERFLOWING
... parentData: offset=Offset(8.0, 8.0) (can use size)
... constraints: BoxConstraints(w=152.4, h=152.4)
... size: Size(152.4, 152.4)
... direction: vertical
... mainAxisAlignment: start
... mainAxisSize: max
... crossAxisAlignment: start
... textDirection: ltr
... verticalDirection: down
◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤
====================================================================================================
Reloaded 1 of 758 libraries in 1,709ms.
D/EGL_emulation( 5457): app_time_stats: avg=120567.27ms min=120567.27ms max=120567.27ms count=1
My full code is:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:getx_5_rest_api/models/product.dart';
import 'package:getx_5_rest_api/models/product.dart';
import 'package:getx_5_rest_api/models/product.dart';
import 'package:getx_5_rest_api/models/product.dart';
import 'package:getx_5_rest_api/models/product.dart';
import 'package:getx_5_rest_api/models/product.dart';
bool isFavorite = false;
void invertFav() {
if (isFavorite == false) {
isFavorite = true;
} else {
if (isFavorite == true) {
isFavorite = false;
}
}
}
// final Welcome welcomeObj = Welcome(name: 'd-default', price: 'd-default', imageLink: 'd-default', rating: 9000.9000 );
class ProductTile extends StatefulWidget {
final Welcome welcomeObj;
const ProductTile(this.welcomeObj);
#override
State<ProductTile> createState() => _ProductTileState();
}
class _ProductTileState extends State<ProductTile> {
#override
Widget build(BuildContext context) {
return Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Stack(
children: [
Container(
// height: 180,
width: double.infinity,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
),
child: ClipRect(
child: Image.asset(
'assets/image.png',
fit: BoxFit.cover,
),
),
),
Positioned(
right: 0,
child: Obx(() => CircleAvatar(
backgroundColor: Colors.white,
child: IconButton(
icon: widget.welcomeObj.isFavorite.value
? Icon(Icons.favorite_rounded)
: Icon(Icons.favorite_border),
onPressed: () {
widget.welcomeObj.isFavorite.toggle();
},
),
)),
)
],
),
),
SizedBox(height: 8),
Expanded(
child: Text(
widget.welcomeObj.name,
maxLines: 2,
style:
TextStyle(fontFamily: 'avenir', fontWeight: FontWeight.w800),
overflow: TextOverflow.ellipsis,
),
),
SizedBox(height: 8),
if (widget.welcomeObj.rating != null)
Align(
alignment: Alignment(-1,0),
child: Container(
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 4, ),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
widget.welcomeObj.rating.toString(),
style: TextStyle(color: Colors.white, fontSize: 12),
),
Icon(
Icons.star,
size: 12,
color: Colors.white,
),
],
),
),
),
Expanded(
child: Text('\$${widget.welcomeObj.price}',
style: TextStyle(fontSize: 21, fontFamily: 'avenir')),
),
],
),
),
);
}
}
I am new to flutter I am stuck I want images sliding images after ontap ! like our gallery app...
but the thing is images slide from privious array...suppose 1st screen I have 10 images list I opened 6 image to next screen to show full screen and when I slide on that page I want after that image I want to see 7 no or 5 no images...hope u get my point
body: Stack(
children: <Widget>[
Container(
child:
Center(
child: Swiper(
autoplay: false,
itemBuilder: (BuildContext context, int index) {
return new PhotoView(
imageProvider: NetworkImage(widget.Backgroundimg,),
minScale: PhotoViewComputedScale.contained * 0.8,
maxScale: PhotoViewComputedScale.covered * 1.5,
);
},
itemCount: 10,
viewportFraction: 1,
scale: 0.9,
),
),
),
here is my body I use json for image listing......After that sending images through widget please help me for sliding array thank you
I would recommend to use Carousel Slider as it will also provide many options and suitable for gallery app.
https://pub.dev/packages/carousel_slider
add the below dependency in pubsec.yaml
carousel_slider: ^2.3.1
and see the given example in link.
use
initialPage: 5,
in carousel_slider parameter to show the initial Image
In cytoscape I'd like to create nodes that are circular, with the diameter depending on the node label (the label is centered in the node). I've set the following:
style: {
'shape': 'ellipse',
'width': 'label'
}
How do I get the height to depend on the width value? Setting 'height': 'label' sets the height to the height of the label.
If you can use a fixed-width font, then #BeerBaron's answer is best.
Alternatively, use the stylesheet you have in the OP:
style: {
'shape': 'ellipse',
'width': 'label',
'height': 'data(height)'
}
Update node heights as a step post-init, e.g.
cy.nodes().forEach(function( n ){ n.data('height', n.width()); });
You should probably preset data.height for each node to some default value at init to ease debugging (e.g. when you add new nodes later).
Depending on label lenght and font, you can set width and height in the javascript part that is appending nodes to the graph, and leave the rest of the style to the initialization of the engine.
For example:
cy.add({
group: 'nodes',
style: {
height: (10*label.lenght),
width: (10*label.lenght),
}
})
The task is to track item's absolute position.
Since item's coordinates are specified relative to its direct parent there's no way to know when item's absolute position have changed in case its parent moves.
Considering example below, I'd like to know when rect2 visually moves due to its parent, i.e. rect1, motion triggered by spacebar key stroke:
import QtQuick 2.2
Item {
id: root
width: 600
height: 200
focus: true
Rectangle {
id: rect1
x: 200
width: 400
height: 200
color: "salmon"
Rectangle {
id: rect2
x: 200
width: 200
height: 200
color: "seagreen"
onXChanged: console.log("rect2 x changed:", x)
}
}
Keys.onSpacePressed: rect1.x = 0
}
As explained on this page, you can add signals among different elements. For your case, you can add the signal xChanged of rect1 to that of rect2.
Component.onCompleted: {
rect1.xChanged.connect(rect2.xChanged);
}
Also, note that it will print 200 still because its position hasn't changed relative to it's parent.
For that you could do something like :
onXChanged: console.log("rect2 x changed:", rect1.x + rect2.x)
Trying to increase navigation bar for iPad
navgroup.height = 80;
Can any on suggest me for increasing the navigation bar for iPad.
Well, the Apple's iOS Human Interface Guidelines states "Don’t specify the height of a navigation bar programmatically,".
So you can't, this is hardcoded to be 44dip on the iPad.
However, you could just make your own navbar view, with your own custom gradient, just float it to the top of your window, this is a start, with a background gradient and custom height of 50px:
var win = Ti.UI.createWindow({
navBarHidden : true
});
var navBar = Ti.UI.createView({
top : 0,
width : Ti.UI.FILL,
height : 50, // Your custom navbar height
backgroundGradient : { // Nice linear gradient, put your own custom colors here
type : 'linear',
startPoint : {
x : 0,
y : 0
},
endPoint : {
x : 0,
y : '100%'
},
colors : [{
color : '#75060a',
offset : 0.0
}, {
color : '#cc0000',
offset : 1.0
}]
}
});
// I usually add a bottom border view, just looks better IMO
navbar.add(Ti.UI.createView({
width : Ti.UI.FILL,
height : 1,
bottom : 0,
backgroundColor : '#000000'
}))
win.add(navBar);
You may want to add custom buttons and titles to this to make it more functional but this should get you started. The nice part about this approach is you have the most control, and its completely cross platform (works on android quite nicely).